diff --git a/README.third_party.md b/README.third_party.md index 8658d2ecced..42576c7aeca 100644 --- a/README.third_party.md +++ b/README.third_party.md @@ -44,7 +44,7 @@ a notice will be included in | [Intel Decimal Floating-Point Math Library] | BSD-3-Clause | v2.0 U1 | | ✗ | | [jbeder/yaml-cpp] | MIT | 0.6.3 | | ✗ | | [JSON-Schema-Test-Suite] | Unknown License | Unknown | | | -| [libmongocrypt] | Apache-2.0 | 1.8.4 | ✗ | ✗ | +| [libmongocrypt] | Apache-2.0 | 1.12.0 | ✗ | ✗ | | [librdkafka - the Apache Kafka C/C++ client library] | BSD-3-Clause, Xmlproc License, ISC, MIT, Public Domain, Zlib, BSD-2-Clause, Andreas Stolcke License | 2.0.2 | | ✗ | | [LibTomCrypt] | WTFPL, Public Domain | 1.18.2 | ✗ | ✗ | | [libunwind/libunwind] | MIT | v1.6.2 | | ✗ | diff --git a/sbom.json b/sbom.json index 8b9c9dac415..be429e5d58f 100644 --- a/sbom.json +++ b/sbom.json @@ -894,7 +894,7 @@ "name": "Organization: github" }, "name": "libmongocrypt", - "version": "1.8.4", + "version": "1.12.0", "licenses": [ { "license": { @@ -902,7 +902,7 @@ } } ], - "purl": "pkg:github/mongodb/libmongocrypt@8354ba537d11494cb0dee21ec6d32fe8ab548c52", + "purl": "pkg:github/mongodb/libmongocrypt@085a0ce6538a28179da6bfd2927aea106924443a", "properties": [ { "name": "internal:team_responsible", diff --git a/src/third_party/libmongocrypt/BUILD.bazel b/src/third_party/libmongocrypt/BUILD.bazel index 5c493112a69..2ad18005607 100644 --- a/src/third_party/libmongocrypt/BUILD.bazel +++ b/src/third_party/libmongocrypt/BUILD.bazel @@ -90,6 +90,7 @@ mongo_cc_library( "dist/src/mc-fle2-payload-uev-common.c", "dist/src/mc-fle2-payload-uev-v2.c", "dist/src/mc-fle2-rfds.c", + "dist/src/mc-fle2-tag-and-encrypted-metadata-block.c", "dist/src/mc-range-edge-generation.c", "dist/src/mc-range-encoding.c", "dist/src/mc-range-mincover.c", diff --git a/src/third_party/libmongocrypt/dist/kms-message/README.md b/src/third_party/libmongocrypt/dist/kms-message/README.md index 2155f5f7449..d45e005e35c 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/README.md +++ b/src/third_party/libmongocrypt/dist/kms-message/README.md @@ -11,7 +11,7 @@ implements the request format. - `test_kms_azure_online` makes live requests, and has additional requirements (must have working credentials). ### Requirements -- A complete installation of the C driver. (libbson is needed for parsing JSON, and libmongoc is used for creating TLS streams). See http://mongoc.org/libmongoc/current/installing.html for installation instructions. For macOS, `brew install mongo-c-driver` will suffice. +- A complete installation of the C driver. (libbson is needed for parsing JSON, and libmongoc is used for creating TLS streams). See the [C Driver Manual](https://www.mongodb.com/docs/languages/c/c-driver/current/libmongoc/tutorials/obtaining-libraries/) for installation instructions. For macOS, `brew install mongo-c-driver` will suffice. - An Azure key vault, and a service principal with an access policy allowing encrypt / decrypt key operations. The following environment variables must be set: - AZURE_TENANT_ID - AZURE_CLIENT_ID diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_gcp_request.c b/src/third_party/libmongocrypt/dist/kms-message/src/kms_gcp_request.c index 564cacc6113..960141bd225 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_gcp_request.c +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_gcp_request.c @@ -87,7 +87,7 @@ kms_gcp_request_oauth_new (const char *host, req->crypto.sign_ctx = opt->crypto.sign_ctx; } - jwt_signature = malloc (SIGNATURE_LEN); + jwt_signature = calloc (1, SIGNATURE_LEN); if (!req->crypto.sign_rsaes_pkcs1_v1_5 ( req->crypto.sign_ctx, private_key_data, diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_reader_writer.c b/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_reader_writer.c index 08affaa948c..56ca01fce49 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_reader_writer.c +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_reader_writer.c @@ -178,6 +178,14 @@ kmip_writer_write_enumeration (kmip_writer_t *writer, kmip_tag_type_t tag, int32 kmip_writer_write_u32 (writer, 0); } +void kmip_writer_write_bool (kmip_writer_t *writer, kmip_tag_type_t tag, bool value) +{ + kmip_writer_write_tag_enum (writer, tag); + kmip_writer_write_u8 (writer, KMIP_ITEM_TYPE_Boolean); + kmip_writer_write_u32 (writer, 8); + kmip_writer_write_u64(writer, (uint64_t) value); +} + void kmip_writer_write_datetime (kmip_writer_t *writer, kmip_tag_type_t tag, int64_t value) { @@ -384,6 +392,15 @@ kmip_reader_read_enumeration (kmip_reader_t *reader, uint32_t *enum_value) return kmip_reader_read_u32 (reader, &ignored); } +bool +kmip_reader_read_bool (kmip_reader_t *reader, bool *value) +{ + uint64_t u64; + CHECK_AND_RET (kmip_reader_read_u64 (reader, &u64)); + *value = (bool) u64; + return true; +} + bool kmip_reader_read_integer (kmip_reader_t *reader, int32_t *value) { diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_reader_writer_private.h b/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_reader_writer_private.h index 314bf394522..a31aa1c6e7d 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_reader_writer_private.h +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_reader_writer_private.h @@ -59,6 +59,9 @@ kmip_writer_write_long_integer (kmip_writer_t *writer, kmip_tag_type_t tag, int6 void kmip_writer_write_enumeration (kmip_writer_t *writer, kmip_tag_type_t tag, int32_t value); +void +kmip_writer_write_bool (kmip_writer_t *writer, kmip_tag_type_t tag, bool value); + void kmip_writer_write_datetime (kmip_writer_t *writer, kmip_tag_type_t tag, int64_t value); @@ -112,6 +115,9 @@ kmip_reader_read_type (kmip_reader_t *reader, kmip_item_type_t *type); bool kmip_reader_read_enumeration (kmip_reader_t *reader, uint32_t *enum_value); +bool +kmip_reader_read_bool (kmip_reader_t *reader, bool *value); + bool kmip_reader_read_integer (kmip_reader_t *reader, int32_t *value); diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_request.c b/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_request.c index c59bff7c5fa..3b9541032b2 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_request.c +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_request.c @@ -16,6 +16,7 @@ #include "kms_message/kms_kmip_request.h" +#include "kms_kmip_tag_type_private.h" #include "kms_message_private.h" #include "kms_kmip_reader_writer_private.h" @@ -181,7 +182,7 @@ kms_kmip_request_activate_new (void *reserved, const char *unique_identifer) kmip_writer_close_struct (writer); /* KMIP_TAG_RequestHeader */ kmip_writer_begin_struct (writer, KMIP_TAG_BatchItem); - /* 0x0A == Get */ + /* 0x12 == Activate */ kmip_writer_write_enumeration (writer, KMIP_TAG_Operation, 0x12); kmip_writer_begin_struct (writer, KMIP_TAG_RequestPayload); kmip_writer_write_string (writer, @@ -254,3 +255,212 @@ kms_kmip_request_get_new (void *reserved, const char *unique_identifer) kmip_writer_destroy (writer); return req; } + +kms_request_t * +kms_kmip_request_create_new (void *reserved) { + /* + Create a KMIP Create request of this form: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ + kmip_writer_t *writer; + kms_request_t *req; + + req = calloc (1, sizeof (kms_request_t)); + req->provider = KMS_REQUEST_PROVIDER_KMIP; + + writer = kmip_writer_new(); + kmip_writer_begin_struct(writer, KMIP_TAG_RequestMessage); + + kmip_writer_begin_struct (writer, KMIP_TAG_RequestHeader); + kmip_writer_begin_struct (writer, KMIP_TAG_ProtocolVersion); + kmip_writer_write_integer (writer, KMIP_TAG_ProtocolVersionMajor, 1); + kmip_writer_write_integer (writer, KMIP_TAG_ProtocolVersionMinor, 2); + kmip_writer_close_struct (writer); /* KMIP_TAG_ProtocolVersion */ + kmip_writer_write_integer (writer, KMIP_TAG_BatchCount, 1); + kmip_writer_close_struct (writer); /* KMIP_TAG_RequestHeader */ + + kmip_writer_begin_struct (writer, KMIP_TAG_BatchItem); + /* 0x01 == Create */ + kmip_writer_write_enumeration (writer, KMIP_TAG_Operation, 0x01); + kmip_writer_begin_struct (writer, KMIP_TAG_RequestPayload); + /* 0x02 == symmetric key */ + kmip_writer_write_enumeration(writer, KMIP_TAG_ObjectType, 0x02); + + { + kmip_writer_begin_struct (writer, KMIP_TAG_TemplateAttribute); + + kmip_writer_begin_struct (writer, KMIP_TAG_Attribute); + const char *cryptographicAlgorithmStr = "Cryptographic Algorithm"; + kmip_writer_write_string (writer, + KMIP_TAG_AttributeName, + cryptographicAlgorithmStr, + strlen (cryptographicAlgorithmStr)); + kmip_writer_write_enumeration (writer, KMIP_TAG_AttributeValue, 3 /* AES */); + kmip_writer_close_struct (writer); + kmip_writer_begin_struct (writer, KMIP_TAG_Attribute); + const char *cryptographicLengthStr = "Cryptographic Length"; + kmip_writer_write_string (writer, + KMIP_TAG_AttributeName, + cryptographicLengthStr, + strlen (cryptographicLengthStr)); + kmip_writer_write_integer (writer, KMIP_TAG_AttributeValue, 256); + kmip_writer_close_struct (writer); + kmip_writer_begin_struct (writer, KMIP_TAG_Attribute); + const char *cryptographicUsageMaskStr = "Cryptographic Usage Mask"; + kmip_writer_write_string (writer, + KMIP_TAG_AttributeName, + cryptographicUsageMaskStr, + strlen (cryptographicUsageMaskStr)); + kmip_writer_write_integer (writer, KMIP_TAG_AttributeValue, 4 | 8 /* Encrypt | Decrypt */); + kmip_writer_close_struct (writer); + + kmip_writer_close_struct (writer); /* KMIP_TAG_TemplateAttribute */ + } + + kmip_writer_close_struct (writer); /* KMIP_TAG_RequestPayload */ + kmip_writer_close_struct (writer); /* KMIP_TAG_BatchItem */ + kmip_writer_close_struct (writer); /* KMIP_TAG_RequestMessage */ + + /* Copy the KMIP writer buffer to a KMIP request. */ + copy_writer_buffer (req, writer); + kmip_writer_destroy (writer); + return req; +} + +static kms_request_t * +kmip_encrypt_decrypt (const char* unique_identifer, const uint8_t *data, size_t len, + const uint8_t *iv_data, size_t iv_len, bool encrypt) { + kmip_writer_t *writer; + kms_request_t *req; + + req = calloc (1, sizeof (kms_request_t)); + req->provider = KMS_REQUEST_PROVIDER_KMIP; + + writer = kmip_writer_new(); + kmip_writer_begin_struct(writer, KMIP_TAG_RequestMessage); + + kmip_writer_begin_struct (writer, KMIP_TAG_RequestHeader); + kmip_writer_begin_struct (writer, KMIP_TAG_ProtocolVersion); + kmip_writer_write_integer (writer, KMIP_TAG_ProtocolVersionMajor, 1); + kmip_writer_write_integer (writer, KMIP_TAG_ProtocolVersionMinor, 2); + kmip_writer_close_struct (writer); /* KMIP_TAG_ProtocolVersion */ + kmip_writer_write_integer (writer, KMIP_TAG_BatchCount, 1); + kmip_writer_close_struct (writer); /* KMIP_TAG_RequestHeader */ + + kmip_writer_begin_struct (writer, KMIP_TAG_BatchItem); + /* 0x1F == Encrypt, 0x20 == Decrypt*/ + kmip_writer_write_enumeration (writer, KMIP_TAG_Operation, encrypt ? 0x1F : 0x20); + kmip_writer_begin_struct (writer, KMIP_TAG_RequestPayload); + kmip_writer_write_string (writer, + KMIP_TAG_UniqueIdentifier, + unique_identifer, + strlen (unique_identifer)); + + kmip_writer_begin_struct (writer, KMIP_TAG_CryptographicParameters); + kmip_writer_write_enumeration(writer, KMIP_TAG_BlockCipherMode, 1 /* CBC */); + kmip_writer_write_enumeration(writer, KMIP_TAG_PaddingMethod, 3 /* PKCS5 */); + kmip_writer_write_enumeration(writer, KMIP_TAG_CryptographicAlgorithm, 3 /* AES */); + if (encrypt) kmip_writer_write_bool(writer, KMIP_TAG_RandomIV, true); + kmip_writer_close_struct(writer); /* KMIP_TAG_CryptographicParameters */ + + kmip_writer_write_bytes(writer, KMIP_TAG_Data, (char *) data, len); + if (!encrypt) kmip_writer_write_bytes(writer, KMIP_TAG_IVCounterNonce, (char *) iv_data, iv_len); + + kmip_writer_close_struct (writer); /* KMIP_TAG_RequestPayload */ + kmip_writer_close_struct (writer); /* KMIP_TAG_BatchItem */ + kmip_writer_close_struct (writer); /* KMIP_TAG_RequestMessage */ + + /* Copy the KMIP writer buffer to a KMIP request. */ + copy_writer_buffer (req, writer); + kmip_writer_destroy (writer); + return req; +} + +kms_request_t * +kms_kmip_request_encrypt_new (void *reserved, const char* unique_identifer, const uint8_t *plaintext, size_t len) { + /* + Create a KMIP Encrypt request of this form: + + + + + + + + + + + + + + + + + + + + + + + */ + return kmip_encrypt_decrypt(unique_identifer, plaintext, len, NULL, 0, true); +} + +kms_request_t * +kms_kmip_request_decrypt_new (void *reserved, const char* unique_identifer, const uint8_t *ciphertext, size_t len, const uint8_t *iv_data, size_t iv_len) { + /* + Create a KMIP Decrypt request of this form: + + + + + + + + + + + + + + + + + + + + + + + */ + return kmip_encrypt_decrypt(unique_identifer, ciphertext, len, iv_data, iv_len, false); +} + diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_response.c b/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_response.c index b4dc2c6fe07..ec389e60134 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_response.c +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_response.c @@ -1,5 +1,7 @@ #include "kms_message/kms_kmip_response.h" +#include "kms_kmip_item_type_private.h" +#include "kms_kmip_tag_type_private.h" #include "kms_message_private.h" #include "kms_kmip_reader_writer_private.h" #include "kms_kmip_result_reason_private.h" @@ -209,6 +211,167 @@ fail: return kms_request_str_detach (nullterminated); } +/* +Example of a successful response to an Encrypt request: + + + + + + + + + + + + + + + + + + + +*/ +uint8_t * +kms_kmip_response_get_iv (kms_response_t *res, size_t *datalen) { + kmip_reader_t *reader = NULL; + size_t pos; + size_t len; + uint8_t *data = NULL; + uint8_t *tmp; + + if (!check_and_require_kmip (res)) { + goto fail; + } + + if (!kms_kmip_response_ok (res)) { + goto fail; + } + + reader = kmip_reader_new (res->kmip.data, res->kmip.len); + + if (!kmip_reader_find_and_recurse (reader, KMIP_TAG_ResponseMessage)) { + KMS_ERROR (res, + "unable to find tag: %s", + kmip_tag_to_string (KMIP_TAG_ResponseMessage)); + goto fail; + } + + if (!kmip_reader_find_and_recurse (reader, KMIP_TAG_BatchItem)) { + KMS_ERROR (res, + "unable to find tag: %s", + kmip_tag_to_string (KMIP_TAG_BatchItem)); + goto fail; + } + + if (!kmip_reader_find_and_recurse (reader, KMIP_TAG_ResponsePayload)) { + KMS_ERROR (res, + "unable to find tag: %s", + kmip_tag_to_string (KMIP_TAG_ResponsePayload)); + goto fail; + } + + if (!kmip_reader_find (reader, KMIP_TAG_IVCounterNonce, KMIP_ITEM_TYPE_ByteString, &pos, &len)) { + KMS_ERROR (res, + "unable to find tag: %s", + kmip_tag_to_string (KMIP_TAG_Data)); + goto fail; + } + + if (!kmip_reader_read_bytes (reader, &tmp, len)) { + KMS_ERROR (res, "unable to read data bytes"); + goto fail; + } + data = malloc (len); + memcpy (data, tmp, len); + *datalen = len; + + fail: + kmip_reader_destroy (reader); + return data; +} + +/* +Example of a successful response to a Decrypt request: + + + + + + + + + + + + + + + + + + +*/ +uint8_t * +kms_kmip_response_get_data (kms_response_t *res, size_t *datalen) { + kmip_reader_t *reader = NULL; + size_t pos; + size_t len; + uint8_t *data = NULL; + uint8_t *tmp; + + if (!check_and_require_kmip (res)) { + goto fail; + } + + if (!kms_kmip_response_ok (res)) { + goto fail; + } + + reader = kmip_reader_new (res->kmip.data, res->kmip.len); + + if (!kmip_reader_find_and_recurse (reader, KMIP_TAG_ResponseMessage)) { + KMS_ERROR (res, + "unable to find tag: %s", + kmip_tag_to_string (KMIP_TAG_ResponseMessage)); + goto fail; + } + + if (!kmip_reader_find_and_recurse (reader, KMIP_TAG_BatchItem)) { + KMS_ERROR (res, + "unable to find tag: %s", + kmip_tag_to_string (KMIP_TAG_BatchItem)); + goto fail; + } + + if (!kmip_reader_find_and_recurse (reader, KMIP_TAG_ResponsePayload)) { + KMS_ERROR (res, + "unable to find tag: %s", + kmip_tag_to_string (KMIP_TAG_ResponsePayload)); + goto fail; + } + + if (!kmip_reader_find (reader, KMIP_TAG_Data, KMIP_ITEM_TYPE_ByteString, &pos, &len)) { + KMS_ERROR (res, + "unable to find tag: %s", + kmip_tag_to_string (KMIP_TAG_Data)); + goto fail; + } + + if (!kmip_reader_read_bytes (reader, &tmp, len)) { + KMS_ERROR (res, "unable to read data bytes"); + goto fail; + } + data = malloc (len); + memcpy (data, tmp, len); + *datalen = len; + + fail: + kmip_reader_destroy (reader); + return data; +} + /* Example of a successful response to a Get request: diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_tag_type_private.h b/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_tag_type_private.h index 293ff1598ae..91df9f9daee 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_tag_type_private.h +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_kmip_tag_type_private.h @@ -312,7 +312,8 @@ KMS_X (AlwaysSensitive, 0x420121) \ KMS_X (Extractable, 0x420122) \ KMS_X (NeverExtractable, 0x420123) \ - KMS_X_LAST (ReplaceExisting, 0x420124) + KMS_X (ReplaceExisting, 0x420124) \ + KMS_X_LAST (Attributes, 0x420125) /* clang-format on */ /* Generate an enum with each tag value. */ diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_kmip_request.h b/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_kmip_request.h index 68eac812089..c688ebcba3b 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_kmip_request.h +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_kmip_request.h @@ -51,6 +51,23 @@ kms_kmip_request_activate_new (void *reserved, const char *unique_identifier); KMS_MSG_EXPORT (kms_request_t *) kms_kmip_request_get_new (void *reserved, const char *unique_identifier); +KMS_MSG_EXPORT (kms_request_t *) +kms_kmip_request_create_new (void *reserved); + +KMS_MSG_EXPORT (kms_request_t *) +kms_kmip_request_encrypt_new (void *reserved, + const char *unique_identifier, + const uint8_t *plaintext, + size_t len); + +KMS_MSG_EXPORT (kms_request_t *) +kms_kmip_request_decrypt_new (void *reserved, + const char *unique_identifier, + const uint8_t *ciphertext, + size_t len, + const uint8_t *iv, + size_t iv_len); + #ifdef __cplusplus } #endif diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_kmip_response.h b/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_kmip_response.h index 4c24c5120e7..a29a87f4b8f 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_kmip_response.h +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_kmip_response.h @@ -37,4 +37,10 @@ kms_kmip_response_get_unique_identifier (kms_response_t *res); KMS_MSG_EXPORT (uint8_t *) kms_kmip_response_get_secretdata (kms_response_t *res, size_t *secretdatalen); +KMS_MSG_EXPORT (uint8_t *) +kms_kmip_response_get_data (kms_response_t *res, size_t *datalen); + +KMS_MSG_EXPORT (uint8_t *) +kms_kmip_response_get_iv (kms_response_t *res, size_t *datalen); + #endif /* KMS_KMIP_RESPONSE_H */ diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_response_parser.h b/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_response_parser.h index 8f134bcf722..915f0ac55a1 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_response_parser.h +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_message/kms_response_parser.h @@ -57,6 +57,9 @@ kms_response_parser_error (kms_response_parser_t *parser); KMS_MSG_EXPORT (void) kms_response_parser_destroy (kms_response_parser_t *parser); +KMS_MSG_EXPORT (void) +kms_response_parser_reset (kms_response_parser_t *parser); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_port.c b/src/third_party/libmongocrypt/dist/kms-message/src/kms_port.c index ee9e6ed9c90..cf5f52f1f56 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_port.c +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_port.c @@ -18,7 +18,8 @@ #if defined(_WIN32) #include #include -char * kms_strndup (const char *src, size_t len) +char * +kms_strndup (const char *src, size_t len) { char *dst = (char *) malloc (len + 1); if (!dst) { @@ -30,4 +31,4 @@ char * kms_strndup (const char *src, size_t len) return dst; } -#endif \ No newline at end of file +#endif diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_request.c b/src/third_party/libmongocrypt/dist/kms-message/src/kms_request.c index d0978d682f4..86f90a1a069 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_request.c +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_request.c @@ -181,10 +181,12 @@ kms_request_set_date (kms_request_t *request, const struct tm *tm) /* use current time */ time_t t; time (&t); -#ifdef _WIN32 +#if defined(KMS_MESSAGE_HAVE_GMTIME_R) + gmtime_r (&t, &tmp_tm); +#elif defined(_MSC_VER) gmtime_s (&tmp_tm, &t); #else - gmtime_r (&t, &tmp_tm); + tmp_tm = *gmtime (&t); #endif tm = &tmp_tm; } diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_request_str.c b/src/third_party/libmongocrypt/dist/kms-message/src/kms_request_str.c index 48ab8103025..57b2e4f9d34 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_request_str.c +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_request_str.c @@ -328,7 +328,7 @@ kms_request_str_append_stripped (kms_request_str_t *str, kms_request_str_reserve (str, appended->len); - // msvcrt is unhappy when it gets non-ANSI characters in isspace + /* msvcrt is unhappy when it gets non-ANSI characters in isspace */ while (*src >= 0 && isspace (*src)) { ++src; } @@ -366,7 +366,7 @@ kms_request_str_append_hashed (_kms_crypto_t *crypto, kms_request_str_t *str, kms_request_str_t *appended) { - uint8_t hash[32]; + uint8_t hash[32] = {0}; char *hex_chars; if (!crypto->sha256 (crypto->ctx, appended->str, appended->len, hash)) { diff --git a/src/third_party/libmongocrypt/dist/kms-message/src/kms_response_parser.c b/src/third_party/libmongocrypt/dist/kms-message/src/kms_response_parser.c index 4d2e7a129f4..67336d70293 100644 --- a/src/third_party/libmongocrypt/dist/kms-message/src/kms_response_parser.c +++ b/src/third_party/libmongocrypt/dist/kms-message/src/kms_response_parser.c @@ -38,6 +38,14 @@ _parser_init (kms_response_parser_t *parser) parser->kmip = NULL; } +void +kms_response_parser_reset (kms_response_parser_t *parser) +{ + KMS_ASSERT(!parser->kmip); // KMIP is not-yet supported. + _parser_destroy(parser); + _parser_init(parser); +} + kms_response_parser_t * kms_response_parser_new (void) { diff --git a/src/third_party/libmongocrypt/dist/src/crypto/libcrypto.c b/src/third_party/libmongocrypt/dist/src/crypto/libcrypto.c index dd1b57873a1..2fee38475d6 100644 --- a/src/third_party/libmongocrypt/dist/src/crypto/libcrypto.c +++ b/src/third_party/libmongocrypt/dist/src/crypto/libcrypto.c @@ -61,7 +61,7 @@ void _native_crypto_init(void) { static bool _encrypt_with_cipher(const EVP_CIPHER *cipher, aes_256_args_t args) { EVP_CIPHER_CTX *ctx; bool ret = false; - int intermediate_bytes_written; + int intermediate_bytes_written = 0; mongocrypt_status_t *status = args.status; ctx = EVP_CIPHER_CTX_new(); @@ -71,8 +71,8 @@ static bool _encrypt_with_cipher(const EVP_CIPHER *cipher, aes_256_args_t args) BSON_ASSERT(args.out); BSON_ASSERT(ctx); BSON_ASSERT(cipher); - BSON_ASSERT(NULL == args.iv || EVP_CIPHER_iv_length(cipher) == args.iv->len); - BSON_ASSERT(EVP_CIPHER_key_length(cipher) == args.key->len); + BSON_ASSERT(NULL == args.iv || (uint32_t)EVP_CIPHER_iv_length(cipher) == args.iv->len); + BSON_ASSERT((uint32_t)EVP_CIPHER_key_length(cipher) == args.key->len); BSON_ASSERT(args.in->len <= INT_MAX); if (!EVP_EncryptInit_ex(ctx, cipher, NULL /* engine */, args.key->data, NULL == args.iv ? NULL : args.iv->data)) { @@ -89,6 +89,7 @@ static bool _encrypt_with_cipher(const EVP_CIPHER *cipher, aes_256_args_t args) goto done; } + BSON_ASSERT(intermediate_bytes_written >= 0 && (uint64_t)intermediate_bytes_written <= UINT32_MAX); /* intermediate_bytes_written cannot be negative, so int -> uint32_t is OK */ *args.bytes_written = (uint32_t)intermediate_bytes_written; @@ -97,7 +98,7 @@ static bool _encrypt_with_cipher(const EVP_CIPHER *cipher, aes_256_args_t args) goto done; } - BSON_ASSERT(UINT32_MAX - *args.bytes_written >= intermediate_bytes_written); + BSON_ASSERT(UINT32_MAX - *args.bytes_written >= (uint32_t)intermediate_bytes_written); *args.bytes_written += (uint32_t)intermediate_bytes_written; ret = true; @@ -116,18 +117,19 @@ done: static bool _decrypt_with_cipher(const EVP_CIPHER *cipher, aes_256_args_t args) { EVP_CIPHER_CTX *ctx; bool ret = false; - int intermediate_bytes_written; + int intermediate_bytes_written = 0; mongocrypt_status_t *status = args.status; ctx = EVP_CIPHER_CTX_new(); + BSON_ASSERT(ctx); BSON_ASSERT_PARAM(cipher); BSON_ASSERT(args.iv); BSON_ASSERT(args.key); BSON_ASSERT(args.in); BSON_ASSERT(args.out); - BSON_ASSERT(EVP_CIPHER_iv_length(cipher) == args.iv->len); - BSON_ASSERT(EVP_CIPHER_key_length(cipher) == args.key->len); + BSON_ASSERT((uint32_t)EVP_CIPHER_iv_length(cipher) == args.iv->len); + BSON_ASSERT((uint32_t)EVP_CIPHER_key_length(cipher) == args.key->len); BSON_ASSERT(args.in->len <= INT_MAX); if (!EVP_DecryptInit_ex(ctx, cipher, NULL /* engine */, args.key->data, args.iv->data)) { @@ -145,6 +147,7 @@ static bool _decrypt_with_cipher(const EVP_CIPHER *cipher, aes_256_args_t args) goto done; } + BSON_ASSERT(intermediate_bytes_written >= 0 && (uint64_t)intermediate_bytes_written <= UINT32_MAX); /* intermediate_bytes_written cannot be negative, so int -> uint32_t is OK */ *args.bytes_written = (uint32_t)intermediate_bytes_written; @@ -153,7 +156,7 @@ static bool _decrypt_with_cipher(const EVP_CIPHER *cipher, aes_256_args_t args) goto done; } - BSON_ASSERT(UINT32_MAX - *args.bytes_written >= intermediate_bytes_written); + BSON_ASSERT(UINT32_MAX - *args.bytes_written >= (uint32_t)intermediate_bytes_written); *args.bytes_written += (uint32_t)intermediate_bytes_written; ret = true; @@ -203,9 +206,9 @@ static bool _hmac_with_hash(const EVP_MD *hash, ctx = HMAC_CTX_new(); - if (out->len != EVP_MD_size(hash)) { + if (out->len != (uint32_t)EVP_MD_size(hash)) { CLIENT_ERR("out does not contain %d bytes", EVP_MD_size(hash)); - return false; + goto done; } if (!HMAC_Init_ex(ctx, key->data, (int)key->len, hash, NULL /* engine */)) { diff --git a/src/third_party/libmongocrypt/dist/src/mc-cmp-private.h b/src/third_party/libmongocrypt/dist/src/mc-cmp-private.h new file mode 100644 index 00000000000..0ed34b076dc --- /dev/null +++ b/src/third_party/libmongocrypt/dist/src/mc-cmp-private.h @@ -0,0 +1,137 @@ +/* + * Copyright 2018-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// `cmp.h` is a modified copy of `bson-cmp.h` from libbson 1.28.0. +#ifndef MC_CMP_H +#define MC_CMP_H + +#include /* ssize_t + BSON_CONCAT */ + +#include +#include +#include + +/* Sanity check: ensure ssize_t limits are as expected relative to size_t. */ +BSON_STATIC_ASSERT2(ssize_t_size_min_check, SSIZE_MIN + 1 == -SSIZE_MAX); +BSON_STATIC_ASSERT2(ssize_t_size_max_check, (size_t)SSIZE_MAX <= SIZE_MAX); + +/* Based on the "Safe Integral Comparisons" proposal merged in C++20: + * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0586r2.html + * + * Due to lack of type deduction in C, relational comparison functions (e.g. + * `cmp_less`) are defined in sets of four "functions" according to the + * signedness of each value argument, e.g.: + * - mc_cmp_less_ss (signed-value, signed-value) + * - mc_cmp_less_uu (unsigned-value, unsigned-value) + * - mc_cmp_less_su (signed-value, unsigned-value) + * - mc_cmp_less_us (unsigned-value, signed-value) + * + * Similarly, the `in_range` function is defined as a set of two "functions" + * according to the signedness of the value argument: + * - mc_in_range_signed (Type, signed-value) + * - mc_in_range_unsigned (Type, unsigned-value) + * + * The user must take care to use the correct signedness for the provided + * argument(s). Enabling compiler warnings for implicit sign conversions is + * recommended. + */ + +#define MC_CMP_SET(op, ss, uu, su, us) \ + static BSON_INLINE bool BSON_CONCAT3(mc_cmp_, op, _ss)(int64_t t, int64_t u) { return (ss); } \ + \ + static BSON_INLINE bool BSON_CONCAT3(mc_cmp_, op, _uu)(uint64_t t, uint64_t u) { return (uu); } \ + \ + static BSON_INLINE bool BSON_CONCAT3(mc_cmp_, op, _su)(int64_t t, uint64_t u) { return (su); } \ + \ + static BSON_INLINE bool BSON_CONCAT3(mc_cmp_, op, _us)(uint64_t t, int64_t u) { return (us); } + +MC_CMP_SET(equal, t == u, t == u, t < 0 ? false : (uint64_t)(t) == u, u < 0 ? false : t == (uint64_t)(u)) + +MC_CMP_SET(not_equal, !mc_cmp_equal_ss(t, u), !mc_cmp_equal_uu(t, u), !mc_cmp_equal_su(t, u), !mc_cmp_equal_us(t, u)) + +MC_CMP_SET(less, t < u, t < u, t < 0 ? true : (uint64_t)(t) < u, u < 0 ? false : t < (uint64_t)(u)) + +MC_CMP_SET(greater, mc_cmp_less_ss(u, t), mc_cmp_less_uu(u, t), mc_cmp_less_us(u, t), mc_cmp_less_su(u, t)) + +MC_CMP_SET(less_equal, + !mc_cmp_greater_ss(t, u), + !mc_cmp_greater_uu(t, u), + !mc_cmp_greater_su(t, u), + !mc_cmp_greater_us(t, u)) + +MC_CMP_SET(greater_equal, !mc_cmp_less_ss(t, u), !mc_cmp_less_uu(t, u), !mc_cmp_less_su(t, u), !mc_cmp_less_us(t, u)) + +#undef MC_CMP_SET + +/* Return true if the given value is within the range of the corresponding + * signed type. The suffix must match the signedness of the given value. */ +#define MC_IN_RANGE_SET_SIGNED(Type, min, max) \ + static BSON_INLINE bool BSON_CONCAT3(mc_in_range, _##Type, _signed)(int64_t value) { \ + return mc_cmp_greater_equal_ss(value, min) && mc_cmp_less_equal_ss(value, max); \ + } \ + \ + static BSON_INLINE bool BSON_CONCAT3(mc_in_range, _##Type, _unsigned)(uint64_t value) { \ + return mc_cmp_greater_equal_us(value, min) && mc_cmp_less_equal_us(value, max); \ + } + +/* Return true if the given value is within the range of the corresponding + * unsigned type. The suffix must match the signedness of the given value. */ +#define MC_IN_RANGE_SET_UNSIGNED(Type, max) \ + static BSON_INLINE bool BSON_CONCAT3(mc_in_range, _##Type, _signed)(int64_t value) { \ + return mc_cmp_greater_equal_su(value, 0u) && mc_cmp_less_equal_su(value, max); \ + } \ + \ + static BSON_INLINE bool BSON_CONCAT3(mc_in_range, _##Type, _unsigned)(uint64_t value) { \ + return mc_cmp_less_equal_uu(value, max); \ + } + +MC_IN_RANGE_SET_SIGNED(signed_char, SCHAR_MIN, SCHAR_MAX) +MC_IN_RANGE_SET_SIGNED(short, SHRT_MIN, SHRT_MAX) +MC_IN_RANGE_SET_SIGNED(int, INT_MIN, INT_MAX) +MC_IN_RANGE_SET_SIGNED(long, LONG_MIN, LONG_MAX) +MC_IN_RANGE_SET_SIGNED(long_long, LLONG_MIN, LLONG_MAX) + +MC_IN_RANGE_SET_UNSIGNED(unsigned_char, UCHAR_MAX) +MC_IN_RANGE_SET_UNSIGNED(unsigned_short, USHRT_MAX) +MC_IN_RANGE_SET_UNSIGNED(unsigned_int, UINT_MAX) +MC_IN_RANGE_SET_UNSIGNED(unsigned_long, ULONG_MAX) +MC_IN_RANGE_SET_UNSIGNED(unsigned_long_long, ULLONG_MAX) + +MC_IN_RANGE_SET_SIGNED(int8_t, INT8_MIN, INT8_MAX) +MC_IN_RANGE_SET_SIGNED(int16_t, INT16_MIN, INT16_MAX) +MC_IN_RANGE_SET_SIGNED(int32_t, INT32_MIN, INT32_MAX) +MC_IN_RANGE_SET_SIGNED(int64_t, INT64_MIN, INT64_MAX) + +MC_IN_RANGE_SET_UNSIGNED(uint8_t, UINT8_MAX) +MC_IN_RANGE_SET_UNSIGNED(uint16_t, UINT16_MAX) +MC_IN_RANGE_SET_UNSIGNED(uint32_t, UINT32_MAX) +MC_IN_RANGE_SET_UNSIGNED(uint64_t, UINT64_MAX) + +MC_IN_RANGE_SET_SIGNED(ssize_t, SSIZE_MIN, SSIZE_MAX) +MC_IN_RANGE_SET_UNSIGNED(size_t, SIZE_MAX) + +#undef MC_IN_RANGE_SET_SIGNED +#undef MC_IN_RANGE_SET_UNSIGNED + +/* Return true if the value with *signed* type is in the representable range of + * Type and false otherwise. */ +#define mc_in_range_signed(Type, value) BSON_CONCAT3(mc_in_range, _##Type, _signed)(value) + +/* Return true if the value with *unsigned* type is in the representable range + * of Type and false otherwise. */ +#define mc_in_range_unsigned(Type, value) BSON_CONCAT3(mc_in_range, _##Type, _unsigned)(value) + +#endif /* MC_CMP_H */ diff --git a/src/third_party/libmongocrypt/dist/src/mc-dec128.h b/src/third_party/libmongocrypt/dist/src/mc-dec128.h index 4495714146e..fd994874fbf 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-dec128.h +++ b/src/third_party/libmongocrypt/dist/src/mc-dec128.h @@ -607,7 +607,7 @@ static inline char *mc_dec128_to_new_decimal_string(mc_dec128 d) { } } -static inline mc_dec128 mc_dec128_from_bson_iter(bson_iter_t *it) { +static inline mc_dec128 mc_dec128_from_bson_iter(const bson_iter_t *it) { bson_decimal128_t b; if (!bson_iter_decimal128(it, &b)) { mc_dec128 nan = MC_DEC128_POSITIVE_NAN; diff --git a/src/third_party/libmongocrypt/dist/src/mc-efc-private.h b/src/third_party/libmongocrypt/dist/src/mc-efc-private.h index c4ff8060585..f9e5c59faf5 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-efc-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-efc-private.h @@ -19,8 +19,19 @@ #include "mongocrypt-buffer-private.h" #include +typedef enum _supported_query_type_flags { + // No queries supported + SUPPORTS_NO_QUERIES = 0, + // Equality query supported + SUPPORTS_EQUALITY_QUERIES = 1 << 0, + // Range query supported + SUPPORTS_RANGE_QUERIES = 1 << 1, + // Range preview query supported + SUPPORTS_RANGE_PREVIEW_DEPRECATED_QUERIES = 1 << 2, +} supported_query_type_flags; + typedef struct _mc_EncryptedField_t { - bool has_queries; + supported_query_type_flags supported_queries; _mongocrypt_buffer_t keyId; const char *path; struct _mc_EncryptedField_t *next; @@ -37,7 +48,10 @@ typedef struct { * into @efc. Fields are copied from @efc_bson. It is OK to free efc_bson after * this call. Fields are appended in reverse order to @efc->fields. Extra * unrecognized fields are not considered an error for forward compatibility. */ -bool mc_EncryptedFieldConfig_parse(mc_EncryptedFieldConfig_t *efc, const bson_t *efc_bson, mongocrypt_status_t *status); +bool mc_EncryptedFieldConfig_parse(mc_EncryptedFieldConfig_t *efc, + const bson_t *efc_bson, + mongocrypt_status_t *status, + bool use_range_v2); void mc_EncryptedFieldConfig_cleanup(mc_EncryptedFieldConfig_t *efc); diff --git a/src/third_party/libmongocrypt/dist/src/mc-efc.c b/src/third_party/libmongocrypt/dist/src/mc-efc.c index dd75e0fc0f1..1a5e6f7dae2 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-efc.c +++ b/src/third_party/libmongocrypt/dist/src/mc-efc.c @@ -16,12 +16,64 @@ #include "mc-efc-private.h" +#include "mlib/str.h" #include "mongocrypt-private.h" #include "mongocrypt-util-private.h" // mc_iter_document_as_bson +static bool _parse_query_type_string(const char *queryType, supported_query_type_flags *out) { + BSON_ASSERT_PARAM(queryType); + BSON_ASSERT_PARAM(out); + + mstr_view qtv = mstrv_view_cstr(queryType); + + if (mstr_eq_ignore_case(mstrv_lit(MONGOCRYPT_QUERY_TYPE_EQUALITY_STR), qtv)) { + *out = SUPPORTS_EQUALITY_QUERIES; + } else if (mstr_eq_ignore_case(mstrv_lit(MONGOCRYPT_QUERY_TYPE_RANGE_STR), qtv)) { + *out = SUPPORTS_RANGE_QUERIES; + } else if (mstr_eq_ignore_case(mstrv_lit(MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_DEPRECATED_STR), qtv)) { + *out = SUPPORTS_RANGE_PREVIEW_DEPRECATED_QUERIES; + } else { + return false; + } + + return true; +} + +static bool +_parse_supported_query_types(bson_iter_t *iter, supported_query_type_flags *out, mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iter); + BSON_ASSERT_PARAM(out); + if (!BSON_ITER_HOLDS_DOCUMENT(iter)) { + CLIENT_ERR("When parsing supported query types: Expected type document, got: %d", bson_iter_type(iter)); + return false; + } + + bson_t query_doc; + if (!mc_iter_document_as_bson(iter, &query_doc, status)) { + return false; + } + bson_iter_t query_type_iter; + if (!bson_iter_init_find(&query_type_iter, &query_doc, "queryType")) { + CLIENT_ERR("When parsing supported query types: Unable to find 'queryType' in query document"); + return false; + } + if (!BSON_ITER_HOLDS_UTF8(&query_type_iter)) { + CLIENT_ERR("When parsing supported query types: Expected 'queryType' to be type UTF-8, got: %d", + bson_iter_type(&query_type_iter)); + return false; + } + const char *queryType = bson_iter_utf8(&query_type_iter, NULL /* length */); + if (!_parse_query_type_string(queryType, out)) { + CLIENT_ERR("When parsing supported query types: Did not recognize query type '%s'", queryType); + return false; + } + return true; +} + /* _parse_field parses and prepends one field document to efc->fields. */ -static bool _parse_field(mc_EncryptedFieldConfig_t *efc, bson_t *field, mongocrypt_status_t *status) { - bool has_queries = false; +static bool +_parse_field(mc_EncryptedFieldConfig_t *efc, bson_t *field, mongocrypt_status_t *status, bool use_range_v2) { + supported_query_type_flags query_types = SUPPORTS_NO_QUERIES; bson_iter_t field_iter; BSON_ASSERT_PARAM(efc); @@ -53,7 +105,42 @@ static bool _parse_field(mc_EncryptedFieldConfig_t *efc, bson_t *field, mongocry field_path = bson_iter_utf8(&field_iter, NULL /* length */); if (bson_iter_init_find(&field_iter, field, "queries")) { - has_queries = true; + if (BSON_ITER_HOLDS_ARRAY(&field_iter)) { + // Multiple queries, iterate through and grab all query types. + uint32_t queries_buf_len; + const uint8_t *queries_buf; + bson_t queries_arr; + bson_iter_array(&field_iter, &queries_buf_len, &queries_buf); + if (!bson_init_static(&queries_arr, queries_buf, queries_buf_len)) { + CLIENT_ERR("Failed to parse 'queries' field"); + return false; + } + + bson_iter_t queries_iter; + bson_iter_init(&queries_iter, &queries_arr); + while (bson_iter_next(&queries_iter)) { + supported_query_type_flags flag; + if (!_parse_supported_query_types(&queries_iter, &flag, status)) { + return false; + } + query_types |= flag; + } + } else { + supported_query_type_flags flag; + if (!_parse_supported_query_types(&field_iter, &flag, status)) { + return false; + } + query_types |= flag; + } + } + + if (query_types & SUPPORTS_RANGE_PREVIEW_DEPRECATED_QUERIES && use_range_v2) { + // When rangev2 is enabled ("range") error if "rangePreview" is included. + // This check is intended to give an easier-to-understand earlier error. + CLIENT_ERR("Cannot use field '%s' with 'rangePreview' queries. 'rangePreview' is unsupported. Use 'range' " + "instead. 'range' is not compatible with 'rangePreview' and requires recreating the collection.", + field_path); + return false; } /* Prepend a new mc_EncryptedField_t */ @@ -61,7 +148,7 @@ static bool _parse_field(mc_EncryptedFieldConfig_t *efc, bson_t *field, mongocry _mongocrypt_buffer_copy_to(&field_keyid, &ef->keyId); ef->path = bson_strdup(field_path); ef->next = efc->fields; - ef->has_queries = has_queries; + ef->supported_queries = query_types; efc->fields = ef; return true; @@ -69,7 +156,8 @@ static bool _parse_field(mc_EncryptedFieldConfig_t *efc, bson_t *field, mongocry bool mc_EncryptedFieldConfig_parse(mc_EncryptedFieldConfig_t *efc, const bson_t *efc_bson, - mongocrypt_status_t *status) { + mongocrypt_status_t *status, + bool use_range_v2) { bson_iter_t iter; BSON_ASSERT_PARAM(efc); @@ -93,7 +181,7 @@ bool mc_EncryptedFieldConfig_parse(mc_EncryptedFieldConfig_t *efc, if (!mc_iter_document_as_bson(&iter, &field, status)) { return false; } - if (!_parse_field(efc, &field, status)) { + if (!_parse_field(efc, &field, status, use_range_v2)) { return false; } } diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle-blob-subtype-private.h b/src/third_party/libmongocrypt/dist/src/mc-fle-blob-subtype-private.h index 949b22f29d3..3fda8e97413 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle-blob-subtype-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-fle-blob-subtype-private.h @@ -19,7 +19,7 @@ /* FLE Blob Subtype is the first byte of a BSON Binary Subtype 6. * FLE1 Blob Subtypes are defined in: - * https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/subtype6.rst + * https://github.com/mongodb/specifications/blob/master/source/bson-binary-encrypted/binary-encrypted.md * FLE2 Blob Subtypes are currently defined in: * https://github.com/markbenvenuto/mongo-enterprise-modules/blob/fle2/fle_protocol.md#reference-bindata-6-subtypes. */ diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-encryption-placeholder-private.h b/src/third_party/libmongocrypt/dist/src/mc-fle2-encryption-placeholder-private.h index d1ec121375e..b2168dadaa3 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-encryption-placeholder-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-encryption-placeholder-private.h @@ -49,9 +49,16 @@ typedef struct { bson_iter_t indexMax; // precision determines the number of digits after the decimal point for // floating point values. - mc_optional_uint32_t precision; + mc_optional_int32_t precision; + // trimFactor determines how many root levels of the hypergraph to trim. + mc_optional_int32_t trimFactor; } mc_FLE2RangeFindSpecEdgesInfo_t; +// `mc_FLE2RangeFindSpecEdgesInfo_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof_mc_FLE2RangeFindSpecEdgesInfo_t, + BSON_ALIGNOF(mc_FLE2RangeFindSpecEdgesInfo_t) >= BSON_ALIGNOF(bson_iter_t)); + /** FLE2RangeFindSpec represents the range find specification that is encoded * inside of a FLE2EncryptionPlaceholder. See * https://github.com/mongodb/mongo/blob/master/src/mongo/crypto/fle_field_schema.idl @@ -74,7 +81,15 @@ typedef struct { mc_FLE2RangeOperator_t secondOperator; } mc_FLE2RangeFindSpec_t; -bool mc_FLE2RangeFindSpec_parse(mc_FLE2RangeFindSpec_t *out, const bson_iter_t *in, mongocrypt_status_t *status); +// `mc_FLE2RangeFindSpec_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof_mc_FLE2RangeFindSpec_t, + BSON_ALIGNOF(mc_FLE2RangeFindSpec_t) >= BSON_ALIGNOF(mc_FLE2RangeFindSpecEdgesInfo_t)); + +bool mc_FLE2RangeFindSpec_parse(mc_FLE2RangeFindSpec_t *out, + const bson_iter_t *in, + bool use_range_v2, + mongocrypt_status_t *status); /** mc_FLE2RangeInsertSpec_t represents the range insert specification that is * encoded inside of a FLE2EncryptionPlaceholder. See @@ -89,10 +104,20 @@ typedef struct { bson_iter_t max; // precision determines the number of digits after the decimal point for // floating point values. - mc_optional_uint32_t precision; + mc_optional_int32_t precision; + // trimFactor determines how many root levels of the hypergraph to trim. + mc_optional_int32_t trimFactor; } mc_FLE2RangeInsertSpec_t; -bool mc_FLE2RangeInsertSpec_parse(mc_FLE2RangeInsertSpec_t *out, const bson_iter_t *in, mongocrypt_status_t *status); +// `mc_FLE2RangeInsertSpec_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof_mc_FLE2RangeInsertSpec_t, + BSON_ALIGNOF(mc_FLE2RangeInsertSpec_t) >= BSON_ALIGNOF(bson_iter_t)); + +bool mc_FLE2RangeInsertSpec_parse(mc_FLE2RangeInsertSpec_t *out, + const bson_iter_t *in, + bool use_range_v2, + mongocrypt_status_t *status); /** FLE2EncryptionPlaceholder implements Encryption BinData (subtype 6) * sub-subtype 0, the intent-to-encrypt mapping. Contains a value to encrypt and @@ -116,11 +141,16 @@ typedef struct { bson_iter_t v_iter; _mongocrypt_buffer_t index_key_id; _mongocrypt_buffer_t user_key_id; - int64_t maxContentionCounter; + int64_t maxContentionFactor; // sparsity is the Queryable Encryption range hypergraph sparsity factor int64_t sparsity; } mc_FLE2EncryptionPlaceholder_t; +// `mc_FLE2EncryptionPlaceholder_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof_mc_FLE2EncryptionPlaceholder_t, + BSON_ALIGNOF(mc_FLE2EncryptionPlaceholder_t) >= BSON_ALIGNOF(bson_iter_t)); + void mc_FLE2EncryptionPlaceholder_init(mc_FLE2EncryptionPlaceholder_t *placeholder); bool mc_FLE2EncryptionPlaceholder_parse(mc_FLE2EncryptionPlaceholder_t *out, diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-encryption-placeholder.c b/src/third_party/libmongocrypt/dist/src/mc-fle2-encryption-placeholder.c index 4a63b6b235c..fc839b88752 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-encryption-placeholder.c +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-encryption-placeholder.c @@ -22,11 +22,14 @@ #include "mongocrypt-buffer-private.h" #include "mongocrypt.h" +#define CLIENT_ERR_PREFIXED_HELPER(Prefix, ErrorString, ...) CLIENT_ERR(Prefix ": " ErrorString, ##__VA_ARGS__) +#define CLIENT_ERR_PREFIXED(ErrorString, ...) CLIENT_ERR_PREFIXED_HELPER(ERROR_PREFIX, ErrorString, ##__VA_ARGS__) + // Common logic for testing field name, tracking duplication, and presence. #define IF_FIELD(Name) \ if (0 == strcmp(field, #Name)) { \ if (has_##Name) { \ - CLIENT_ERR("Duplicate field '" #Name "' in placeholder bson"); \ + CLIENT_ERR_PREFIXED("Duplicate field '" #Name "' in placeholder bson"); \ goto fail; \ } \ has_##Name = true; @@ -37,7 +40,7 @@ #define CHECK_HAS(Name) \ if (!has_##Name) { \ - CLIENT_ERR("Missing field '" #Name "' in placeholder"); \ + CLIENT_ERR_PREFIXED("Missing field '" #Name "' in placeholder"); \ goto fail; \ } @@ -45,10 +48,12 @@ void mc_FLE2EncryptionPlaceholder_init(mc_FLE2EncryptionPlaceholder_t *placehold memset(placeholder, 0, sizeof(mc_FLE2EncryptionPlaceholder_t)); } +#define ERROR_PREFIX "Error parsing FLE2EncryptionPlaceholder" + bool mc_FLE2EncryptionPlaceholder_parse(mc_FLE2EncryptionPlaceholder_t *out, const bson_t *in, mongocrypt_status_t *status) { - bson_iter_t iter; + bson_iter_t iter = {0}; bool has_t = false, has_a = false, has_v = false, has_cm = false; bool has_ki = false, has_ku = false; bool has_s = false; @@ -58,7 +63,7 @@ bool mc_FLE2EncryptionPlaceholder_parse(mc_FLE2EncryptionPlaceholder_t *out, mc_FLE2EncryptionPlaceholder_init(out); if (!bson_validate(in, BSON_VALIDATE_NONE, NULL) || !bson_iter_init(&iter, in)) { - CLIENT_ERR("invalid BSON"); + CLIENT_ERR_PREFIXED("invalid BSON"); return false; } @@ -69,12 +74,12 @@ bool mc_FLE2EncryptionPlaceholder_parse(mc_FLE2EncryptionPlaceholder_t *out, IF_FIELD(t) { int32_t type; if (!BSON_ITER_HOLDS_INT32(&iter)) { - CLIENT_ERR("invalid marking, 't' must be an int32"); + CLIENT_ERR_PREFIXED("invalid marking, 't' must be an int32"); goto fail; } type = bson_iter_int32(&iter); if ((type != MONGOCRYPT_FLE2_PLACEHOLDER_TYPE_INSERT) && (type != MONGOCRYPT_FLE2_PLACEHOLDER_TYPE_FIND)) { - CLIENT_ERR("invalid placeholder type value: %d", type); + CLIENT_ERR_PREFIXED("invalid placeholder type value: %d", type); goto fail; } out->type = (mongocrypt_fle2_placeholder_type_t)type; @@ -84,13 +89,13 @@ bool mc_FLE2EncryptionPlaceholder_parse(mc_FLE2EncryptionPlaceholder_t *out, IF_FIELD(a) { int32_t algorithm; if (!BSON_ITER_HOLDS_INT32(&iter)) { - CLIENT_ERR("invalid marking, 'a' must be an int32"); + CLIENT_ERR_PREFIXED("invalid marking, 'a' must be an int32"); goto fail; } algorithm = bson_iter_int32(&iter); if (algorithm != MONGOCRYPT_FLE2_ALGORITHM_UNINDEXED && algorithm != MONGOCRYPT_FLE2_ALGORITHM_EQUALITY && algorithm != MONGOCRYPT_FLE2_ALGORITHM_RANGE) { - CLIENT_ERR("invalid algorithm value: %d", algorithm); + CLIENT_ERR_PREFIXED("invalid algorithm value: %d", algorithm); goto fail; } out->algorithm = (mongocrypt_fle2_encryption_algorithm_t)algorithm; @@ -99,7 +104,7 @@ bool mc_FLE2EncryptionPlaceholder_parse(mc_FLE2EncryptionPlaceholder_t *out, IF_FIELD(ki) { if (!_mongocrypt_buffer_from_uuid_iter(&out->index_key_id, &iter)) { - CLIENT_ERR("index key id must be a UUID"); + CLIENT_ERR_PREFIXED("index key id must be a UUID"); goto fail; } } @@ -107,7 +112,7 @@ bool mc_FLE2EncryptionPlaceholder_parse(mc_FLE2EncryptionPlaceholder_t *out, IF_FIELD(ku) { if (!_mongocrypt_buffer_from_uuid_iter(&out->user_key_id, &iter)) { - CLIENT_ERR("user key id must be a UUID"); + CLIENT_ERR_PREFIXED("user key id must be a UUID"); goto fail; } } @@ -120,11 +125,11 @@ bool mc_FLE2EncryptionPlaceholder_parse(mc_FLE2EncryptionPlaceholder_t *out, IF_FIELD(cm) { if (!BSON_ITER_HOLDS_INT64(&iter)) { - CLIENT_ERR("invalid marking, 'cm' must be an int64"); + CLIENT_ERR_PREFIXED("invalid marking, 'cm' must be an int64"); goto fail; } - out->maxContentionCounter = bson_iter_int64(&iter); - if (!mc_validate_contention(out->maxContentionCounter, status)) { + out->maxContentionFactor = bson_iter_int64(&iter); + if (!mc_validate_contention(out->maxContentionFactor, status)) { goto fail; } } @@ -132,7 +137,7 @@ bool mc_FLE2EncryptionPlaceholder_parse(mc_FLE2EncryptionPlaceholder_t *out, IF_FIELD(s) { if (!BSON_ITER_HOLDS_INT64(&iter)) { - CLIENT_ERR("invalid marking, 's' must be an int64"); + CLIENT_ERR_PREFIXED("invalid marking, 's' must be an int64"); goto fail; } out->sparsity = bson_iter_int64(&iter); @@ -168,37 +173,47 @@ void mc_FLE2EncryptionPlaceholder_cleanup(mc_FLE2EncryptionPlaceholder_t *placeh mc_FLE2EncryptionPlaceholder_init(placeholder); } +#undef ERROR_PREFIX +#define ERROR_PREFIX "Error validating contention" + bool mc_validate_contention(int64_t contention, mongocrypt_status_t *status) { if (contention < 0) { - CLIENT_ERR("contention must be non-negative, got: %" PRId64, contention); + CLIENT_ERR_PREFIXED("contention must be non-negative, got: %" PRId64, contention); return false; } if (contention == INT64_MAX) { - CLIENT_ERR("contention must be < INT64_MAX, got: %" PRId64, contention); + CLIENT_ERR_PREFIXED("contention must be < INT64_MAX, got: %" PRId64, contention); return false; } return true; } +#undef ERROR_PREFIX +#define ERROR_PREFIX "Error validating sparsity" + bool mc_validate_sparsity(int64_t sparsity, mongocrypt_status_t *status) { if (sparsity < 0) { - CLIENT_ERR("sparsity must be non-negative, got: %" PRId64, sparsity); + CLIENT_ERR_PREFIXED("sparsity must be non-negative, got: %" PRId64, sparsity); return false; } // mc_getEdgesInt expects a size_t sparsity. - if (sparsity >= SIZE_MAX) { - CLIENT_ERR("sparsity must be < %zu, got: %" PRId64, SIZE_MAX, sparsity); + if ((uint64_t)sparsity >= SIZE_MAX) { + CLIENT_ERR_PREFIXED("sparsity must be < %zu, got: %" PRId64, SIZE_MAX, sparsity); return false; } return true; } +#undef ERROR_PREFIX +#define ERROR_PREFIX "Error parsing FLE2RangeFindSpecEdgesInfo" + static bool mc_FLE2RangeFindSpecEdgesInfo_parse(mc_FLE2RangeFindSpecEdgesInfo_t *out, const bson_iter_t *in, + bool use_range_v2, mongocrypt_status_t *status) { bson_iter_t iter; bool has_lowerBound = false, has_lbIncluded = false, has_upperBound = false, has_ubIncluded = false, - has_indexMin = false, has_indexMax = false, has_precision = false; + has_indexMin = false, has_indexMax = false, has_precision = false, has_trimFactor = false; BSON_ASSERT_PARAM(out); BSON_ASSERT_PARAM(in); @@ -206,8 +221,7 @@ static bool mc_FLE2RangeFindSpecEdgesInfo_parse(mc_FLE2RangeFindSpecEdgesInfo_t iter = *in; if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) { - CLIENT_ERR("invalid FLE2RangeFindSpecEdgesInfo: must be an iterator to " - "a document"); + CLIENT_ERR_PREFIXED("must be an iterator to a document"); return false; } bson_iter_recurse(&iter, &iter); @@ -223,8 +237,7 @@ static bool mc_FLE2RangeFindSpecEdgesInfo_parse(mc_FLE2RangeFindSpecEdgesInfo_t IF_FIELD(lbIncluded) { if (!BSON_ITER_HOLDS_BOOL(&iter)) { - CLIENT_ERR("invalid FLE2RangeFindSpecEdgesInfo: 'lbIncluded' must " - "be a bool"); + CLIENT_ERR_PREFIXED("'lbIncluded' must be a bool"); goto fail; } out->lbIncluded = bson_iter_bool(&iter); @@ -238,8 +251,7 @@ static bool mc_FLE2RangeFindSpecEdgesInfo_parse(mc_FLE2RangeFindSpecEdgesInfo_t IF_FIELD(ubIncluded) { if (!BSON_ITER_HOLDS_BOOL(&iter)) { - CLIENT_ERR("invalid FLE2RangeFindSpecEdgesInfo: 'ubIncluded' must " - "be a bool"); + CLIENT_ERR_PREFIXED("'ubIncluded' must be a bool"); goto fail; } out->ubIncluded = bson_iter_bool(&iter); @@ -258,18 +270,31 @@ static bool mc_FLE2RangeFindSpecEdgesInfo_parse(mc_FLE2RangeFindSpecEdgesInfo_t IF_FIELD(precision) { if (!BSON_ITER_HOLDS_INT32(&iter)) { - CLIENT_ERR("invalid FLE2RangeFindSpecEdgesInfo: 'precision' must " - "be an int32"); + CLIENT_ERR_PREFIXED("'precision' must be an int32"); goto fail; } int32_t val = bson_iter_int32(&iter); if (val < 0) { - CLIENT_ERR("invalid FLE2RangeFindSpecEdgesInfo: 'precision' must be" - "non-negative"); + CLIENT_ERR_PREFIXED("'precision' must be non-negative"); goto fail; } - out->precision = OPT_U32((uint32_t)val); + out->precision = OPT_I32(val); + } + END_IF_FIELD + + IF_FIELD(trimFactor) { + if (!BSON_ITER_HOLDS_INT32(&iter)) { + CLIENT_ERR_PREFIXED("'trimFactor' must be an int32"); + goto fail; + } + int32_t val = bson_iter_int32(&iter); + if (val < 0) { + CLIENT_ERR_PREFIXED("'trimFactor' must be non-negative"); + goto fail; + } + + out->trimFactor = OPT_I32(val); } END_IF_FIELD } @@ -282,13 +307,25 @@ static bool mc_FLE2RangeFindSpecEdgesInfo_parse(mc_FLE2RangeFindSpecEdgesInfo_t CHECK_HAS(indexMax) // Do not error if precision is not present. Precision optional and only // applies to double/decimal128. + + if (!use_range_v2 && out->trimFactor.set) { + CLIENT_ERR_PREFIXED("'trimFactor' is not supported for QE range v1"); + return false; + } + return true; fail: return false; } -bool mc_FLE2RangeFindSpec_parse(mc_FLE2RangeFindSpec_t *out, const bson_iter_t *in, mongocrypt_status_t *status) { +#undef ERROR_PREFIX +#define ERROR_PREFIX "Error parsing FLE2RangeFindSpec" + +bool mc_FLE2RangeFindSpec_parse(mc_FLE2RangeFindSpec_t *out, + const bson_iter_t *in, + bool use_range_v2, + mongocrypt_status_t *status) { BSON_ASSERT_PARAM(out); BSON_ASSERT_PARAM(in); @@ -298,7 +335,7 @@ bool mc_FLE2RangeFindSpec_parse(mc_FLE2RangeFindSpec_t *out, const bson_iter_t * *out = (mc_FLE2RangeFindSpec_t){{{{0}}}}; if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) { - CLIENT_ERR("invalid FLE2RangeFindSpec: must be an iterator to a document"); + CLIENT_ERR_PREFIXED("must be an iterator to a document"); return false; } bson_iter_recurse(&iter, &iter); @@ -308,7 +345,7 @@ bool mc_FLE2RangeFindSpec_parse(mc_FLE2RangeFindSpec_t *out, const bson_iter_t * BSON_ASSERT(field); IF_FIELD(edgesInfo) { - if (!mc_FLE2RangeFindSpecEdgesInfo_parse(&out->edgesInfo.value, &iter, status)) { + if (!mc_FLE2RangeFindSpecEdgesInfo_parse(&out->edgesInfo.value, &iter, use_range_v2, status)) { goto fail; } out->edgesInfo.set = true; @@ -317,7 +354,7 @@ bool mc_FLE2RangeFindSpec_parse(mc_FLE2RangeFindSpec_t *out, const bson_iter_t * IF_FIELD(payloadId) { if (!BSON_ITER_HOLDS_INT32(&iter)) { - CLIENT_ERR("invalid FLE2RangeFindSpec: 'payloadId' must be an int32"); + CLIENT_ERR_PREFIXED("'payloadId' must be an int32"); goto fail; } out->payloadId = bson_iter_int32(&iter); @@ -326,15 +363,14 @@ bool mc_FLE2RangeFindSpec_parse(mc_FLE2RangeFindSpec_t *out, const bson_iter_t * IF_FIELD(firstOperator) { if (!BSON_ITER_HOLDS_INT32(&iter)) { - CLIENT_ERR("invalid FLE2RangeFindSpec: 'firstOperator' must be an int32"); + CLIENT_ERR_PREFIXED("'firstOperator' must be an int32"); goto fail; } const int32_t first_op = bson_iter_int32(&iter); if (first_op < FLE2RangeOperator_min_val || first_op > FLE2RangeOperator_max_val) { - CLIENT_ERR("invalid FLE2RangeFindSpec: 'firstOperator' must be " - "between %d and %d", - FLE2RangeOperator_min_val, - FLE2RangeOperator_max_val); + CLIENT_ERR_PREFIXED("'firstOperator' must be between %d and %d", + FLE2RangeOperator_min_val, + FLE2RangeOperator_max_val); goto fail; } out->firstOperator = (mc_FLE2RangeOperator_t)first_op; @@ -343,15 +379,14 @@ bool mc_FLE2RangeFindSpec_parse(mc_FLE2RangeFindSpec_t *out, const bson_iter_t * IF_FIELD(secondOperator) { if (!BSON_ITER_HOLDS_INT32(&iter)) { - CLIENT_ERR("invalid FLE2RangeFindSpec: 'secondOperator' must be an int32"); + CLIENT_ERR_PREFIXED("'secondOperator' must be an int32"); goto fail; } const int32_t second_op = bson_iter_int32(&iter); if (second_op < FLE2RangeOperator_min_val || second_op > FLE2RangeOperator_max_val) { - CLIENT_ERR("invalid FLE2RangeFindSpec: 'secondOperator' must be " - "between %d and %d", - FLE2RangeOperator_min_val, - FLE2RangeOperator_max_val); + CLIENT_ERR_PREFIXED("'secondOperator' must be between %d and %d", + FLE2RangeOperator_min_val, + FLE2RangeOperator_max_val); goto fail; } out->secondOperator = (mc_FLE2RangeOperator_t)second_op; @@ -369,17 +404,23 @@ fail: return false; } -bool mc_FLE2RangeInsertSpec_parse(mc_FLE2RangeInsertSpec_t *out, const bson_iter_t *in, mongocrypt_status_t *status) { +#undef ERROR_PREFIX +#define ERROR_PREFIX "Error parsing FLE2RangeInsertSpec" + +bool mc_FLE2RangeInsertSpec_parse(mc_FLE2RangeInsertSpec_t *out, + const bson_iter_t *in, + bool use_range_v2, + mongocrypt_status_t *status) { BSON_ASSERT_PARAM(out); BSON_ASSERT_PARAM(in); *out = (mc_FLE2RangeInsertSpec_t){{0}}; bson_iter_t iter = *in; - bool has_v = false, has_min = false, has_max = false, has_precision = false; + bool has_v = false, has_min = false, has_max = false, has_precision = false, has_trimFactor = false; if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) { - CLIENT_ERR("invalid FLE2RangeInsertSpec: must be an iterator to a document"); + CLIENT_ERR_PREFIXED("must be an iterator to a document"); return false; } bson_iter_recurse(&iter, &iter); @@ -405,17 +446,29 @@ bool mc_FLE2RangeInsertSpec_parse(mc_FLE2RangeInsertSpec_t *out, const bson_iter IF_FIELD(precision) { if (!BSON_ITER_HOLDS_INT32(&iter)) { - CLIENT_ERR("invalid FLE2RangeFindSpecEdgesInfo: 'precision' must " - "be an int32"); + CLIENT_ERR_PREFIXED("'precision' must be an int32"); goto fail; } int32_t val = bson_iter_int32(&iter); if (val < 0) { - CLIENT_ERR("invalid FLE2RangeFindSpecEdgesInfo: 'precision' must be" - "non-negative"); + CLIENT_ERR_PREFIXED("'precision' must be non-negative"); goto fail; } - out->precision = OPT_U32((uint32_t)val); + out->precision = OPT_I32(val); + } + END_IF_FIELD + + IF_FIELD(trimFactor) { + if (!BSON_ITER_HOLDS_INT32(&iter)) { + CLIENT_ERR_PREFIXED("'trimFactor' must be an int32"); + goto fail; + } + int32_t val = bson_iter_int32(&iter); + if (val < 0) { + CLIENT_ERR_PREFIXED("'trimFactor' must be non-negative"); + goto fail; + } + out->trimFactor = OPT_I32(val); } END_IF_FIELD } @@ -425,8 +478,16 @@ bool mc_FLE2RangeInsertSpec_parse(mc_FLE2RangeInsertSpec_t *out, const bson_iter CHECK_HAS(max) // Do not error if precision is not present. Precision optional and only // applies to double/decimal128. + + if (!use_range_v2 && out->trimFactor.set) { + CLIENT_ERR_PREFIXED("'trimFactor' is not supported for QE range v1"); + return false; + } + return true; fail: return false; } + +#undef ERROR_PREFIX diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-private-v2.h b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-private-v2.h index 452f2a9a7ca..02fa0e5bc79 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-private-v2.h +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-private-v2.h @@ -25,7 +25,7 @@ typedef struct { _mongocrypt_buffer_t edcDerivedToken; // d _mongocrypt_buffer_t escDerivedToken; // s _mongocrypt_buffer_t serverDerivedFromDataToken; // l - int64_t maxContentionCounter; // cm + int64_t maxContentionFactor; // cm } mc_FLE2FindEqualityPayloadV2_t; void mc_FLE2FindEqualityPayloadV2_init(mc_FLE2FindEqualityPayloadV2_t *payload); diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-private.h b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-private.h index 8eb399eabf3..6dcc0e38fe4 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-private.h @@ -26,7 +26,7 @@ typedef struct { _mongocrypt_buffer_t escDerivedToken; // s _mongocrypt_buffer_t eccDerivedToken; // c _mongocrypt_buffer_t serverEncryptionToken; // e - int64_t maxContentionCounter; // cm + int64_t maxContentionFactor; // cm } mc_FLE2FindEqualityPayload_t; void mc_FLE2FindEqualityPayload_init(mc_FLE2FindEqualityPayload_t *payload); diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-v2.c b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-v2.c index 02d2dbfde65..afd1654fb81 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-v2.c +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload-v2.c @@ -99,7 +99,7 @@ bool mc_FLE2FindEqualityPayloadV2_parse(mc_FLE2FindEqualityPayloadV2_t *out, CLIENT_ERR("Field 'cm' expected to hold an int64"); goto fail; } - out->maxContentionCounter = bson_iter_int64(&iter); + out->maxContentionFactor = bson_iter_int64(&iter); } END_IF_FIELD } @@ -126,7 +126,7 @@ bool mc_FLE2FindEqualityPayloadV2_serialize(const mc_FLE2FindEqualityPayloadV2_t APPEND_BINDATA("d", payload->edcDerivedToken); APPEND_BINDATA("s", payload->escDerivedToken); APPEND_BINDATA("l", payload->serverDerivedFromDataToken); - if (!BSON_APPEND_INT64(out, "cm", payload->maxContentionCounter)) { + if (!BSON_APPEND_INT64(out, "cm", payload->maxContentionFactor)) { return false; } return true; diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload.c b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload.c index 24d551a03de..e60dfa90e88 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload.c +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-equality-payload.c @@ -101,7 +101,7 @@ bool mc_FLE2FindEqualityPayload_parse(mc_FLE2FindEqualityPayload_t *out, CLIENT_ERR("Field 'cm' expected to hold an int64"); goto fail; } - out->maxContentionCounter = bson_iter_int64(&iter); + out->maxContentionFactor = bson_iter_int64(&iter); } END_IF_FIELD } @@ -130,7 +130,7 @@ bool mc_FLE2FindEqualityPayload_serialize(const mc_FLE2FindEqualityPayload_t *pa PAYLOAD_APPEND_BINDATA("s", payload->escDerivedToken); PAYLOAD_APPEND_BINDATA("c", payload->eccDerivedToken); PAYLOAD_APPEND_BINDATA("e", payload->serverEncryptionToken); - if (!BSON_APPEND_INT64(out, "cm", payload->maxContentionCounter)) { + if (!BSON_APPEND_INT64(out, "cm", payload->maxContentionFactor)) { return false; } return true; diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-private-v2.h b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-private-v2.h index f35331aa200..79bbd8ca991 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-private-v2.h +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-private-v2.h @@ -23,13 +23,14 @@ #include "mc-array-private.h" #include "mc-fle2-range-operator-private.h" +#include "mc-optional-private.h" /** FLE2FindRangePayloadEdgesInfoV2 represents the token information for a range * find query. It is encoded inside an FLE2FindRangePayloadV2. */ typedef struct { mc_array_t edgeFindTokenSetArray; // g - int64_t maxContentionCounter; // cm + int64_t maxContentionFactor; // cm } mc_FLE2FindRangePayloadEdgesInfoV2_t; /** @@ -44,8 +45,17 @@ typedef struct { * } FLE2FindRangePayloadV2; * * bson is a BSON document of this form: - * g: array // Array of Edges - * cm: // Queryable Encryption max counter + * payload: + * g: array // Array of Edges + * cm: // Queryable Encryption max counter + * payloadId: // Payload ID. + * firstOperator: + * secondOperator: + * sp: optional // Sparsity. + * pn: optional // Precision. + * tf: optional // Trim Factor. + * mn: optional // Index Min. + * mx: optional // Index Max. */ typedef struct { struct { @@ -61,12 +71,22 @@ typedef struct { // secondOperator represents the second query operator for which this payload // was generated. Only populated for two-sided ranges. It is 0 if unset. mc_FLE2RangeOperator_t secondOperator; + mc_optional_int64_t sparsity; // sp + mc_optional_int32_t precision; // pn + mc_optional_int32_t trimFactor; // tf + bson_value_t indexMin; // mn + bson_value_t indexMax; // mx } mc_FLE2FindRangePayloadV2_t; +// `mc_FLE2FindRangePayloadV2_t` inherits extended alignment from libbson. To dynamically allocate, use aligned +// allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof_mc_FLE2FindRangePayloadV2_t, + BSON_ALIGNOF(mc_FLE2FindRangePayloadV2_t) >= BSON_ALIGNOF(bson_value_t)); + /** * EdgeFindTokenSetV2 is the following BSON document: - * d: // EDCDerivedFromDataTokenAndCounter - * s: // ESCDerivedFromDataTokenAndCounter + * d: // EDCDerivedFromDataTokenAndContentionFactor + * s: // ESCDerivedFromDataTokenAndContentionFactor * l: // ServerDerivedFromDataToken * * Instances of mc_EdgeFindTokenSetV2_t are expected to be owned by @@ -81,7 +101,7 @@ typedef struct { void mc_FLE2FindRangePayloadV2_init(mc_FLE2FindRangePayloadV2_t *payload); -bool mc_FLE2FindRangePayloadV2_serialize(const mc_FLE2FindRangePayloadV2_t *payload, bson_t *out); +bool mc_FLE2FindRangePayloadV2_serialize(const mc_FLE2FindRangePayloadV2_t *payload, bson_t *out, bool use_range_v2); void mc_FLE2FindRangePayloadV2_cleanup(mc_FLE2FindRangePayloadV2_t *payload); diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-private.h b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-private.h index 0796c30e319..d2e4ee4457e 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-private.h @@ -32,7 +32,7 @@ typedef struct { mc_array_t edgeFindTokenSetArray; // g _mongocrypt_buffer_t serverEncryptionToken; // e - int64_t maxContentionCounter; // cm + int64_t maxContentionFactor; // cm } mc_FLE2FindRangePayloadEdgesInfo_t; /** @@ -49,7 +49,7 @@ typedef struct { * bson is a BSON document of this form: * g: array // Array of Edges * e: // ServerDataEncryptionLevel1Token - * cm: // Queryable Encryption max counter + * cm: // Queryable Encryption max contentionFactor */ typedef struct { struct { @@ -69,9 +69,9 @@ typedef struct { /** * EdgeFindTokenSet is the following BSON document: - * d: // EDCDerivedFromDataTokenAndCounter - * s: // ESCDerivedFromDataTokenAndCounter - * c: // ECCDerivedFromDataTokenAndCounter + * d: // EDCDerivedFromDataTokenAndContentionFactor + * s: // ESCDerivedFromDataTokenAndContentionFactor + * c: // ECCDerivedFromDataTokenAndContentionFactor * * Instances of mc_EdgeFindTokenSet_t are expected to be owned by * mc_FLE2FindRangePayload_t and are freed in diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-v2.c b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-v2.c index dfb5421a663..e07cd76781c 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-v2.c +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload-v2.c @@ -53,7 +53,7 @@ void mc_FLE2FindRangePayloadV2_cleanup(mc_FLE2FindRangePayloadV2_t *payload) { return false; \ } -bool mc_FLE2FindRangePayloadV2_serialize(const mc_FLE2FindRangePayloadV2_t *payload, bson_t *out) { +bool mc_FLE2FindRangePayloadV2_serialize(const mc_FLE2FindRangePayloadV2_t *payload, bson_t *out, bool use_range_v2) { BSON_ASSERT_PARAM(out); BSON_ASSERT_PARAM(payload); @@ -106,7 +106,7 @@ bool mc_FLE2FindRangePayloadV2_serialize(const mc_FLE2FindRangePayloadV2_t *payl } // Append "payload.cm". - if (!BSON_APPEND_INT64(&payload_bson, "cm", payload->payload.value.maxContentionCounter)) { + if (!BSON_APPEND_INT64(&payload_bson, "cm", payload->payload.value.maxContentionFactor)) { return false; } @@ -131,6 +131,42 @@ bool mc_FLE2FindRangePayloadV2_serialize(const mc_FLE2FindRangePayloadV2_t *payl return false; } + if (use_range_v2) { + // Encode parameters that were used to generate the mincover. + // The crypto parameters are all optionally set. Find payloads may come in pairs (a lower and upper bound). + // One of the pair includes the mincover. The other payload was not generated with crypto parameters. + + if (payload->sparsity.set) { + if (!BSON_APPEND_INT64(out, "sp", payload->sparsity.value)) { + return false; + } + } + + if (payload->precision.set) { + if (!BSON_APPEND_INT32(out, "pn", payload->precision.value)) { + return false; + } + } + + if (payload->trimFactor.set) { + if (!BSON_APPEND_INT32(out, "tf", payload->trimFactor.value)) { + return false; + } + } + + if (payload->indexMin.value_type != BSON_TYPE_EOD) { + if (!BSON_APPEND_VALUE(out, "mn", &payload->indexMin)) { + return false; + } + } + + if (payload->indexMax.value_type != BSON_TYPE_EOD) { + if (!BSON_APPEND_VALUE(out, "mx", &payload->indexMax)) { + return false; + } + } + } + return true; } diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload.c b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload.c index 4eb1de4b9f1..1a3610e0d71 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload.c +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-find-range-payload.c @@ -108,7 +108,7 @@ bool mc_FLE2FindRangePayload_serialize(const mc_FLE2FindRangePayload_t *payload, // Append "payload.e" and "payload.cm". APPEND_BINDATA(&payload_bson, "e", payload->payload.value.serverEncryptionToken); - if (!BSON_APPEND_INT64(&payload_bson, "cm", payload->payload.value.maxContentionCounter)) { + if (!BSON_APPEND_INT64(&payload_bson, "cm", payload->payload.value.maxContentionFactor)) { return false; } diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-private-v2.h b/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-private-v2.h index e3556263f46..911ca746bd2 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-private-v2.h +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-private-v2.h @@ -20,6 +20,7 @@ #include #include "mc-array-private.h" +#include "mc-optional-private.h" #include "mongocrypt-buffer-private.h" #include "mongocrypt-private.h" #include "mongocrypt.h" @@ -36,8 +37,8 @@ * } FLE2InsertUpdatePayloadV2; * * bson is a BSON document of this form: - * d: // EDCDerivedFromDataTokenAndCounter - * s: // ESCDerivedFromDataTokenAndCounter + * d: // EDCDerivedFromDataTokenAndContentionFactor + * s: // ESCDerivedFromDataTokenAndContentionFactor * p: // Encrypted Tokens * u: // Index KeyId * t: // Encrypted type @@ -45,13 +46,18 @@ * e: // ServerDataEncryptionLevel1Token * l: // ServerDerivedFromDataToken * k: // Randomly sampled contention factor value - * g: array // Array of Edges + * g: array // Array of Edges. Only included for range payloads. + * sp: optional // Sparsity. Only included for range payloads. + * pn: optional // Precision. Only included for range payloads. + * tf: optional // Trim Factor. Only included for range payloads. + * mn: optional // Index Min. Only included for range payloads. + * mx: optional // Index Max. Only included for range payloads. * * p is the result of: * Encrypt( * key=ECOCToken, * plaintext=( - * ESCDerivedFromDataTokenAndCounter) + * ESCDerivedFromDataTokenAndContentionFactor) * ) * * v is the result of: @@ -72,14 +78,24 @@ typedef struct { _mongocrypt_buffer_t serverDerivedFromDataToken; // l int64_t contentionFactor; // k mc_array_t edgeTokenSetArray; // g + mc_optional_int64_t sparsity; // sp + mc_optional_int32_t precision; // pn + mc_optional_int32_t trimFactor; // tf + bson_value_t indexMin; // mn + bson_value_t indexMax; // mx _mongocrypt_buffer_t plaintext; _mongocrypt_buffer_t userKeyId; } mc_FLE2InsertUpdatePayloadV2_t; +// `mc_FLE2InsertUpdatePayloadV2_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof_mc_FLE2InsertUpdatePayloadV2_t, + BSON_ALIGNOF(mc_FLE2InsertUpdatePayloadV2_t) >= BSON_ALIGNOF(bson_value_t)); + /** * EdgeTokenSetV2 is the following BSON document: - * d: // EDCDerivedFromDataTokenAndCounter - * s: // ESCDerivedFromDataTokenAndCounter + * d: // EDCDerivedFromDataTokenAndContentionFactor + * s: // ESCDerivedFromDataTokenAndContentionFactor * l: // ServerDerivedFromDataToken * p: // Encrypted Tokens * @@ -110,7 +126,9 @@ const _mongocrypt_buffer_t *mc_FLE2InsertUpdatePayloadV2_decrypt(_mongocrypt_cry bool mc_FLE2InsertUpdatePayloadV2_serialize(const mc_FLE2InsertUpdatePayloadV2_t *payload, bson_t *out); -bool mc_FLE2InsertUpdatePayloadV2_serializeForRange(const mc_FLE2InsertUpdatePayloadV2_t *payload, bson_t *out); +bool mc_FLE2InsertUpdatePayloadV2_serializeForRange(const mc_FLE2InsertUpdatePayloadV2_t *payload, + bson_t *out, + bool use_range_v2); void mc_FLE2InsertUpdatePayloadV2_cleanup(mc_FLE2InsertUpdatePayloadV2_t *payload); diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-private.h b/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-private.h index 76bdf4af1c4..a7d44958a93 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-private.h @@ -36,9 +36,9 @@ * } FLE2InsertUpdatePayload; * * bson is a BSON document of this form: - * d: // EDCDerivedFromDataTokenAndCounter - * s: // ESCDerivedFromDataTokenAndCounter - * c: // ECCDerivedFromDataTokenAndCounter + * d: // EDCDerivedFromDataTokenAndContentionFactor + * s: // ESCDerivedFromDataTokenAndContentionFactor + * c: // ECCDerivedFromDataTokenAndContentionFactor * p: // Encrypted Tokens * u: // Index KeyId * t: // Encrypted type @@ -50,8 +50,8 @@ * Encrypt( * key=ECOCToken, * plaintext=( - * ESCDerivedFromDataTokenAndCounter || - * ECCDerivedFromDataTokenAndCounter) + * ESCDerivedFromDataTokenAndContentionFactor || + * ECCDerivedFromDataTokenAndContentionFactor) * ) * * v is the result of: @@ -77,9 +77,9 @@ typedef struct { /** * EdgeTokenSet is the following BSON document: - * d: // EDCDerivedFromDataTokenAndCounter - * s: // ESCDerivedFromDataTokenAndCounter - * c: // ECCDerivedFromDataTokenAndCounter + * d: // EDCDerivedFromDataTokenAndContentionFactor + * s: // ESCDerivedFromDataTokenAndContentionFactor + * c: // ECCDerivedFromDataTokenAndContentionFactor * p: // Encrypted Tokens * * Instances of mc_EdgeTokenSet_t are expected to be owned by diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-v2.c b/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-v2.c index f4e9bf309e0..b86e41239a0 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-v2.c +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-insert-update-payload-v2.c @@ -18,6 +18,7 @@ #include "mc-fle2-insert-update-payload-private-v2.h" #include "mongocrypt-buffer-private.h" +#include "mongocrypt-util-private.h" // mc_bson_type_to_string #include "mongocrypt.h" void mc_FLE2InsertUpdatePayloadV2_init(mc_FLE2InsertUpdatePayloadV2_t *payload) { @@ -53,6 +54,8 @@ void mc_FLE2InsertUpdatePayloadV2_cleanup(mc_FLE2InsertUpdatePayloadV2_t *payloa mc_EdgeTokenSetV2_cleanup(&entry); } _mc_array_destroy(&payload->edgeTokenSetArray); + bson_value_destroy(&payload->indexMin); + bson_value_destroy(&payload->indexMax); } #define IF_FIELD(Name) \ @@ -104,6 +107,7 @@ bool mc_FLE2InsertUpdatePayloadV2_parse(mc_FLE2InsertUpdatePayloadV2_t *out, bool has_d = false, has_s = false, has_p = false; bool has_u = false, has_t = false, has_v = false; bool has_e = false, has_l = false, has_k = false; + bool has_sp = false, has_pn = false, has_tf = false, has_mn = false, has_mx = false; bson_t in_bson; BSON_ASSERT_PARAM(out); @@ -163,6 +167,57 @@ bool mc_FLE2InsertUpdatePayloadV2_parse(mc_FLE2InsertUpdatePayloadV2_t *out, PARSE_BINARY(v, value) PARSE_BINARY(e, serverEncryptionToken) PARSE_BINARY(l, serverDerivedFromDataToken) + + IF_FIELD(sp) { + if (!BSON_ITER_HOLDS_INT64(&iter)) { + CLIENT_ERR("Field 'sp' expected to hold an int64, got: %s", + mc_bson_type_to_string(bson_iter_type(&iter))); + goto fail; + } + int64_t sparsity = bson_iter_int64(&iter); + out->sparsity = OPT_I64(sparsity); + } + END_IF_FIELD + + IF_FIELD(pn) { + if (!BSON_ITER_HOLDS_INT32(&iter)) { + CLIENT_ERR("Field 'pn' expected to hold an int32, got: %s", + mc_bson_type_to_string(bson_iter_type(&iter))); + goto fail; + } + int32_t precision = bson_iter_int32(&iter); + if (precision < 0) { + CLIENT_ERR("Field 'pn' must be non-negative, got: %" PRId32, precision); + goto fail; + } + out->precision = OPT_I32(precision); + } + END_IF_FIELD + + IF_FIELD(tf) { + if (!BSON_ITER_HOLDS_INT32(&iter)) { + CLIENT_ERR("Field 'tf' expected to hold an int32, got: %s", + mc_bson_type_to_string(bson_iter_type(&iter))); + goto fail; + } + int32_t trimFactor = bson_iter_int32(&iter); + if (trimFactor < 0) { + CLIENT_ERR("Field 'tf' must be non-negative, got: %" PRId32, trimFactor); + goto fail; + } + out->trimFactor = OPT_I32(trimFactor); + } + END_IF_FIELD + + IF_FIELD(mn) { + bson_value_copy(bson_iter_value(&iter), &out->indexMin); + } + END_IF_FIELD + + IF_FIELD(mx) { + bson_value_copy(bson_iter_value(&iter), &out->indexMax); + } + END_IF_FIELD } CHECK_HAS(d); @@ -174,6 +229,7 @@ bool mc_FLE2InsertUpdatePayloadV2_parse(mc_FLE2InsertUpdatePayloadV2_t *out, CHECK_HAS(e); CHECK_HAS(l); CHECK_HAS(k); + // The fields `sp`, `pn`, `tf`, `mn`, and `mx` are only set for "range" payloads. if (!_mongocrypt_buffer_from_subrange(&out->userKeyId, &out->value, 0, UUID_LEN)) { CLIENT_ERR("failed to create userKeyId buffer"); @@ -213,7 +269,9 @@ bool mc_FLE2InsertUpdatePayloadV2_serialize(const mc_FLE2InsertUpdatePayloadV2_t return true; } -bool mc_FLE2InsertUpdatePayloadV2_serializeForRange(const mc_FLE2InsertUpdatePayloadV2_t *payload, bson_t *out) { +bool mc_FLE2InsertUpdatePayloadV2_serializeForRange(const mc_FLE2InsertUpdatePayloadV2_t *payload, + bson_t *out, + bool use_range_v2) { BSON_ASSERT_PARAM(out); BSON_ASSERT_PARAM(payload); @@ -257,6 +315,36 @@ bool mc_FLE2InsertUpdatePayloadV2_serializeForRange(const mc_FLE2InsertUpdatePay return false; } + if (use_range_v2) { + // Encode parameters that were used to generate the payload. + BSON_ASSERT(payload->sparsity.set); + if (!BSON_APPEND_INT64(out, "sp", payload->sparsity.value)) { + return false; + } + + // Precision may be unset. + if (payload->precision.set) { + if (!BSON_APPEND_INT32(out, "pn", payload->precision.value)) { + return false; + } + } + + BSON_ASSERT(payload->trimFactor.set); + if (!BSON_APPEND_INT32(out, "tf", payload->trimFactor.value)) { + return false; + } + + BSON_ASSERT(payload->indexMin.value_type != BSON_TYPE_EOD); + if (!BSON_APPEND_VALUE(out, "mn", &payload->indexMin)) { + return false; + } + + BSON_ASSERT(payload->indexMax.value_type != BSON_TYPE_EOD); + if (!BSON_APPEND_VALUE(out, "mx", &payload->indexMax)) { + return false; + } + } + return true; } diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev-private-v2.h b/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev-private-v2.h index 5a585cd4ce0..4ead1756e93 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev-private-v2.h +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev-private-v2.h @@ -17,6 +17,7 @@ #ifndef MONGOCRYPT_INDEXED_ENCRYPTED_VALUE_PRIVATE_V2_H #define MONGOCRYPT_INDEXED_ENCRYPTED_VALUE_PRIVATE_V2_H +#include "mc-fle2-tag-and-encrypted-metadata-block-private.h" #include "mc-tokens-private.h" #include "mongocrypt-buffer-private.h" #include "mongocrypt-crypto-private.h" @@ -24,31 +25,98 @@ /* * FLE2IndexedEqualityEncryptedValueV2 and FLE2IndexedRangeEncryptedValueV2 - * share a common internal implementation. Accessors such as add/get_[SK]_Key - * may be called for either type and produce appropriate results, - * however the _parse() method is unique per type. + * share a common internal implementation. * * Lifecycle: * 1. mc_FLE2IndexedEncryptedValueV2_init - * 2. mc_FLE2Indexed(Equality|Range)EncryptedValueV2_parse + * 2. mc_FLE2IndexedEncryptedValueV2_parse * 3. mc_FLE2IndexedEncryptedValueV2_get_S_KeyId * 4. mc_FLE2IndexedEncryptedValueV2_add_S_Key * 5. mc_FLE2IndexedEncryptedValueV2_get_K_KeyId * 6. mc_FLE2IndexedEncryptedValueV2_add_K_Key * 7. mc_FLE2IndexedEncryptedValueV2_get_ClientValue - * 8. mc_FLE2IndexedEncryptedValueV2_destroy + * 8. mc_FLE2IndexedEncryptedValueV2_serialize + * 9. mc_FLE2IndexedEncryptedValueV2_destroy + * + * + * FLE2IndexedEqualityEncryptedValueV2 has the following data layout: + * + * struct FLE2IndexedEqualityEncryptedValueV2 { + * uint8_t fle_blob_subtype = 14; + * uint8_t S_KeyId[16]; + * uint8_t original_bson_type; + * uint8_t ServerEncryptedValue[ServerEncryptedValue.length]; + * FLE2TagAndEncryptedMetadataBlock metadata; + * } + * + * ServerEncryptedValue := + * EncryptCTR(ServerEncryptionToken, K_KeyId || ClientEncryptedValue) + * ClientEncryptedValue := EncryptCBCAEAD(K_Key, clientValue, AD=K_KeyId) + * + * + * struct FLE2TagAndEncryptedMetadataBlock { + * uint8_t encryptedCount[32]; // EncryptCTR(countEncryptionToken, + * // count || contentionFactor) + * uint8_t tag[32]; // HMAC-SHA256(count, edcTwiceDerived) + * uint8_t encryptedZeros[32]; // EncryptCTR(zerosEncryptionToken, 0*) + * } + * + * + * FLE2IndexedRangeEncryptedValueV2 has the following data layout: + * + * struct FLE2IndexedRangeEncryptedValueV2 { + * uint8_t fle_blob_subtype = 15; + * uint8_t S_KeyId[16]; + * uint8_t original_bson_type; + * uint8_t edge_count; + * uint8_t ServerEncryptedValue[ServerEncryptedValue.length]; + * FLE2TagAndEncryptedMetadataBlock metadata[edge_count]; + * } + * + * Note that this format differs from FLE2IndexedEqualityEncryptedValueV2 + * in only two ways: + * 1/ `edge_count` is introduced as an octet following `original_bson_type`. + * 2/ Rather than a single metadata block, we have {edge_count} blocks. + * */ typedef struct _mc_FLE2IndexedEncryptedValueV2_t mc_FLE2IndexedEncryptedValueV2_t; mc_FLE2IndexedEncryptedValueV2_t *mc_FLE2IndexedEncryptedValueV2_new(void); +bson_type_t mc_FLE2IndexedEncryptedValueV2_get_bson_value_type(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mongocrypt_status_t *status); +/* + * Populates an mc_FLE2IndexedEncryptedValueV2_t from a buffer. + * + * Input buffer must take the form of: + * fle_blob_subtype (8u) + * S_KeyId (8u * 16u) + * original_bson_type (8u) + * if (range) + * edge_count(8u) + * ServerEncryptedValue (8u * SEV_len) + * metadata (96u * {range ? edge_count : 1u}) + * + * Returns an error if the input buffer is not valid. + */ bool mc_FLE2IndexedEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t *iev, const _mongocrypt_buffer_t *buf, mongocrypt_status_t *status); -bson_type_t mc_FLE2IndexedEncryptedValueV2_get_bson_value_type(const mc_FLE2IndexedEncryptedValueV2_t *iev, - mongocrypt_status_t *status); +/* + * Serializes an mc_FLE2IndexedEncryptedValueV2_t into a buffer. + * + * The serialized output follows the same layout as the input `buf` to + * mc_FLE2IndexedEncryptedValueV2_parse, allowing for round-trip + * conversions between the serialized and parsed forms. + * + * Returns an error if the input structure is not valid, or if the buffer + * provided is insufficient to hold the serialized data. + */ +bool mc_FLE2IndexedEncryptedValueV2_serialize(const mc_FLE2IndexedEncryptedValueV2_t *iev, + _mongocrypt_buffer_t *buf, + mongocrypt_status_t *status); const _mongocrypt_buffer_t *mc_FLE2IndexedEncryptedValueV2_get_S_KeyId(const mc_FLE2IndexedEncryptedValueV2_t *iev, mongocrypt_status_t *status); @@ -73,61 +141,18 @@ bool mc_FLE2IndexedEncryptedValueV2_add_K_Key(_mongocrypt_crypto_t *crypto, const _mongocrypt_buffer_t *mc_FLE2IndexedEncryptedValueV2_get_ClientValue(const mc_FLE2IndexedEncryptedValueV2_t *iev, mongocrypt_status_t *status); +uint8_t mc_FLE2IndexedEncryptedValueV2_get_edge_count(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mongocrypt_status_t *status); + +bool mc_FLE2IndexedEncryptedValueV2_get_edge(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + const uint8_t edge_index, + mongocrypt_status_t *status); + +bool mc_FLE2IndexedEncryptedValueV2_get_metadata(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + mongocrypt_status_t *status); + void mc_FLE2IndexedEncryptedValueV2_destroy(mc_FLE2IndexedEncryptedValueV2_t *iev); -/* - * FLE2IndexedEqualityEncryptedValueV2 has the following data layout: - * - * struct FLE2IndexedEqualityEncryptedValueV2 { - * uint8_t fle_blob_subtype = 14; - * uint8_t S_KeyId[16]; - * uint8_t original_bson_type; - * uint8_t ServerEncryptedValue[ServerEncryptedValue.length]; - * FLE2TagAndEncryptedMetadataBlock metadata; - * } - * - * ServerEncryptedValue := - * EncryptCTR(ServerEncryptionToken, K_KeyId || ClientEncryptedValue) - * ClientEncryptedValue := EncryptCBCAEAD(K_Key, clientValue, AD=K_KeyId) - * - * The MetadataBlock is ignored by libmongocrypt, - * but has the following structure and a fixed size of 96 octets: - * - * struct FLE2TagAndEncryptedMetadataBlock { - * uint8_t encryptedCount[32]; // EncryptCTR(countEncryptionToken, - * // count || contentionFactor) - * uint8_t tag[32]; // HMAC-SHA256(count, edcTwiceDerived) - * uint8_t encryptedZeros[32]; // EncryptCTR(zerosEncryptionToken, 0*) - * } - */ - -bool mc_FLE2IndexedEqualityEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t *iev, - const _mongocrypt_buffer_t *buf, - mongocrypt_status_t *status); - -/* - * FLE2IndexedRangeEncryptedValueV2 has the following data layout: - * - * struct FLE2IndexedRangeEncryptedValueV2 { - * uint8_t fle_blob_subtype = 15; - * uint8_t S_KeyId[16]; - * uint8_t original_bson_type; - * uint8_t edge_count; - * uint8_t ServerEncryptedValue[ServerEncryptedValue.length]; - * FLE2TagAndEncryptedMetadataBlock metadata[edge_count]; - * } - * - * Note that this format differs from FLE2IndexedEqualityEncryptedValueV2 - * in only two ways: - * 1/ `edge_count` is introduced as an octet following `original_bson_type`. - * 2/ Rather than a single metadata block, we have {edge_count} blocks. - * - * Since libmongocrypt ignores metadata blocks, we can ignore most all - * differences between Equality and Range types for IndexedEncrypted data. - */ - -bool mc_FLE2IndexedRangeEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t *iev, - const _mongocrypt_buffer_t *buf, - mongocrypt_status_t *status); - #endif /* MONGOCRYPT_INDEXED_ENCRYPTED_VALUE_PRIVATE_V2_H */ diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev-v2.c b/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev-v2.c index de11a223247..211d335781e 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev-v2.c +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev-v2.c @@ -19,6 +19,7 @@ #include "mc-fle-blob-subtype-private.h" #include "mc-fle2-payload-iev-private-v2.h" +#include "mc-fle2-tag-and-encrypted-metadata-block-private.h" #include "mc-reader-private.h" #include "mc-tokens-private.h" #include "mc-writer-private.h" @@ -54,6 +55,8 @@ struct _mc_FLE2IndexedEncryptedValueV2_t { // Populated during _add_K_Key // ClientValue := DecryptCBCAEAD(K_Key, ClientEncryptedValue, AD=K_KeyId) _mongocrypt_buffer_t ClientValue; + + mc_FLE2TagAndEncryptedMetadataBlock_t *metadata; }; #define kMetadataLen 96U // encCount(32) + tag(32) + encZeros(32) @@ -69,31 +72,6 @@ mc_FLE2IndexedEncryptedValueV2_t *mc_FLE2IndexedEncryptedValueV2_new(void) { return bson_malloc0(sizeof(mc_FLE2IndexedEncryptedValueV2_t)); } -bool mc_FLE2IndexedEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t *iev, - const _mongocrypt_buffer_t *buf, - mongocrypt_status_t *status) { - BSON_ASSERT_PARAM(iev); - BSON_ASSERT_PARAM(buf); - - if ((buf->data == NULL) || (buf->len == 0)) { - CLIENT_ERR("Empty buffer passed to mc_FLE2IndexedEncryptedValueV2_parse"); - return false; - } - - if (buf->data[0] == MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2) { - return mc_FLE2IndexedEqualityEncryptedValueV2_parse(iev, buf, status); - } else if (buf->data[0] == MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2) { - return mc_FLE2IndexedRangeEncryptedValueV2_parse(iev, buf, status); - } else { - CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_parse expected " - "fle_blob_subtype %d or %d got: %" PRIu8, - MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2, - MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2, - iev->fle_blob_subtype); - return false; - } -} - bson_type_t mc_FLE2IndexedEncryptedValueV2_get_bson_value_type(const mc_FLE2IndexedEncryptedValueV2_t *iev, mongocrypt_status_t *status) { BSON_ASSERT_PARAM(iev); @@ -196,7 +174,7 @@ bool mc_FLE2IndexedEncryptedValueV2_add_S_Key(_mongocrypt_crypto_t *crypto, BSON_ASSERT(bytes_written == DecryptedServerEncryptedValueLen); if (!_mongocrypt_buffer_from_subrange(&iev->K_KeyId, &iev->DecryptedServerEncryptedValue, 0, UUID_LEN)) { CLIENT_ERR("Error creating K_KeyId subrange from DecryptedServerEncryptedValue"); - return false; + goto fail; } iev->K_KeyId.subtype = BSON_SUBTYPE_UUID; @@ -207,7 +185,7 @@ bool mc_FLE2IndexedEncryptedValueV2_add_S_Key(_mongocrypt_crypto_t *crypto, iev->DecryptedServerEncryptedValue.len - UUID_LEN)) { CLIENT_ERR("Error creating ClientEncryptedValue subrange from " "DecryptedServerEncryptedValue"); - return false; + goto fail; } iev->ClientEncryptedValueDecoded = true; @@ -322,70 +300,99 @@ void mc_FLE2IndexedEncryptedValueV2_destroy(mc_FLE2IndexedEncryptedValueV2_t *ie _mongocrypt_buffer_cleanup(&iev->DecryptedServerEncryptedValue); _mongocrypt_buffer_cleanup(&iev->ServerEncryptedValue); _mongocrypt_buffer_cleanup(&iev->S_KeyId); + + for (int i = 0; i < iev->edge_count; i++) { + mc_FLE2TagAndEncryptedMetadataBlock_cleanup(&iev->metadata[i]); + } + + // Metadata array is dynamically allocated + bson_free(iev->metadata); + bson_free(iev); } -// ----------------------------------------------------------------------- -// Equality - -bool mc_FLE2IndexedEqualityEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t *iev, - const _mongocrypt_buffer_t *buf, - mongocrypt_status_t *status) { +uint8_t mc_FLE2IndexedEncryptedValueV2_get_edge_count(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mongocrypt_status_t *status) { BSON_ASSERT_PARAM(iev); - BSON_ASSERT_PARAM(buf); - if (iev->type != kTypeInit) { - CLIENT_ERR("mc_FLE2IndexedEqualityEncryptedValueV2_parse must not be " - "called twice"); + if (iev->type == kTypeInit) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_edge_count " + "must be called after " + "mc_FLE2IndexedEncryptedValueV2_parse"); + return 0; + } + + if (iev->type != kTypeRange) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_edge_count must be called with type range"); + return 0; + } + + return iev->edge_count; +} + +bool mc_FLE2IndexedEncryptedValueV2_get_edge(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + const uint8_t edge_index, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iev); + BSON_ASSERT_PARAM(out); + + if (iev->type == kTypeInit) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_edge " + "must be called after " + "mc_FLE2IndexedEncryptedValueV2_parse"); return false; } - mc_reader_t reader; - mc_reader_init_from_buffer(&reader, buf, __FUNCTION__); - - CHECK_AND_RETURN(mc_reader_read_u8(&reader, &iev->fle_blob_subtype, status)); - - if (iev->fle_blob_subtype != MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2) { - CLIENT_ERR("mc_FLE2IndexedEqualityEncryptedValueV2_parse expected " - "fle_blob_subtype %d got: %" PRIu8, - MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2, - iev->fle_blob_subtype); + if (iev->type != kTypeRange) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_edge must be called with type range"); return false; } - /* Read S_KeyId. */ - CHECK_AND_RETURN(mc_reader_read_uuid_buffer(&reader, &iev->S_KeyId, status)); - - /* Read original_bson_type. */ - CHECK_AND_RETURN(mc_reader_read_u8(&reader, &iev->bson_value_type, status)); - - /* Read ServerEncryptedValue. */ - const uint64_t SEV_and_metadata_len = mc_reader_get_remaining_length(&reader); - if (SEV_and_metadata_len < kMinSEVAndMetadataLen) { - CLIENT_ERR("Invalid payload size %" PRIu64 ", smaller than minimum length %d", - SEV_and_metadata_len, - kMinSEVAndMetadataLen); + if (edge_index >= iev->edge_count) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_edge must be called with index edge_index less than edge count"); return false; } - const uint64_t SEV_len = SEV_and_metadata_len - kMetadataLen; - CHECK_AND_RETURN(mc_reader_read_buffer(&reader, &iev->ServerEncryptedValue, SEV_len, status)); - // Ignore Metadata block. - BSON_ASSERT(mc_reader_get_remaining_length(&reader) == kMetadataLen); - - iev->type = kTypeEquality; + // Write edge into out struct + *out = iev->metadata[edge_index]; return true; } -// ----------------------------------------------------------------------- -// Range +bool mc_FLE2IndexedEncryptedValueV2_get_metadata(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iev); + BSON_ASSERT_PARAM(out); -bool mc_FLE2IndexedRangeEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t *iev, - const _mongocrypt_buffer_t *buf, - mongocrypt_status_t *status) { + if (iev->type == kTypeInit) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_metadata " + "must be called after " + "mc_FLE2IndexedEncryptedValueV2_parse"); + return false; + } + + if (iev->type != kTypeEquality) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_metadata must be called with type equality"); + return false; + } + + // Write edge into out struct + *out = *iev->metadata; + return true; +} + +bool mc_FLE2IndexedEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t *iev, + const _mongocrypt_buffer_t *buf, + mongocrypt_status_t *status) { BSON_ASSERT_PARAM(iev); BSON_ASSERT_PARAM(buf); + if ((buf->data == NULL) || (buf->len == 0)) { + CLIENT_ERR("Empty buffer passed to mc_FLE2IndexedEncryptedValueV2_parse"); + return false; + } + if (iev->type != kTypeInit) { CLIENT_ERR("mc_FLE2IndexedRangeEncryptedValueV2_parse must not be " "called twice"); @@ -397,10 +404,13 @@ bool mc_FLE2IndexedRangeEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t CHECK_AND_RETURN(mc_reader_read_u8(&reader, &iev->fle_blob_subtype, status)); - if (iev->fle_blob_subtype != MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2) { - CLIENT_ERR("mc_FLE2IndexedRangeEncryptedValueV2_parse expected " - "fle_blob_subtype %d got: %" PRIu8, - MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2, + if (iev->fle_blob_subtype == MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2) { + iev->type = kTypeEquality; + } else if (iev->fle_blob_subtype == MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2) { + iev->type = kTypeRange; + } else { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_parse expected " + "fle_blob_subtype MC_SUBTYPE_FLE2Indexed(Equality|Range)EncryptedValueV2 got: %" PRIu8, iev->fle_blob_subtype); return false; } @@ -412,26 +422,88 @@ bool mc_FLE2IndexedRangeEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t CHECK_AND_RETURN(mc_reader_read_u8(&reader, &iev->bson_value_type, status)); /* Read edge_count */ - CHECK_AND_RETURN(mc_reader_read_u8(&reader, &iev->edge_count, status)); + // Set equality edge_count to 1 as it doesn't technically exist but + // there will be a singular metadata block + if (iev->type == kTypeEquality) { + iev->edge_count = 1; + } else { + CHECK_AND_RETURN(mc_reader_read_u8(&reader, &iev->edge_count, status)); + } + // Maximum edge_count(255) times kMetadataLen(96) fits easily without // overflow. - const uint64_t edges_len = iev->edge_count * kMetadataLen; + const uint64_t metadata_len = iev->edge_count * kMetadataLen; /* Read ServerEncryptedValue. */ - const uint64_t min_required_len = kMinServerEncryptedValueLen + edges_len; - const uint64_t SEV_and_edges_len = mc_reader_get_remaining_length(&reader); - if (SEV_and_edges_len < min_required_len) { + const uint64_t min_required_len = kMinServerEncryptedValueLen + metadata_len; + const uint64_t SEV_and_metadata_len = mc_reader_get_remaining_length(&reader); + if (SEV_and_metadata_len < min_required_len) { CLIENT_ERR("Invalid payload size %" PRIu64 ", smaller than minimum length %" PRIu64, - SEV_and_edges_len, + SEV_and_metadata_len, min_required_len); return false; } - const uint64_t SEV_len = SEV_and_edges_len - edges_len; + const uint64_t SEV_len = SEV_and_metadata_len - metadata_len; CHECK_AND_RETURN(mc_reader_read_buffer(&reader, &iev->ServerEncryptedValue, SEV_len, status)); - // Ignore Metadata block. - BSON_ASSERT(mc_reader_get_remaining_length(&reader) == edges_len); + iev->metadata = (mc_FLE2TagAndEncryptedMetadataBlock_t *)bson_malloc0( + iev->edge_count * sizeof(mc_FLE2TagAndEncryptedMetadataBlock_t)); + + // Read each metadata element + for (uint8_t i = 0; i < iev->edge_count; i++) { + _mongocrypt_buffer_t tmp_buf; + + CHECK_AND_RETURN(mc_reader_read_buffer(&reader, &tmp_buf, kMetadataLen, status)); + CHECK_AND_RETURN(mc_FLE2TagAndEncryptedMetadataBlock_parse(&iev->metadata[i], &tmp_buf, status)); + + _mongocrypt_buffer_cleanup(&tmp_buf); + } - iev->type = kTypeRange; return true; } + +bool mc_FLE2IndexedEncryptedValueV2_serialize(const mc_FLE2IndexedEncryptedValueV2_t *iev, + _mongocrypt_buffer_t *buf, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iev); + BSON_ASSERT_PARAM(buf); + + if (iev->type != kTypeRange && iev->type != kTypeEquality) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_serialize must be called with type equality or range"); + return false; + } + + mc_writer_t writer; + mc_writer_init_from_buffer(&writer, buf, __FUNCTION__); + + // Serialize fle_blob_subtype + CHECK_AND_RETURN(mc_writer_write_u8(&writer, iev->fle_blob_subtype, status)); + + // Serialize S_KeyId + CHECK_AND_RETURN(mc_writer_write_uuid_buffer(&writer, &iev->S_KeyId, status)); + + // 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 == kTypeRange) { + CHECK_AND_RETURN(mc_writer_write_u8(&writer, iev->edge_count, status)); + } + + // Serialize encrypted value + CHECK_AND_RETURN( + mc_writer_write_buffer(&writer, &iev->ServerEncryptedValue, iev->ServerEncryptedValue.len, status)); + + // Serialize metadata + for (int i = 0; i < iev->edge_count; ++i) { + _mongocrypt_buffer_t tmp_buf; + _mongocrypt_buffer_init_size(&tmp_buf, kMetadataLen); + + CHECK_AND_RETURN(mc_FLE2TagAndEncryptedMetadataBlock_serialize(&iev->metadata[i], &tmp_buf, status)); + CHECK_AND_RETURN(mc_writer_write_buffer(&writer, &tmp_buf, kMetadataLen, status)); + + _mongocrypt_buffer_cleanup(&tmp_buf); + } + + return true; +} \ No newline at end of file diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev.c b/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev.c index 48b2eef0a45..0d2654db18c 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev.c +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-payload-iev.c @@ -100,7 +100,6 @@ bool mc_FLE2IndexedEncryptedValue_write(_mongocrypt_crypto_t *crypto, goto cleanup; \ } - const _mongocrypt_value_encryption_algorithm_t *fle2alg = _mcFLE2Algorithm(); bool ok = false; BSON_ASSERT_PARAM(crypto); @@ -130,27 +129,6 @@ bool mc_FLE2IndexedEncryptedValue_write(_mongocrypt_crypto_t *crypto, index_tokens, &encryption_out, status)); - uint32_t expected_plaintext_size = 0; - CHECK_AND_GOTO(safe_uint32_t_sum(ClientEncryptedValue->len, - (uint32_t)(sizeof(uint64_t) * 2 + sizeof(uint32_t) * 3), - &expected_plaintext_size, - status)); - - uint32_t expected_cipher_size = fle2alg->get_ciphertext_len(expected_plaintext_size, status); - - if (expected_cipher_size == 0) { - CHECK_AND_GOTO(false); - } - - uint32_t expected_buf_size = 0; - CHECK_AND_RETURN( - safe_uint32_t_sum(expected_cipher_size, (uint32_t)(1 + sizeof(S_KeyId)), &expected_buf_size, status)); - - if (buf->len < expected_buf_size) { - CLIENT_ERR("mc_FLE2IndexedEncryptedValue_write buf is not large enough for iev"); - CHECK_AND_GOTO(false); - } - mc_writer_t writer; mc_writer_init_from_buffer(&writer, buf, __FUNCTION__); diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-rfds-private.h b/src/third_party/libmongocrypt/dist/src/mc-fle2-rfds-private.h index 3d99c6b4d00..cbdde696f4b 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-rfds-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-rfds-private.h @@ -41,6 +41,11 @@ typedef struct { mc_FLE2RangeOperator_t secondOp; } mc_FLE2RangeFindDriverSpec_t; +// `mc_FLE2RangeFindDriverSpec_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof_mc_FLE2RangeFindDriverSpec_t, + BSON_ALIGNOF(mc_FLE2RangeFindDriverSpec_t) >= BSON_ALIGNOF(bson_iter_t)); + // mc_FLE2RangeFindDriverSpec_parse parses a FLE2RangeFindDriverSpec document. bool mc_FLE2RangeFindDriverSpec_parse(mc_FLE2RangeFindDriverSpec_t *spec, const bson_t *in, @@ -52,7 +57,7 @@ bool mc_FLE2RangeFindDriverSpec_parse(mc_FLE2RangeFindDriverSpec_t *spec, // `out` must be initialized by caller. bool mc_FLE2RangeFindDriverSpec_to_placeholders(mc_FLE2RangeFindDriverSpec_t *spec, const mc_RangeOpts_t *range_opts, - int64_t maxContentionCounter, + int64_t maxContentionFactor, const _mongocrypt_buffer_t *user_key_id, const _mongocrypt_buffer_t *index_key_id, int32_t payloadId, @@ -73,11 +78,17 @@ typedef struct { mc_FLE2RangeOperator_t secondOp; bson_iter_t indexMin; bson_iter_t indexMax; - int64_t maxContentionCounter; + int64_t maxContentionFactor; int64_t sparsity; - mc_optional_uint32_t precision; + mc_optional_int32_t precision; + mc_optional_int32_t trimFactor; } mc_makeRangeFindPlaceholder_args_t; +// `mc_makeRangeFindPlaceholder_args_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof_mc_makeRangeFindPlaceholder_args_t, + BSON_ALIGNOF(mc_makeRangeFindPlaceholder_args_t) >= BSON_ALIGNOF(bson_iter_t)); + // mc_makeRangeFindPlaceholder creates a placeholder to be consumed by // libmongocrypt to encrypt a range find query. It is included in the header to // be used by tests. diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-rfds.c b/src/third_party/libmongocrypt/dist/src/mc-fle2-rfds.c index 02dac30439b..0622bfd9d54 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-fle2-rfds.c +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-rfds.c @@ -70,7 +70,7 @@ static bool parse_and(const bson_t *in, bson_iter_t *out, mongocrypt_status_t *s BSON_ASSERT_PARAM(out); BSON_ASSERT(status || true); - bson_iter_t and; + bson_iter_t and = {0}; if (!bson_iter_init(&and, in) || !bson_iter_next(&and) || 0 != strcmp(bson_iter_key(&and), "$and")) { ERR_WITH_BSON(in, "%s", "error unable to find '$and'"); return false; @@ -105,7 +105,7 @@ parse_aggregate_expression(const bson_t *orig, bson_iter_t *in, operator_value_t BSON_ASSERT_PARAM(out); BSON_ASSERT(status || true); - bson_iter_t array, value; + bson_iter_t array = {0}, value; const char *op_type_str = bson_iter_key(in); bool ok = false; const char *field; @@ -162,7 +162,7 @@ parse_match_expression(const bson_t *orig, bson_iter_t *in, operator_value_t *ou BSON_ASSERT_PARAM(out); BSON_ASSERT(status || true); - bson_iter_t document, value; + bson_iter_t document = {0}, value; const char *op_type_str; bool ok = false; const char *field = bson_iter_key(in); @@ -217,7 +217,7 @@ bool mc_FLE2RangeFindDriverSpec_parse(mc_FLE2RangeFindDriverSpec_t *spec, // {$and: [{$gt: ["$age", 5]}, {$lt:["$age", 50]}]} // Or `in` may be a Match Expression with this form: // {$and: [{age: {$gt: 5}}, {age: {$lt: 50}} ]} - bson_iter_t and, array; + bson_iter_t and = {0}, array = {0}; bool ok = false; if (!parse_and(in, &and, status)) { @@ -264,7 +264,7 @@ bool mc_FLE2RangeFindDriverSpec_parse(mc_FLE2RangeFindDriverSpec_t *spec, } } - operator_value_t op; + operator_value_t op = {0}; switch (arg_type) { case AGGREGATE_EXPRESSION: if (!parse_aggregate_expression(in, &doc, &op, status)) { @@ -364,8 +364,10 @@ bool mc_makeRangeFindPlaceholder(mc_makeRangeFindPlaceholder_args_t *args, TRY(bson_append_iter(edgesInfo, "indexMin", -1, &args->indexMin)); TRY(bson_append_iter(edgesInfo, "indexMax", -1, &args->indexMax)); if (args->precision.set) { - BSON_ASSERT(args->precision.value <= INT32_MAX); - TRY(BSON_APPEND_INT32(edgesInfo, "precision", (int32_t)args->precision.value)); + TRY(BSON_APPEND_INT32(edgesInfo, "precision", args->precision.value)); + } + if (args->trimFactor.set) { + TRY(BSON_APPEND_INT32(edgesInfo, "trimFactor", args->trimFactor.value)); } TRY(BSON_APPEND_DOCUMENT(v, "edgesInfo", edgesInfo)); } @@ -383,7 +385,7 @@ bool mc_makeRangeFindPlaceholder(mc_makeRangeFindPlaceholder_args_t *args, TRY(_mongocrypt_buffer_append(args->index_key_id, p, "ki", 2)); TRY(_mongocrypt_buffer_append(args->user_key_id, p, "ku", 2)); TRY(BSON_APPEND_DOCUMENT(p, "v", v)); - TRY(BSON_APPEND_INT64(p, "cm", args->maxContentionCounter)); + TRY(BSON_APPEND_INT64(p, "cm", args->maxContentionFactor)); TRY(BSON_APPEND_INT64(p, "s", args->sparsity)); #undef TRY @@ -403,7 +405,7 @@ fail: bool mc_FLE2RangeFindDriverSpec_to_placeholders(mc_FLE2RangeFindDriverSpec_t *spec, const mc_RangeOpts_t *range_opts, - int64_t maxContentionCounter, + int64_t maxContentionFactor, const _mongocrypt_buffer_t *user_key_id, const _mongocrypt_buffer_t *index_key_id, int32_t payloadId, @@ -469,8 +471,9 @@ bool mc_FLE2RangeFindDriverSpec_to_placeholders(mc_FLE2RangeFindDriverSpec_t *sp .indexMin = indexMin, .indexMax = indexMax, .precision = range_opts->precision, - .maxContentionCounter = maxContentionCounter, - .sparsity = range_opts->sparsity}; + .maxContentionFactor = maxContentionFactor, + .sparsity = range_opts->sparsity, + .trimFactor = range_opts->trimFactor}; // First operator is the non-stub. if (!mc_makeRangeFindPlaceholder(&args, &p1, status)) { @@ -485,7 +488,7 @@ bool mc_FLE2RangeFindDriverSpec_to_placeholders(mc_FLE2RangeFindDriverSpec_t *sp .payloadId = payloadId, .firstOp = spec->firstOp, .secondOp = spec->secondOp, - .maxContentionCounter = maxContentionCounter, + .maxContentionFactor = maxContentionFactor, .sparsity = range_opts->sparsity}; // First operator is the non-stub. diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-tag-and-encrypted-metadata-block-private.h b/src/third_party/libmongocrypt/dist/src/mc-fle2-tag-and-encrypted-metadata-block-private.h new file mode 100644 index 00000000000..e287c43e501 --- /dev/null +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-tag-and-encrypted-metadata-block-private.h @@ -0,0 +1,44 @@ +/* + * Copyright 2024-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MC_FLE2_TAG_AND_ENCRYPTED_METADATA_BLOCK_H +#define MC_FLE2_TAG_AND_ENCRYPTED_METADATA_BLOCK_H + +#include "mc-reader-private.h" +#include "mc-writer-private.h" +#include "mongocrypt-private.h" + +typedef struct { + _mongocrypt_buffer_t encryptedCount; + _mongocrypt_buffer_t tag; + _mongocrypt_buffer_t encryptedZeros; +} mc_FLE2TagAndEncryptedMetadataBlock_t; + +#define kFieldLen 32U + +void mc_FLE2TagAndEncryptedMetadataBlock_init(mc_FLE2TagAndEncryptedMetadataBlock_t *metadata); + +void mc_FLE2TagAndEncryptedMetadataBlock_cleanup(mc_FLE2TagAndEncryptedMetadataBlock_t *metadata); + +bool mc_FLE2TagAndEncryptedMetadataBlock_parse(mc_FLE2TagAndEncryptedMetadataBlock_t *metadata, + const _mongocrypt_buffer_t *buf, + mongocrypt_status_t *status); + +bool mc_FLE2TagAndEncryptedMetadataBlock_serialize(const mc_FLE2TagAndEncryptedMetadataBlock_t *metadata, + _mongocrypt_buffer_t *buf, + mongocrypt_status_t *status); + +#endif /* MC_FLE2_TAG_AND_ENCRYPTED_METADATA_BLOCK_H */ \ No newline at end of file diff --git a/src/third_party/libmongocrypt/dist/src/mc-fle2-tag-and-encrypted-metadata-block.c b/src/third_party/libmongocrypt/dist/src/mc-fle2-tag-and-encrypted-metadata-block.c new file mode 100644 index 00000000000..404e9840367 --- /dev/null +++ b/src/third_party/libmongocrypt/dist/src/mc-fle2-tag-and-encrypted-metadata-block.c @@ -0,0 +1,81 @@ +/* + * Copyright 2024-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mc-fle2-tag-and-encrypted-metadata-block-private.h" +#include "mc-reader-private.h" +#include "mc-writer-private.h" +#include "mongocrypt-private.h" + +#define CHECK_AND_RETURN(x) \ + if (!(x)) { \ + return false; \ + } + +void mc_FLE2TagAndEncryptedMetadataBlock_init(mc_FLE2TagAndEncryptedMetadataBlock_t *metadata) { + BSON_ASSERT_PARAM(metadata); + memset(metadata, 0, sizeof(mc_FLE2TagAndEncryptedMetadataBlock_t)); +} + +void mc_FLE2TagAndEncryptedMetadataBlock_cleanup(mc_FLE2TagAndEncryptedMetadataBlock_t *metadata) { + BSON_ASSERT_PARAM(metadata); + + _mongocrypt_buffer_cleanup(&metadata->encryptedCount); + _mongocrypt_buffer_cleanup(&metadata->tag); + _mongocrypt_buffer_cleanup(&metadata->encryptedZeros); +} + +bool mc_FLE2TagAndEncryptedMetadataBlock_parse(mc_FLE2TagAndEncryptedMetadataBlock_t *metadata, + const _mongocrypt_buffer_t *buf, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(metadata); + BSON_ASSERT_PARAM(buf); + + if ((buf->data == NULL) || (buf->len == 0)) { + CLIENT_ERR("Empty buffer passed to mc_FLE2IndexedEncryptedValueV2_parse"); + return false; + } + + mc_reader_t reader; + mc_reader_init_from_buffer(&reader, buf, __FUNCTION__); + + mc_FLE2TagAndEncryptedMetadataBlock_init(metadata); + + CHECK_AND_RETURN(mc_reader_read_buffer(&reader, &metadata->encryptedCount, kFieldLen, status)); + + CHECK_AND_RETURN(mc_reader_read_buffer(&reader, &metadata->tag, kFieldLen, status)); + + CHECK_AND_RETURN(mc_reader_read_buffer(&reader, &metadata->encryptedZeros, kFieldLen, status)); + + return true; +} + +bool mc_FLE2TagAndEncryptedMetadataBlock_serialize(const mc_FLE2TagAndEncryptedMetadataBlock_t *metadata, + _mongocrypt_buffer_t *buf, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(metadata); + BSON_ASSERT_PARAM(buf); + + mc_writer_t writer; + mc_writer_init_from_buffer(&writer, buf, __FUNCTION__); + + CHECK_AND_RETURN(mc_writer_write_buffer(&writer, &metadata->encryptedCount, kFieldLen, status)); + + CHECK_AND_RETURN(mc_writer_write_buffer(&writer, &metadata->tag, kFieldLen, status)); + + CHECK_AND_RETURN(mc_writer_write_buffer(&writer, &metadata->encryptedZeros, kFieldLen, status)); + + return true; +} diff --git a/src/third_party/libmongocrypt/dist/src/mc-optional-private.h b/src/third_party/libmongocrypt/dist/src/mc-optional-private.h index d27a53d605b..42a92eb398b 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-optional-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-optional-private.h @@ -23,6 +23,17 @@ #include "./mc-dec128.h" #include "./mlib/int128.h" +typedef struct { + bool set; + bool value; +} mc_optional_bool_t; + +#define OPT_BOOL(val) \ + (mc_optional_bool_t) { .set = true, .value = val } + +#define OPT_BOOL_C(val) \ + { .set = true, .value = val } + typedef struct { bool set; int32_t value; diff --git a/src/third_party/libmongocrypt/dist/src/mc-range-edge-generation-private.h b/src/third_party/libmongocrypt/dist/src/mc-range-edge-generation-private.h index e9d2a5f86f6..268eef335e6 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-range-edge-generation-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-range-edge-generation-private.h @@ -37,49 +37,59 @@ size_t mc_edges_len(mc_edges_t *edges); // mc_edges_destroys frees `edges`. void mc_edges_destroy(mc_edges_t *edges); +// mc_edges_is_leaf returns whether the given edge is the leaf node of the edge set. +bool mc_edges_is_leaf(const mc_edges_t *edges, const char *edge); + +// Return the trimFactor that was used to generate these edges. +int32_t mc_edges_get_used_trimFactor(const mc_edges_t *edges); + typedef struct { int32_t value; mc_optional_int32_t min; mc_optional_int32_t max; size_t sparsity; + mc_optional_int32_t trimFactor; } mc_getEdgesInt32_args_t; // mc_getEdgesInt32 implements the Edge Generation algorithm described in // SERVER-67751 for int32_t. -mc_edges_t *mc_getEdgesInt32(mc_getEdgesInt32_args_t args, mongocrypt_status_t *status); +mc_edges_t *mc_getEdgesInt32(mc_getEdgesInt32_args_t args, mongocrypt_status_t *status, bool use_range_v2); typedef struct { int64_t value; mc_optional_int64_t min; mc_optional_int64_t max; size_t sparsity; + mc_optional_int32_t trimFactor; } mc_getEdgesInt64_args_t; // mc_getEdgesInt64 implements the Edge Generation algorithm described in // SERVER-67751 for int64_t. -mc_edges_t *mc_getEdgesInt64(mc_getEdgesInt64_args_t args, mongocrypt_status_t *status); +mc_edges_t *mc_getEdgesInt64(mc_getEdgesInt64_args_t args, mongocrypt_status_t *status, bool use_range_v2); typedef struct { double value; size_t sparsity; mc_optional_double_t min; mc_optional_double_t max; - mc_optional_uint32_t precision; + mc_optional_int32_t precision; + mc_optional_int32_t trimFactor; } mc_getEdgesDouble_args_t; // mc_getEdgesDouble implements the Edge Generation algorithm described in // SERVER-67751 for double. -mc_edges_t *mc_getEdgesDouble(mc_getEdgesDouble_args_t args, mongocrypt_status_t *status); +mc_edges_t *mc_getEdgesDouble(mc_getEdgesDouble_args_t args, mongocrypt_status_t *status, bool use_range_v2); #if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT typedef struct { mc_dec128 value; size_t sparsity; mc_optional_dec128_t min, max; - mc_optional_uint32_t precision; + mc_optional_int32_t precision; + mc_optional_int32_t trimFactor; } mc_getEdgesDecimal128_args_t; -mc_edges_t *mc_getEdgesDecimal128(mc_getEdgesDecimal128_args_t args, mongocrypt_status_t *status); +mc_edges_t *mc_getEdgesDecimal128(mc_getEdgesDecimal128_args_t args, mongocrypt_status_t *status, bool use_range_v2); #endif // MONGOCRYPT_HAVE_DECIMAL128_SUPPORT BSON_STATIC_ASSERT2(ull_is_u64, sizeof(uint64_t) == sizeof(unsigned long long)); diff --git a/src/third_party/libmongocrypt/dist/src/mc-range-edge-generation.c b/src/third_party/libmongocrypt/dist/src/mc-range-edge-generation.c index 3e7c5d0a00a..83b1900c042 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-range-edge-generation.c +++ b/src/third_party/libmongocrypt/dist/src/mc-range-edge-generation.c @@ -14,10 +14,12 @@ * limitations under the License. */ +#include "mc-optional-private.h" #include "mc-range-edge-generation-private.h" #include "mc-array-private.h" #include "mc-check-conversions-private.h" +#include "mc-cmp-private.h" #include "mc-range-encoding-private.h" #include "mongocrypt-private.h" @@ -25,27 +27,60 @@ struct _mc_edges_t { size_t sparsity; /* edges is an array of `char*` edge strings. */ mc_array_t edges; + char *leaf; + int32_t usedTrimFactor; // The `trimFactor` that was used to produce these edges. }; -static mc_edges_t *mc_edges_new(const char *leaf, size_t sparsity, mongocrypt_status_t *status) { +int32_t mc_edges_get_used_trimFactor(const mc_edges_t *edges) { + return edges->usedTrimFactor; +} + +static mc_edges_t *mc_edges_new(const char *leaf, + size_t sparsity, + mc_optional_int32_t opt_trimFactor, + mongocrypt_status_t *status, + bool use_range_v2) { BSON_ASSERT_PARAM(leaf); if (sparsity < 1) { CLIENT_ERR("sparsity must be 1 or larger"); return NULL; } + + const size_t leaf_len = strlen(leaf); + const int32_t trimFactor = trimFactorDefault(leaf_len, opt_trimFactor, use_range_v2); + if (trimFactor != 0 && mc_cmp_greater_equal_su(trimFactor, leaf_len)) { + // We append a total of leaf_len + 1 (for the root) - trimFactor edges. When this number is equal to 1, we + // degenerate into equality, which is not desired, so trimFactor must be less than leaf_len. + CLIENT_ERR("trimFactor must be less than the number of bits (%ld) used to represent an element of the domain, " + "but got %" PRId32, + leaf_len, + trimFactor); + return NULL; + } + if (trimFactor < 0) { + CLIENT_ERR("trimFactor must be >= 0, but got %" PRId32, trimFactor); + return NULL; + } + mc_edges_t *edges = bson_malloc0(sizeof(mc_edges_t)); + edges->usedTrimFactor = trimFactor; edges->sparsity = sparsity; _mc_array_init(&edges->edges, sizeof(char *)); + edges->leaf = bson_strdup(leaf); - char *root = bson_strdup("root"); - _mc_array_append_val(&edges->edges, root); + if (trimFactor == 0) { + char *root = bson_strdup("root"); + _mc_array_append_val(&edges->edges, root); + } char *leaf_copy = bson_strdup(leaf); _mc_array_append_val(&edges->edges, leaf_copy); - const size_t leaf_len = strlen(leaf); - // Start loop at 1. The full leaf is unconditionally appended after loop. - for (size_t i = 1; i < leaf_len; i++) { + // Start loop at max(trimFactor, 1). The full leaf is unconditionally appended after loop. + BSON_ASSERT(mc_in_range_size_t_signed(trimFactor)); + size_t trimFactor_sz = (size_t)trimFactor; + size_t startLevel = trimFactor > 0 ? trimFactor_sz : 1; + for (size_t i = startLevel; i < leaf_len; i++) { if (i % sparsity == 0) { char *edge = bson_malloc(i + 1); bson_strncpy(edge, leaf, i + 1); @@ -78,20 +113,26 @@ void mc_edges_destroy(mc_edges_t *edges) { bson_free(val); } _mc_array_destroy(&edges->edges); + bson_free(edges->leaf); bson_free(edges); } +bool mc_edges_is_leaf(const mc_edges_t *edges, const char *edge) { + BSON_ASSERT_PARAM(edges); + BSON_ASSERT_PARAM(edge); + + return strcmp(edge, edges->leaf) == 0; +} + mc_bitstring mc_convert_to_bitstring_u64(uint64_t in) { mc_bitstring ret = {{0}}; char *out = ret.str; uint64_t bit = UINT64_C(1) << 63; + int loops = 0; // used to determine a bit shift while (bit > 0) { - if (bit & in) { - *out++ = '1'; - } else { - *out++ = '0'; - } + *out++ = (char)('0' + ((bit & in) >> (63 - loops))); bit >>= 1; + loops++; } return ret; } @@ -122,7 +163,7 @@ mc_bitstring mc_convert_to_bitstring_u128(mlib_int128 i) { return ret; } -mc_edges_t *mc_getEdgesInt32(mc_getEdgesInt32_args_t args, mongocrypt_status_t *status) { +mc_edges_t *mc_getEdgesInt32(mc_getEdgesInt32_args_t args, mongocrypt_status_t *status, bool use_range_v2) { mc_OSTType_Int32 got; if (!mc_getTypeInfo32((mc_getTypeInfo32_args_t){.value = args.value, .min = args.min, .max = args.max}, &got, @@ -138,11 +179,11 @@ mc_edges_t *mc_getEdgesInt32(mc_getEdgesInt32_args_t args, mongocrypt_status_t * mc_bitstring valueBin = mc_convert_to_bitstring_u32(got.value); size_t offset = mc_count_leading_zeros_u32(got.max); const char *leaf = valueBin.str + offset; - mc_edges_t *ret = mc_edges_new(leaf, args.sparsity, status); + mc_edges_t *ret = mc_edges_new(leaf, args.sparsity, args.trimFactor, status, use_range_v2); return ret; } -mc_edges_t *mc_getEdgesInt64(mc_getEdgesInt64_args_t args, mongocrypt_status_t *status) { +mc_edges_t *mc_getEdgesInt64(mc_getEdgesInt64_args_t args, mongocrypt_status_t *status, bool use_range_v2) { mc_OSTType_Int64 got; if (!mc_getTypeInfo64((mc_getTypeInfo64_args_t){.value = args.value, .min = args.min, .max = args.max}, &got, @@ -158,18 +199,19 @@ mc_edges_t *mc_getEdgesInt64(mc_getEdgesInt64_args_t args, mongocrypt_status_t * mc_bitstring valueBin = mc_convert_to_bitstring_u64(got.value); size_t offset = mc_count_leading_zeros_u64(got.max); const char *leaf = valueBin.str + offset; - mc_edges_t *ret = mc_edges_new(leaf, args.sparsity, status); + mc_edges_t *ret = mc_edges_new(leaf, args.sparsity, args.trimFactor, status, use_range_v2); return ret; } -mc_edges_t *mc_getEdgesDouble(mc_getEdgesDouble_args_t args, mongocrypt_status_t *status) { +mc_edges_t *mc_getEdgesDouble(mc_getEdgesDouble_args_t args, mongocrypt_status_t *status, bool use_range_v2) { mc_OSTType_Double got; if (!mc_getTypeInfoDouble((mc_getTypeInfoDouble_args_t){.value = args.value, .min = args.min, .max = args.max, .precision = args.precision}, &got, - status)) { + status, + use_range_v2)) { return NULL; } @@ -181,12 +223,12 @@ mc_edges_t *mc_getEdgesDouble(mc_getEdgesDouble_args_t args, mongocrypt_status_t mc_bitstring valueBin = mc_convert_to_bitstring_u64(got.value); size_t offset = mc_count_leading_zeros_u64(got.max); const char *leaf = valueBin.str + offset; - mc_edges_t *ret = mc_edges_new(leaf, args.sparsity, status); + mc_edges_t *ret = mc_edges_new(leaf, args.sparsity, args.trimFactor, status, use_range_v2); return ret; } #if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT -mc_edges_t *mc_getEdgesDecimal128(mc_getEdgesDecimal128_args_t args, mongocrypt_status_t *status) { +mc_edges_t *mc_getEdgesDecimal128(mc_getEdgesDecimal128_args_t args, mongocrypt_status_t *status, bool use_range_v2) { mc_OSTType_Decimal128 got; if (!mc_getTypeInfoDecimal128( (mc_getTypeInfoDecimal128_args_t){ @@ -196,7 +238,8 @@ mc_edges_t *mc_getEdgesDecimal128(mc_getEdgesDecimal128_args_t args, mongocrypt_ .precision = args.precision, }, &got, - status)) { + status, + use_range_v2)) { return NULL; } @@ -205,7 +248,7 @@ mc_edges_t *mc_getEdgesDecimal128(mc_getEdgesDecimal128_args_t args, mongocrypt_ mc_bitstring bits = mc_convert_to_bitstring_u128(got.value); size_t offset = mc_count_leading_zeros_u128(got.max); const char *leaf = bits.str + offset; - mc_edges_t *ret = mc_edges_new(leaf, args.sparsity, status); + mc_edges_t *ret = mc_edges_new(leaf, args.sparsity, args.trimFactor, status, use_range_v2); return ret; } -#endif // MONGOCRYPT_HAVE_DECIMAL128_SUPPORT \ No newline at end of file +#endif // MONGOCRYPT_HAVE_DECIMAL128_SUPPORT diff --git a/src/third_party/libmongocrypt/dist/src/mc-range-encoding-private.h b/src/third_party/libmongocrypt/dist/src/mc-range-encoding-private.h index a79e97a47ad..2250b1fb476 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-range-encoding-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-range-encoding-private.h @@ -83,14 +83,22 @@ typedef struct { double value; mc_optional_double_t min; mc_optional_double_t max; - mc_optional_uint32_t precision; + mc_optional_int32_t precision; } mc_getTypeInfoDouble_args_t; +// `mc_canUsePrecisionModeDouble` returns true if the domain can be represented in fewer than 64 bits. +bool mc_canUsePrecisionModeDouble(double min, + double max, + int32_t precision, + uint32_t *maxBitsOut, + mongocrypt_status_t *status); + /* mc_getTypeInfoDouble encodes the double `args.value` into an OSTType_Double * `out`. Returns false and sets `status` on error. */ bool mc_getTypeInfoDouble(mc_getTypeInfoDouble_args_t args, mc_OSTType_Double *out, - mongocrypt_status_t *status) MONGOCRYPT_WARN_UNUSED_RESULT; + mongocrypt_status_t *status, + bool use_range_v2) MONGOCRYPT_WARN_UNUSED_RESULT; #if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT /** @@ -103,9 +111,16 @@ typedef struct { typedef struct { mc_dec128 value; mc_optional_dec128_t min, max; - mc_optional_uint32_t precision; + mc_optional_int32_t precision; } mc_getTypeInfoDecimal128_args_t; +// `mc_canUsePrecisionModeDecimal` returns true if the domain can be represented in fewer than 128 bits. +bool mc_canUsePrecisionModeDecimal(mc_dec128 min, + mc_dec128 max, + int32_t precision, + uint32_t *maxBitsOut, + mongocrypt_status_t *status); + /** * @brief Obtain the OST encoding of a finite Decimal128 value. * @@ -116,7 +131,11 @@ typedef struct { */ bool mc_getTypeInfoDecimal128(mc_getTypeInfoDecimal128_args_t args, mc_OSTType_Decimal128 *out, - mongocrypt_status_t *status) MONGOCRYPT_WARN_UNUSED_RESULT; + mongocrypt_status_t *status, + bool use_range_v2) MONGOCRYPT_WARN_UNUSED_RESULT; #endif // MONGOCRYPT_HAVE_DECIMAL128_SUPPORT +extern const int64_t mc_FLERangeSparsityDefault; + +int32_t trimFactorDefault(size_t maxlen, mc_optional_int32_t trimFactor, bool use_range_v2); #endif /* MC_RANGE_ENCODING_PRIVATE_H */ diff --git a/src/third_party/libmongocrypt/dist/src/mc-range-encoding.c b/src/third_party/libmongocrypt/dist/src/mc-range-encoding.c index 65f34915b99..e7d423b4be4 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-range-encoding.c +++ b/src/third_party/libmongocrypt/dist/src/mc-range-encoding.c @@ -15,6 +15,7 @@ */ #include "mc-check-conversions-private.h" +#include "mc-cmp-private.h" #include "mc-range-encoding-private.h" #include "mongocrypt-private.h" #include "mongocrypt-util-private.h" // mc_isinf @@ -162,8 +163,131 @@ bool mc_getTypeInfo64(mc_getTypeInfo64_args_t args, mc_OSTType_Int64 *out, mongo } #define exp10Double(x) pow(10, x) +#define SCALED_DOUBLE_BOUNDS 9007199254740992.0 // 2^53 -bool mc_getTypeInfoDouble(mc_getTypeInfoDouble_args_t args, mc_OSTType_Double *out, mongocrypt_status_t *status) { +uint64_t subtract_int64_t(int64_t max, int64_t min) { + BSON_ASSERT(max > min); + // If the values have the same sign, then simple subtraction + // will work because we know max > min. + if ((max > 0 && min > 0) || (max < 0 && min < 0)) { + return (uint64_t)(max - min); + } + + // If they are opposite signs, then we can just invert + // min to be positive and return the sum. + uint64_t u_return = (uint64_t)max; + u_return += (uint64_t)(~min + 1); + return u_return; +} + +bool ceil_log2_double(uint64_t i, uint32_t *maxBitsOut, mongocrypt_status_t *status) { + if (i == 0) { + CLIENT_ERR("Invalid input to ceil_log2_double function. Input cannot be 0."); + return false; + } + + uint32_t clz = (uint32_t)_mlibCountLeadingZeros_u64(i); + uint32_t bits; + if ((i & (i - 1)) == 0) { + bits = 64 - clz - 1; + } else { + bits = 64 - clz; + } + *maxBitsOut = bits; + return true; +} + +bool mc_canUsePrecisionModeDouble(double min, + double max, + int32_t precision, + uint32_t *maxBitsOut, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(maxBitsOut); + BSON_ASSERT(precision >= 0); + + if (min >= max) { + CLIENT_ERR("Invalid bounds for double range precision, min must be less than max. min: %g, max: %g", min, max); + return false; + } + + const double scaled_prc = exp10Double(precision); + + const double scaled_max = max * scaled_prc; + const double scaled_min = min * scaled_prc; + + if (scaled_max != trunc(scaled_max)) { + CLIENT_ERR("Invalid upper bound for double precision. Fractional digits must be less than the specified " + "precision value. max: %g", + max); + return false; + } + + if (scaled_min != trunc(scaled_min)) { + CLIENT_ERR("Invalid lower bound for double precision. Fractional digits must be less than the specified " + "precision value. min: %g", + min); + return false; + } + + if (fabs(scaled_max) >= SCALED_DOUBLE_BOUNDS) { + CLIENT_ERR( + "Invalid upper bound for double precision. Absolute scaled value of max must be less than %g. max: %g", + SCALED_DOUBLE_BOUNDS, + max); + return false; + } + + if (fabs(scaled_min) >= SCALED_DOUBLE_BOUNDS) { + CLIENT_ERR( + "Invalid lower bound for double precision. Absolute scaled value of min must be less than %g. min: %g", + SCALED_DOUBLE_BOUNDS, + min); + return false; + } + + const double t_1 = scaled_max - scaled_min; + const double t_4 = (double)UINT64_MAX - t_1; + const double t_5 = floor(log10(t_4)) - 1; + + if ((double)precision > t_5) { + CLIENT_ERR("Invalid value for precision. precision: %" PRId32, precision); + return false; + } + + const int64_t i_1 = (int64_t)(scaled_max); + const int64_t i_2 = (int64_t)(scaled_min); + + const uint64_t range = subtract_int64_t(i_1, i_2); + + if (((uint64_t)scaled_prc) > UINT64_MAX - range) { + CLIENT_ERR("Invalid value for min, max, and precision. The calculated domain size is too large. min: %g, max: " + "%g, precision: %" PRId32, + min, + max, + precision); + return false; + } + + const uint64_t i_3 = range + (uint64_t)(scaled_prc); + + if (!ceil_log2_double(i_3, maxBitsOut, status)) { + return false; + } + + // Integers between -2^53 and 2^53 can be exactly represented. Outside this range, doubles lose precision by a + // multiple of 2^(n-52) where n = #bits. We disallow users from using precision mode when the bounds exceed 2^53 to + // prevent the users from being surprised by how floating point math works. + if (*maxBitsOut >= 53) { + return false; + } + + return true; +} + +bool mc_getTypeInfoDouble(mc_getTypeInfoDouble_args_t args, + mc_OSTType_Double *out, + mongocrypt_status_t *status, + bool use_range_v2) { if (args.min.set != args.max.set || args.min.set != args.precision.set) { CLIENT_ERR("min, max, and precision must all be set or must all be unset"); return false; @@ -194,6 +318,19 @@ bool mc_getTypeInfoDouble(mc_getTypeInfoDouble_args_t args, mc_OSTType_Double *o } } + if (args.precision.set) { + if (args.precision.value < 0) { + CLIENT_ERR("Precision must be non-negative, but got %" PRId32, args.precision.value); + return false; + } + + double scaled = exp10Double(args.precision.value); + if (!mc_isfinite(scaled)) { + CLIENT_ERR("Precision is too large and cannot be used to calculate the scaled range bounds"); + return false; + } + } + const bool is_neg = args.value < 0.0; // Map negative 0 to zero so sign bit is 0. @@ -210,39 +347,33 @@ bool mc_getTypeInfoDouble(mc_getTypeInfoDouble_args_t args, mc_OSTType_Double *o bool use_precision_mode = false; uint32_t bits_range; if (args.precision.set) { - // Subnormal representations can support up to 5x10^-324 as a number - if (args.precision.value > 324) { - CLIENT_ERR("Precision must be between 0 and 324 inclusive, got: %" PRIu32, args.precision.value); + use_precision_mode = + mc_canUsePrecisionModeDouble(args.min.value, args.max.value, args.precision.value, &bits_range, status); + + if (!use_precision_mode && use_range_v2) { + if (!mongocrypt_status_ok(status)) { + return false; + } + + CLIENT_ERR("The domain of double values specified by the min, max, and precision cannot be represented in " + "fewer than 53 bits. min: %g, max: %g, precision: %" PRId32, + args.min.value, + args.max.value, + args.precision.value); return false; } - double range = args.max.value - args.min.value; - - // We can overflow if max = max double and min = min double so make sure - // we have finite number after we do subtraction - // Ignore conversion warnings to fix error with glibc. - if (mc_isfinite(range)) { - // This creates a range which is wider then we permit by our min/max - // bounds check with the +1 but it is as the algorithm is written in - // WRITING-11907. - double rangeAndPrecision = (range + 1) * exp10Double(args.precision.value); - - if (mc_isfinite(rangeAndPrecision)) { - double bits_range_double = log2(rangeAndPrecision); - bits_range = (uint32_t)ceil(bits_range_double); - - if (bits_range < 64) { - use_precision_mode = true; - } - } - } + // If we are not in range_v2, then we don't care about the error returned + // from canUsePrecisionMode so we can reset the status. + _mongocrypt_status_reset(status); } if (use_precision_mode) { // Take a number of xxxx.ppppp and truncate it xxxx.ppp if precision = 3. // We do not change the digits before the decimal place. - double v_prime = trunc(args.value * exp10Double(args.precision.value)) / exp10Double(args.precision.value); - int64_t v_prime2 = (int64_t)((v_prime - args.min.value) * exp10Double(args.precision.value)); + int64_t v_prime = (int64_t)(trunc(args.value * exp10Double(args.precision.value))); + int64_t scaled_min = (int64_t)(args.min.value * exp10Double(args.precision.value)); + int64_t v_prime2 = v_prime - scaled_min; BSON_ASSERT(v_prime2 < INT64_MAX && v_prime2 >= 0); @@ -298,7 +429,7 @@ bool mc_getTypeInfoDouble(mc_getTypeInfoDouble_args_t args, mc_OSTType_Double *o * @param dec * @return mlib_int128 */ -static mlib_int128 dec128_to_int128(mc_dec128 dec) { +static mlib_int128 dec128_to_uint128(mc_dec128 dec) { // Only normal numbers BSON_ASSERT(mc_dec128_is_finite(dec)); BSON_ASSERT(!mc_dec128_is_nan(dec)); @@ -313,6 +444,7 @@ static mlib_int128 dec128_to_int128(mc_dec128 dec) { // Decimal128: int32_t exp = ((int32_t)mc_dec128_get_biased_exp(dec)) - MC_DEC128_EXPONENT_BIAS; // We will scale up/down based on whether it is negative: + BSON_ASSERT(abs(exp) <= UINT8_MAX); mlib_int128 e1 = mlib_int128_pow10((uint8_t)abs(exp)); if (exp < 0) { ret = mlib_int128_div(ret, e1); @@ -323,15 +455,176 @@ static mlib_int128 dec128_to_int128(mc_dec128 dec) { return ret; } +// (2^127 - 1) = the maximum signed 128-bit integer value, as a decimal128 +#define INT_128_MAX_AS_DECIMAL mc_dec128_from_string("170141183460469231731687303715884105727") +// (2^128 - 1) = the max unsigned 128-bit integer value, as a decimal128 +#define UINT_128_MAX_AS_DECIMAL mc_dec128_from_string("340282366920938463463374607431768211455") + +static mlib_int128 dec128_to_int128(mc_dec128 dec) { + BSON_ASSERT(mc_dec128_less(dec, INT_128_MAX_AS_DECIMAL)); + + bool negative = false; + + if (mc_dec128_is_negative(dec)) { + negative = true; + dec = mc_dec128_mul(MC_DEC128(-1), dec); + } + + mlib_int128 ret_val = dec128_to_uint128(dec); + + if (negative) { + ret_val = mlib_int128_mul(MLIB_INT128(-1), ret_val); + } + + return ret_val; +} + +bool ceil_log2_int128(mlib_int128 i, uint32_t *maxBitsOut, mongocrypt_status_t *status) { + if (mlib_int128_eq(i, MLIB_INT128(0))) { + CLIENT_ERR("Invalid input to ceil_log2_int128 function. Input cannot be 0."); + return false; + } + + uint32_t clz = (uint32_t)_mlibCountLeadingZeros_u128(i); + uint32_t bits; + + // if i & (i - 1) == 0 + if (mlib_int128_eq((mlib_int128_bitand(i, (mlib_int128_sub(i, MLIB_INT128(1))))), MLIB_INT128(0))) { + bits = 128 - clz - 1; + } else { + bits = 128 - clz; + } + *maxBitsOut = bits; + return true; +} + +bool mc_canUsePrecisionModeDecimal(mc_dec128 min, + mc_dec128 max, + int32_t precision, + uint32_t *maxBitsOut, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(maxBitsOut); + BSON_ASSERT(precision >= 0); + + if (!mc_dec128_is_finite(max)) { + CLIENT_ERR("Invalid upper bound for Decimal128 precision. Max is infinite."); + return false; + } + + if (!mc_dec128_is_finite(min)) { + CLIENT_ERR("Invalid lower bound for Decimal128 precision. Min is infinite."); + return false; + } + + if (mc_dec128_greater_equal(min, max)) { + CLIENT_ERR("Invalid upper and lower bounds for Decimal128 precision. Min must be strictly less than max. min: " + "%s, max: %s", + mc_dec128_to_string(min).str, + mc_dec128_to_string(max).str); + return false; + } + + mc_dec128 scaled_max = mc_dec128_scale(max, precision); + mc_dec128 scaled_min = mc_dec128_scale(min, precision); + + mc_dec128 scaled_max_trunc = mc_dec128_round_integral_ex(scaled_max, MC_DEC128_ROUND_TOWARD_ZERO, NULL); + mc_dec128 scaled_min_trunc = mc_dec128_round_integral_ex(scaled_min, MC_DEC128_ROUND_TOWARD_ZERO, NULL); + + if (mc_dec128_not_equal(scaled_max, scaled_max_trunc)) { + CLIENT_ERR("Invalid upper bound for Decimal128 precision. Fractional digits must be less than " + "the specified precision value. max: %s, precision: %" PRId32, + mc_dec128_to_string(max).str, + precision); + return false; + } + + if (mc_dec128_not_equal(scaled_min, scaled_min_trunc)) { + CLIENT_ERR("Invalid lower bound for Decimal128 precision. Fractional digits must be less than " + "the specified precision value. min: %s, precision: %" PRId32, + mc_dec128_to_string(min).str, + precision); + return false; + } + + if (mc_dec128_greater(mc_dec128_abs(scaled_max), INT_128_MAX_AS_DECIMAL)) { + CLIENT_ERR("Invalid upper bound for Decimal128 precision. Absolute scaled value must be less than " + "or equal to %s. max: %s", + mc_dec128_to_string(INT_128_MAX_AS_DECIMAL).str, + mc_dec128_to_string(max).str); + return false; + } + + if (mc_dec128_greater(mc_dec128_abs(scaled_min), INT_128_MAX_AS_DECIMAL)) { + CLIENT_ERR("Invalid lower bound for Decimal128 precision. Absolute scaled value must be less than " + "or equal to %s. min: %s", + mc_dec128_to_string(INT_128_MAX_AS_DECIMAL).str, + mc_dec128_to_string(min).str); + return false; + } + + mc_dec128 t_1 = mc_dec128_sub(scaled_max, scaled_min); + mc_dec128 t_4 = mc_dec128_sub(UINT_128_MAX_AS_DECIMAL, t_1); + + // t_5 = floor(log10(t_4)) - 1; + mc_dec128 t_5 = mc_dec128_sub(mc_dec128_round_integral_ex(mc_dec128_log10(t_4), MC_DEC128_ROUND_TOWARD_ZERO, NULL), + MC_DEC128(1)); + + // We convert precision to a double so we can avoid warning C4146 on Windows. + mc_dec128 prc_dec = mc_dec128_from_double((double)precision); + + if (mc_dec128_less(t_5, prc_dec)) { + CLIENT_ERR("Invalid value for precision. precision: %" PRId32, precision); + return false; + } + + mlib_int128 i_1 = dec128_to_int128(scaled_max); + mlib_int128 i_2 = dec128_to_int128(scaled_min); + + // Because we have guaranteed earlier that max is greater than min, we can + // subtract these values and guarantee that taking their unsigned + // representation will yield the actual range result. + mlib_int128 range128 = mlib_int128_sub(i_1, i_2); + + if (precision > UINT8_MAX) { + CLIENT_ERR("Invalid value for precision. Must be less than 255. precision: %" PRId32, precision); + return false; + } + + mlib_int128 i_3 = mlib_int128_add(range128, mlib_int128_pow10((uint8_t)precision)); + if (!ceil_log2_int128(i_3, maxBitsOut, status)) { + return false; + } + + if (*maxBitsOut >= 128) { + return false; + } + + return true; +} + bool mc_getTypeInfoDecimal128(mc_getTypeInfoDecimal128_args_t args, mc_OSTType_Decimal128 *out, - mongocrypt_status_t *status) { + mongocrypt_status_t *status, + bool use_range_v2) { /// Basic param checks if (args.min.set != args.max.set || args.min.set != args.precision.set) { CLIENT_ERR("min, max, and precision must all be set or must all be unset"); return false; } + if (args.precision.set) { + if (args.precision.value < 0) { + CLIENT_ERR("Precision must be non-negative, but got %" PRId32, args.precision.value); + return false; + } + + mc_dec128 scaled = mc_dec128_scale(MC_DEC128(1), args.precision.value); + if (!mc_dec128_is_finite(scaled)) { + CLIENT_ERR("Precision is too large and cannot be used to calculate the scaled range bounds"); + return false; + } + } + // We only accept normal numbers if (mc_dec128_is_inf(args.value) || mc_dec128_is_nan(args.value)) { CLIENT_ERR("Infinity and Nan Decimal128 values are not supported."); @@ -375,45 +668,27 @@ bool mc_getTypeInfoDecimal128(mc_getTypeInfoDecimal128_args_t args, // full range. bool use_precision_mode = false; // The number of bits required to hold the result (used for precision mode) - uint8_t bits_range = 0; + uint32_t bits_range = 0; if (args.precision.set) { - // Subnormal representations can support up to 5x10^-6182 as a number - if (args.precision.value > 6182) { - CLIENT_ERR("Precision must be between 0 and 6182 inclusive, got: %" PRIu32, args.precision.value); + use_precision_mode = + mc_canUsePrecisionModeDecimal(args.min.value, args.max.value, args.precision.value, &bits_range, status); + + if (use_range_v2 && !use_precision_mode) { + if (!mongocrypt_status_ok(status)) { + return false; + } + + CLIENT_ERR("The domain of decimal values specified by the min, max, and precision cannot be represented in " + "fewer than 128 bits. min: %s, max: %s, precision: %" PRIu32, + mc_dec128_to_string(args.min.value).str, + mc_dec128_to_string(args.max.value).str, + args.precision.value); return false; } - // max - min - mc_dec128 bounds_n1 = mc_dec128_sub(args.max.value, args.min.value); - // The size of [min, max]: (max - min) + 1 - mc_dec128 bounds = mc_dec128_add(bounds_n1, MC_DEC128_ONE); - - // We can overflow if max = max_dec128 and min = min_dec128 so make sure - // we have finite number after we do subtraction - if (mc_dec128_is_finite(bounds)) { - // This creates a range which is wider then we permit by our min/max - // bounds check with the +1 but it is as the algorithm is written in - // WRITING-11907. - mc_dec128 precision_scaled_bounds = mc_dec128_scale(bounds, args.precision.value); - /// The number of bits required to hold the result for the given - /// precision (as decimal) - mc_dec128 bits_range_dec = mc_dec128_log2(precision_scaled_bounds); - - if (mc_dec128_is_finite(bits_range_dec) && mc_dec128_less(bits_range_dec, MC_DEC128(128))) { - // We need fewer than 128 bits to hold the result. But round up, - // just to be sure: - int64_t r = - mc_dec128_to_int64(mc_dec128_round_integral_ex(bits_range_dec, MC_DEC128_ROUND_UPWARD, NULL)); - BSON_ASSERT(r >= 0); - BSON_ASSERT(r <= UINT8_MAX); - // We've computed the proper 'bits_range' - bits_range = (uint8_t)r; - - if (bits_range < 128) { - use_precision_mode = true; - } - } - } + // If we are not in range_v2, then we don't care about the error returned + // from canUsePrecisionMode so we can reset the status. + _mongocrypt_status_reset(status); } // Constant zero @@ -456,9 +731,9 @@ bool mc_getTypeInfoDecimal128(mc_getTypeInfoDecimal128_args_t args, v_prime2 = mc_dec128_round_integral_ex(v_prime2, MC_DEC128_ROUND_TOWARD_ZERO, NULL); BSON_ASSERT(mc_dec128_less(mc_dec128_log2(v_prime2), MC_DEC128(128))); - + BSON_ASSERT(bits_range < 128); // Resulting OST maximum - mlib_int128 ost_max = mlib_int128_sub(mlib_int128_pow2(bits_range), i128_one); + mlib_int128 ost_max = mlib_int128_sub(mlib_int128_pow2((uint8_t)bits_range), i128_one); // Now we need to get the Decimal128 out as a 128-bit integer // But Decimal128 does not support conversion to Int128. @@ -575,3 +850,23 @@ bool mc_getTypeInfoDecimal128(mc_getTypeInfoDecimal128_args_t args, } #endif // defined MONGOCRYPT_HAVE_DECIMAL128_SUPPORT + +const int64_t mc_FLERangeSparsityDefault = 2; +const int32_t mc_FLERangeTrimFactorDefault = 6; + +int32_t trimFactorDefault(size_t maxlen, mc_optional_int32_t trimFactor, bool use_range_v2) { + if (trimFactor.set) { + return trimFactor.value; + } + + if (!use_range_v2) { + // Preserve old default. + return 0; + } + + if (mc_cmp_greater_su(mc_FLERangeTrimFactorDefault, maxlen - 1)) { + return (int32_t)(maxlen - 1); + } else { + return mc_FLERangeTrimFactorDefault; + } +} diff --git a/src/third_party/libmongocrypt/dist/src/mc-range-mincover-generator.template.h b/src/third_party/libmongocrypt/dist/src/mc-range-mincover-generator.template.h index 30809ee2651..9da1bff7f00 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-range-mincover-generator.template.h +++ b/src/third_party/libmongocrypt/dist/src/mc-range-mincover-generator.template.h @@ -17,18 +17,6 @@ // mc-range-mincover-generator.template.h is meant to be included in another // source file. -// TODO: replace `CONCAT` with `BSON_CONCAT` after libbson dependency is -// upgraded to 1.20.0 or higher. -#ifndef CONCAT -#define CONCAT_1(a, b) a##b -#define CONCAT(a, b) CONCAT_1(a, b) -#endif -// TODO: replace `CONCAT3` with `BSON_CONCAT3` after libbson dependency is -// upgraded to 1.20.0 or higher. -#ifndef CONCAT3 -#define CONCAT3(a, b, c) CONCAT(a, CONCAT(b, c)) -#endif - #if !(defined(UINT_T) && defined(UINT_C) && defined(UINT_FMT_S) && defined(DECORATE_NAME)) #ifdef __INTELLISENSE__ #define UINT_T uint32_t @@ -101,6 +89,7 @@ typedef struct { UINT_T _rangeMin; UINT_T _rangeMax; size_t _sparsity; + int32_t _trimFactor; // _maxlen is the maximum bit length of edges in the mincover. size_t _maxlen; } DECORATE_NAME(MinCoverGenerator); @@ -110,7 +99,9 @@ static inline DECORATE_NAME(MinCoverGenerator) UINT_T rangeMax, UINT_T max, size_t sparsity, - mongocrypt_status_t *status) { + mc_optional_int32_t opt_trimFactor, + mongocrypt_status_t *status, + bool use_range_v2) { BSON_ASSERT_PARAM(status); if (UINT_COMPARE(rangeMin, rangeMax) > 0) { @@ -131,11 +122,25 @@ static inline DECORATE_NAME(MinCoverGenerator) CLIENT_ERR("Sparsity must be > 0"); return NULL; } + size_t maxlen = (size_t)BITS - DECORATE_NAME(mc_count_leading_zeros)(max); + int32_t trimFactor = trimFactorDefault(maxlen, opt_trimFactor, use_range_v2); + if (trimFactor != 0 && mc_cmp_greater_equal_su(trimFactor, maxlen)) { + CLIENT_ERR("Trim factor must be less than the number of bits (%ld) used to represent an element of the domain, " + "but got %" PRId32, + maxlen, + trimFactor); + return NULL; + } + if (trimFactor < 0) { + CLIENT_ERR("Trim factor must be >= 0, but got (%" PRId32 ")", trimFactor); + return NULL; + } DECORATE_NAME(MinCoverGenerator) *mcg = bson_malloc0(sizeof(DECORATE_NAME(MinCoverGenerator))); mcg->_rangeMin = rangeMin; mcg->_rangeMax = rangeMax; mcg->_maxlen = (size_t)BITS - DECORATE_NAME(mc_count_leading_zeros)(max); mcg->_sparsity = sparsity; + mcg->_trimFactor = trimFactor; return mcg; } @@ -164,7 +169,9 @@ static inline bool DECORATE_NAME(MinCoverGenerator_isLevelStored)(DECORATE_NAME( size_t maskedBits) { BSON_ASSERT_PARAM(mcg); size_t level = mcg->_maxlen - maskedBits; - return 0 == maskedBits || 0 == (level % mcg->_sparsity); + BSON_ASSERT(mc_in_range_size_t_signed(mcg->_trimFactor)); + size_t trimFactor_sz = (size_t)mcg->_trimFactor; + return 0 == maskedBits || (level >= trimFactor_sz && 0 == (level % mcg->_sparsity)); } char * @@ -219,6 +226,11 @@ static inline mc_mincover_t *DECORATE_NAME(MinCoverGenerator_minCover)(DECORATE_ return mc; } +static inline int32_t DECORATE_NAME(MinCoverGenerator_usedTrimFactor)(DECORATE_NAME(MinCoverGenerator) * mcg) { + BSON_ASSERT_PARAM(mcg); + return mcg->_trimFactor; +} + // adjustBounds increments *lowerBound if includeLowerBound is false and // decrements *upperBound if includeUpperBound is false. // lowerBound, min, upperBound, and max are expected to come from the result diff --git a/src/third_party/libmongocrypt/dist/src/mc-range-mincover-private.h b/src/third_party/libmongocrypt/dist/src/mc-range-mincover-private.h index 3e073b0d726..2129dff0895 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-range-mincover-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-range-mincover-private.h @@ -33,6 +33,9 @@ const char *mc_mincover_get(mc_mincover_t *mincover, size_t index); // mc_mincover_len returns the number of represented mincover. size_t mc_mincover_len(mc_mincover_t *mincover); +// Return the trimFactor that was used to generate this mincover. +int32_t mc_mincover_get_used_trimFactor(const mc_mincover_t *mincover); + // mc_mincover_destroys frees `mincover`. void mc_mincover_destroy(mc_mincover_t *mincover); @@ -44,12 +47,14 @@ typedef struct { mc_optional_int32_t min; mc_optional_int32_t max; size_t sparsity; + mc_optional_int32_t trimFactor; } mc_getMincoverInt32_args_t; // mc_getMincoverInt32 implements the Mincover Generation algorithm described in // SERVER-68600 for int32_t. mc_mincover_t *mc_getMincoverInt32(mc_getMincoverInt32_args_t args, - mongocrypt_status_t *status) MONGOCRYPT_WARN_UNUSED_RESULT; + mongocrypt_status_t *status, + bool use_range_v2) MONGOCRYPT_WARN_UNUSED_RESULT; typedef struct { int64_t lowerBound; @@ -59,12 +64,14 @@ typedef struct { mc_optional_int64_t min; mc_optional_int64_t max; size_t sparsity; + mc_optional_int32_t trimFactor; } mc_getMincoverInt64_args_t; // mc_getMincoverInt64 implements the Mincover Generation algorithm described in // SERVER-68600 for int64_t. mc_mincover_t *mc_getMincoverInt64(mc_getMincoverInt64_args_t args, - mongocrypt_status_t *status) MONGOCRYPT_WARN_UNUSED_RESULT; + mongocrypt_status_t *status, + bool use_range_v2) MONGOCRYPT_WARN_UNUSED_RESULT; typedef struct { double lowerBound; @@ -74,13 +81,15 @@ typedef struct { size_t sparsity; mc_optional_double_t min; mc_optional_double_t max; - mc_optional_uint32_t precision; + mc_optional_int32_t precision; + mc_optional_int32_t trimFactor; } mc_getMincoverDouble_args_t; // mc_getMincoverDouble implements the Mincover Generation algorithm described // in SERVER-68600 for double. mc_mincover_t *mc_getMincoverDouble(mc_getMincoverDouble_args_t args, - mongocrypt_status_t *status) MONGOCRYPT_WARN_UNUSED_RESULT; + mongocrypt_status_t *status, + bool use_range_v2) MONGOCRYPT_WARN_UNUSED_RESULT; #if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT typedef struct { @@ -90,13 +99,15 @@ typedef struct { bool includeUpperBound; size_t sparsity; mc_optional_dec128_t min, max; - mc_optional_uint32_t precision; + mc_optional_int32_t precision; + mc_optional_int32_t trimFactor; } mc_getMincoverDecimal128_args_t; // mc_getMincoverDecimal128 implements the Mincover Generation algorithm // described in SERVER-68600 for Decimal128 (as mc_dec128). mc_mincover_t *mc_getMincoverDecimal128(mc_getMincoverDecimal128_args_t args, - mongocrypt_status_t *status) MONGOCRYPT_WARN_UNUSED_RESULT; + mongocrypt_status_t *status, + bool use_range_v2) MONGOCRYPT_WARN_UNUSED_RESULT; #endif // MONGOCRYPT_HAVE_DECIMAL128_SUPPORT #endif /* MC_RANGE_MINCOVER_PRIVATE_H */ diff --git a/src/third_party/libmongocrypt/dist/src/mc-range-mincover.c b/src/third_party/libmongocrypt/dist/src/mc-range-mincover.c index 514d24cfdbb..3e8d8d471bd 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-range-mincover.c +++ b/src/third_party/libmongocrypt/dist/src/mc-range-mincover.c @@ -19,14 +19,16 @@ #include "mc-check-conversions-private.h" #include "mc-array-private.h" +#include "mc-cmp-private.h" #include "mc-range-edge-generation-private.h" // mc_count_leading_zeros_u32 -#include "mc-range-encoding-private.h" // mc_getTypeInfo32 +#include "mc-range-encoding-private.h" // mc_getTypeInfo32, trimFactorDefault #include "mc-range-mincover-private.h" #include "mongocrypt-private.h" struct _mc_mincover_t { /* mincover is an array of `char*` edge strings. */ mc_array_t mincover; + int32_t usedTrimFactor; // The `trimFactor` that was used to produce this mincover. }; static mc_mincover_t *mc_mincover_new(void) { @@ -48,6 +50,10 @@ size_t mc_mincover_len(mc_mincover_t *mincover) { return mincover->mincover.len; } +int32_t mc_mincover_get_used_trimFactor(const mc_mincover_t *mincover) { + return mincover->usedTrimFactor; +} + void mc_mincover_destroy(mc_mincover_t *mincover) { if (NULL == mincover) { return; @@ -127,7 +133,7 @@ void mc_mincover_destroy(mc_mincover_t *mincover) { } else \ (void)0 -mc_mincover_t *mc_getMincoverInt32(mc_getMincoverInt32_args_t args, mongocrypt_status_t *status) { +mc_mincover_t *mc_getMincoverInt32(mc_getMincoverInt32_args_t args, mongocrypt_status_t *status, bool use_range_v2) { BSON_ASSERT_PARAM(status); CHECK_BOUNDS(args, PRId32, IDENTITY, LESSTHAN); mc_OSTType_Int32 a, b; @@ -149,16 +155,18 @@ mc_mincover_t *mc_getMincoverInt32(mc_getMincoverInt32_args_t args, mongocrypt_s return NULL; } - MinCoverGenerator_u32 *mcg = MinCoverGenerator_new_u32(a.value, b.value, a.max, args.sparsity, status); + MinCoverGenerator_u32 *mcg = + MinCoverGenerator_new_u32(a.value, b.value, a.max, args.sparsity, args.trimFactor, status, use_range_v2); if (!mcg) { return NULL; } mc_mincover_t *mc = MinCoverGenerator_minCover_u32(mcg); + mc->usedTrimFactor = MinCoverGenerator_usedTrimFactor_u32(mcg); MinCoverGenerator_destroy_u32(mcg); return mc; } -mc_mincover_t *mc_getMincoverInt64(mc_getMincoverInt64_args_t args, mongocrypt_status_t *status) { +mc_mincover_t *mc_getMincoverInt64(mc_getMincoverInt64_args_t args, mongocrypt_status_t *status, bool use_range_v2) { BSON_ASSERT_PARAM(status); CHECK_BOUNDS(args, PRId64, IDENTITY, LESSTHAN); mc_OSTType_Int64 a, b; @@ -180,18 +188,20 @@ mc_mincover_t *mc_getMincoverInt64(mc_getMincoverInt64_args_t args, mongocrypt_s return NULL; } - MinCoverGenerator_u64 *mcg = MinCoverGenerator_new_u64(a.value, b.value, a.max, args.sparsity, status); + MinCoverGenerator_u64 *mcg = + MinCoverGenerator_new_u64(a.value, b.value, a.max, args.sparsity, args.trimFactor, status, use_range_v2); if (!mcg) { return NULL; } mc_mincover_t *mc = MinCoverGenerator_minCover_u64(mcg); + mc->usedTrimFactor = MinCoverGenerator_usedTrimFactor_u64(mcg); MinCoverGenerator_destroy_u64(mcg); return mc; } // mc_getMincoverDouble implements the Mincover Generation algorithm described // in SERVER-68600 for double. -mc_mincover_t *mc_getMincoverDouble(mc_getMincoverDouble_args_t args, mongocrypt_status_t *status) { +mc_mincover_t *mc_getMincoverDouble(mc_getMincoverDouble_args_t args, mongocrypt_status_t *status, bool use_range_v2) { BSON_ASSERT_PARAM(status); CHECK_BOUNDS(args, "g", IDENTITY, LESSTHAN); @@ -201,7 +211,8 @@ mc_mincover_t *mc_getMincoverDouble(mc_getMincoverDouble_args_t args, mongocrypt .max = args.max, .precision = args.precision}, &a, - status)) { + status, + use_range_v2)) { return NULL; } if (!mc_getTypeInfoDouble((mc_getTypeInfoDouble_args_t){.value = args.upperBound, @@ -209,7 +220,8 @@ mc_mincover_t *mc_getMincoverDouble(mc_getMincoverDouble_args_t args, mongocrypt .max = args.max, .precision = args.precision}, &b, - status)) { + status, + use_range_v2)) { return NULL; } @@ -220,17 +232,20 @@ mc_mincover_t *mc_getMincoverDouble(mc_getMincoverDouble_args_t args, mongocrypt return NULL; } - MinCoverGenerator_u64 *mcg = MinCoverGenerator_new_u64(a.value, b.value, a.max, args.sparsity, status); + MinCoverGenerator_u64 *mcg = + MinCoverGenerator_new_u64(a.value, b.value, a.max, args.sparsity, args.trimFactor, status, use_range_v2); if (!mcg) { return NULL; } mc_mincover_t *mc = MinCoverGenerator_minCover_u64(mcg); + mc->usedTrimFactor = MinCoverGenerator_usedTrimFactor_u64(mcg); MinCoverGenerator_destroy_u64(mcg); return mc; } #if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT -mc_mincover_t *mc_getMincoverDecimal128(mc_getMincoverDecimal128_args_t args, mongocrypt_status_t *status) { +mc_mincover_t * +mc_getMincoverDecimal128(mc_getMincoverDecimal128_args_t args, mongocrypt_status_t *status, bool use_range_v2) { BSON_ASSERT_PARAM(status); #define ToString(Dec) (mc_dec128_to_string(Dec).str) CHECK_BOUNDS(args, "s", ToString, mc_dec128_less); @@ -241,7 +256,8 @@ mc_mincover_t *mc_getMincoverDecimal128(mc_getMincoverDecimal128_args_t args, mo .max = args.max, .precision = args.precision}, &a, - status)) { + status, + use_range_v2)) { return NULL; } if (!mc_getTypeInfoDecimal128((mc_getTypeInfoDecimal128_args_t){.value = args.upperBound, @@ -249,7 +265,8 @@ mc_mincover_t *mc_getMincoverDecimal128(mc_getMincoverDecimal128_args_t args, mo .max = args.max, .precision = args.precision}, &b, - status)) { + status, + use_range_v2)) { return NULL; } @@ -260,11 +277,13 @@ mc_mincover_t *mc_getMincoverDecimal128(mc_getMincoverDecimal128_args_t args, mo return NULL; } - MinCoverGenerator_u128 *mcg = MinCoverGenerator_new_u128(a.value, b.value, a.max, args.sparsity, status); + MinCoverGenerator_u128 *mcg = + MinCoverGenerator_new_u128(a.value, b.value, a.max, args.sparsity, args.trimFactor, status, use_range_v2); if (!mcg) { return NULL; } mc_mincover_t *mc = MinCoverGenerator_minCover_u128(mcg); + mc->usedTrimFactor = MinCoverGenerator_usedTrimFactor_u128(mcg); MinCoverGenerator_destroy_u128(mcg); return mc; } diff --git a/src/third_party/libmongocrypt/dist/src/mc-rangeopts-private.h b/src/third_party/libmongocrypt/dist/src/mc-rangeopts-private.h index 0df6725c870..80f4c807c71 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-rangeopts-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-rangeopts-private.h @@ -36,19 +36,26 @@ typedef struct { } max; int64_t sparsity; - mc_optional_uint32_t precision; + mc_optional_int32_t precision; + mc_optional_int32_t trimFactor; } mc_RangeOpts_t; +// `mc_RangeOpts_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof_mc_RangeOpts_t, + BSON_ALIGNOF(mc_RangeOpts_t) >= BSON_MAX(BSON_ALIGNOF(bson_t), BSON_ALIGNOF(bson_iter_t))); + /* mc_RangeOpts_parse parses a BSON document into mc_RangeOpts_t. * The document is expected to have the form: * { * "min": BSON value, * "max": BSON value, - * "sparsity": Int64, - * "precision": Optional + * "sparsity": Optional, + * "precision": Optional, + * "trimFactor": Optional, * } */ -bool mc_RangeOpts_parse(mc_RangeOpts_t *ro, const bson_t *in, mongocrypt_status_t *status); +bool mc_RangeOpts_parse(mc_RangeOpts_t *ro, const bson_t *in, bool use_range_v2, mongocrypt_status_t *status); /* * mc_RangeOpts_to_FLE2RangeInsertSpec creates a placeholder value to be @@ -69,6 +76,7 @@ bool mc_RangeOpts_parse(mc_RangeOpts_t *ro, const bson_t *in, mongocrypt_status_ bool mc_RangeOpts_to_FLE2RangeInsertSpec(const mc_RangeOpts_t *ro, const bson_t *v, bson_t *out, + bool use_range_v2, mongocrypt_status_t *status); /* mc_RangeOpts_appendMin appends the minimum value of the range for a given @@ -89,6 +97,16 @@ bool mc_RangeOpts_appendMax(const mc_RangeOpts_t *ro, bson_t *out, mongocrypt_status_t *status); +/* mc_RangeOpts_appendTrimFactor appends the trim factor of the field. If `ro->trimFactor` is unset, + * defaults to 0. Errors if `ro->trimFactor` is out of bounds based on the size of the domain + * computed from `valueType`, `ro->min` and `ro->max`. */ +bool mc_RangeOpts_appendTrimFactor(const mc_RangeOpts_t *ro, + bson_type_t valueType, + const char *fieldName, + bson_t *out, + mongocrypt_status_t *status, + bool use_range_v2); + void mc_RangeOpts_cleanup(mc_RangeOpts_t *ro); #endif // MC_RANGEOPTS_PRIVATE_H diff --git a/src/third_party/libmongocrypt/dist/src/mc-rangeopts.c b/src/third_party/libmongocrypt/dist/src/mc-rangeopts.c index 5de8df50785..729ef49384e 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-rangeopts.c +++ b/src/third_party/libmongocrypt/dist/src/mc-rangeopts.c @@ -17,16 +17,22 @@ #include "mc-rangeopts-private.h" #include "mc-check-conversions-private.h" +#include "mc-cmp-private.h" +#include "mc-range-edge-generation-private.h" // mc_count_leading_zeros_XX +#include "mc-range-encoding-private.h" // mc_getTypeInfoXX #include "mongocrypt-private.h" #include "mongocrypt-util-private.h" // mc_bson_type_to_string #include // DBL_MAX +#define CLIENT_ERR_PREFIXED_HELPER(Prefix, ErrorString, ...) CLIENT_ERR(Prefix ": " ErrorString, ##__VA_ARGS__) +#define CLIENT_ERR_PREFIXED(ErrorString, ...) CLIENT_ERR_PREFIXED_HELPER(ERROR_PREFIX, ErrorString, ##__VA_ARGS__) + // Common logic for testing field name, tracking duplication, and presence. -#define IF_FIELD(Name, ErrorPrefix) \ +#define IF_FIELD(Name) \ if (0 == strcmp(field, #Name)) { \ if (has_##Name) { \ - CLIENT_ERR("%sUnexpected duplicate field '" #Name "'", ErrorPrefix); \ + CLIENT_ERR_PREFIXED("Unexpected duplicate field '" #Name "'"); \ return false; \ } \ has_##Name = true; @@ -35,17 +41,17 @@ continue; \ } -#define CHECK_HAS(Name, ErrorPrefix) \ +#define CHECK_HAS(Name) \ if (!has_##Name) { \ - CLIENT_ERR("%sMissing field '" #Name "'", ErrorPrefix); \ + CLIENT_ERR_PREFIXED("Missing field '" #Name "'"); \ return false; \ } -bool mc_RangeOpts_parse(mc_RangeOpts_t *ro, const bson_t *in, mongocrypt_status_t *status) { - bson_iter_t iter; - bool has_min = false, has_max = false, has_sparsity = false, has_precision = false; - const char *const error_prefix = "Error parsing RangeOpts: "; +#define ERROR_PREFIX "Error parsing RangeOpts" +bool mc_RangeOpts_parse(mc_RangeOpts_t *ro, const bson_t *in, bool use_range_v2, mongocrypt_status_t *status) { + bson_iter_t iter = {0}; + bool has_min = false, has_max = false, has_sparsity = false, has_precision = false, has_trimFactor = false; BSON_ASSERT_PARAM(ro); BSON_ASSERT_PARAM(in); BSON_ASSERT(status || true); @@ -54,7 +60,7 @@ bool mc_RangeOpts_parse(mc_RangeOpts_t *ro, const bson_t *in, mongocrypt_status_ ro->bson = bson_copy(in); if (!bson_iter_init(&iter, ro->bson)) { - CLIENT_ERR("%sInvalid BSON", error_prefix); + CLIENT_ERR_PREFIXED("Invalid BSON"); return false; } @@ -62,74 +68,91 @@ bool mc_RangeOpts_parse(mc_RangeOpts_t *ro, const bson_t *in, mongocrypt_status_ const char *field = bson_iter_key(&iter); BSON_ASSERT(field); - IF_FIELD(min, error_prefix) + IF_FIELD(min) ro->min.set = true; ro->min.value = iter; END_IF_FIELD - IF_FIELD(max, error_prefix) + IF_FIELD(max) ro->max.set = true; ro->max.value = iter; END_IF_FIELD - IF_FIELD(sparsity, error_prefix) + IF_FIELD(sparsity) if (!BSON_ITER_HOLDS_INT64(&iter)) { - CLIENT_ERR("%sExpected int64 for sparsity, got: %s", - error_prefix, - mc_bson_type_to_string(bson_iter_type(&iter))); + CLIENT_ERR_PREFIXED("Expected int64 for sparsity, got: %s", mc_bson_type_to_string(bson_iter_type(&iter))); return false; }; ro->sparsity = bson_iter_int64(&iter); END_IF_FIELD - IF_FIELD(precision, error_prefix) { + IF_FIELD(precision) { if (!BSON_ITER_HOLDS_INT32(&iter)) { - CLIENT_ERR("%s'precision' must be an int32", error_prefix); + CLIENT_ERR_PREFIXED("'precision' must be an int32"); return false; } int32_t val = bson_iter_int32(&iter); if (val < 0) { - CLIENT_ERR("%s'precision' must be non-negative", error_prefix); + CLIENT_ERR_PREFIXED("'precision' must be non-negative"); return false; } - ro->precision = OPT_U32((uint32_t)val); + ro->precision = OPT_I32(val); } END_IF_FIELD - CLIENT_ERR("%sUnrecognized field: '%s'", error_prefix, field); + IF_FIELD(trimFactor) { + if (!BSON_ITER_HOLDS_INT32(&iter)) { + CLIENT_ERR_PREFIXED("Expected int32 for trimFactor, got: %s", + mc_bson_type_to_string(bson_iter_type(&iter))); + return false; + }; + int32_t val = bson_iter_int32(&iter); + if (val < 0) { + CLIENT_ERR_PREFIXED("'trimFactor' must be non-negative"); + return false; + } + ro->trimFactor = OPT_I32(val); + } + END_IF_FIELD + + CLIENT_ERR_PREFIXED("Unrecognized field: '%s'", field); return false; } // Do not error if min/max are not present. min/max are optional. - CHECK_HAS(sparsity, error_prefix); // Do not error if precision is not present. Precision is optional and only // applies to double/decimal128. + // Do not error if trimFactor is not present. It is optional. + + if (!has_sparsity && use_range_v2) { + ro->sparsity = mc_FLERangeSparsityDefault; + } // Expect precision only to be set for double or decimal128. if (has_precision) { if (!ro->min.set) { - CLIENT_ERR("setting precision requires min"); + CLIENT_ERR_PREFIXED("setting precision requires min"); return false; } bson_type_t minType = bson_iter_type(&ro->min.value); if (minType != BSON_TYPE_DOUBLE && minType != BSON_TYPE_DECIMAL128) { - CLIENT_ERR("expected 'precision' to be set with double or decimal128 " - "index, but got: %s min", - mc_bson_type_to_string(minType)); + CLIENT_ERR_PREFIXED("expected 'precision' to be set with double or decimal128 " + "index, but got: %s min", + mc_bson_type_to_string(minType)); return false; } if (!ro->max.set) { - CLIENT_ERR("setting precision requires max"); + CLIENT_ERR_PREFIXED("setting precision requires max"); return false; } bson_type_t maxType = bson_iter_type(&ro->max.value); if (maxType != BSON_TYPE_DOUBLE && maxType != BSON_TYPE_DECIMAL128) { - CLIENT_ERR("expected 'precision' to be set with double or decimal128 " - "index, but got: %s max", - mc_bson_type_to_string(maxType)); + CLIENT_ERR_PREFIXED("expected 'precision' to be set with double or decimal128 " + "index, but got: %s max", + mc_bson_type_to_string(maxType)); return false; } } @@ -138,10 +161,10 @@ bool mc_RangeOpts_parse(mc_RangeOpts_t *ro, const bson_t *in, mongocrypt_status_ if (ro->min.set && ro->max.set) { bson_type_t minType = bson_iter_type(&ro->min.value), maxType = bson_iter_type(&ro->max.value); if (minType != maxType) { - CLIENT_ERR("expected 'min' and 'max' to be same type, but got: %s " - "min and %s max", - mc_bson_type_to_string(minType), - mc_bson_type_to_string(maxType)); + CLIENT_ERR_PREFIXED("expected 'min' and 'max' to be same type, but got: %s " + "min and %s max", + mc_bson_type_to_string(minType), + mc_bson_type_to_string(maxType)); return false; } } @@ -152,7 +175,8 @@ bool mc_RangeOpts_parse(mc_RangeOpts_t *ro, const bson_t *in, mongocrypt_status_ bson_type_t minType = bson_iter_type(&ro->min.value); if (minType == BSON_TYPE_DOUBLE || minType == BSON_TYPE_DECIMAL128) { if (!has_precision) { - CLIENT_ERR("expected 'precision' to be set with 'min' for %s", mc_bson_type_to_string(minType)); + CLIENT_ERR_PREFIXED("expected 'precision' to be set with 'min' for %s", + mc_bson_type_to_string(minType)); return false; } } @@ -162,39 +186,53 @@ bool mc_RangeOpts_parse(mc_RangeOpts_t *ro, const bson_t *in, mongocrypt_status_ bson_type_t maxType = bson_iter_type(&ro->max.value); if (maxType == BSON_TYPE_DOUBLE || maxType == BSON_TYPE_DECIMAL128) { if (!has_precision) { - CLIENT_ERR("expected 'precision' to be set with 'max' for %s", mc_bson_type_to_string(maxType)); + CLIENT_ERR_PREFIXED("expected 'precision' to be set with 'max' for %s", + mc_bson_type_to_string(maxType)); return false; } } } } + if (ro->trimFactor.set) { + if (!use_range_v2) { + // Once `use_range_v2` is default true, this block may be removed. + CLIENT_ERR_PREFIXED("'trimFactor' is not supported for QE range v1"); + return false; + } + // At this point, we do not know the type of the field if min and max are unspecified. Wait to + // validate the value of trimFactor. + } + return true; } +#undef ERROR_PREFIX +#define ERROR_PREFIX "Error making FLE2RangeInsertSpec" + bool mc_RangeOpts_to_FLE2RangeInsertSpec(const mc_RangeOpts_t *ro, const bson_t *v, bson_t *out, + bool use_range_v2, mongocrypt_status_t *status) { BSON_ASSERT_PARAM(ro); BSON_ASSERT_PARAM(v); BSON_ASSERT_PARAM(out); BSON_ASSERT(status || true); - const char *const error_prefix = "Error making FLE2RangeInsertSpec: "; bson_iter_t v_iter; if (!bson_iter_init_find(&v_iter, v, "v")) { - CLIENT_ERR("Unable to find 'v' in input"); + CLIENT_ERR_PREFIXED("Unable to find 'v' in input"); return false; } bson_t child; if (!BSON_APPEND_DOCUMENT_BEGIN(out, "v", &child)) { - CLIENT_ERR("%sError appending to BSON", error_prefix); + CLIENT_ERR_PREFIXED("Error appending to BSON"); return false; } if (!bson_append_iter(&child, "v", 1, &v_iter)) { - CLIENT_ERR("%sError appending to BSON", error_prefix); + CLIENT_ERR_PREFIXED("Error appending to BSON"); return false; } @@ -209,17 +247,26 @@ bool mc_RangeOpts_to_FLE2RangeInsertSpec(const mc_RangeOpts_t *ro, if (ro->precision.set) { BSON_ASSERT(ro->precision.value <= INT32_MAX); if (!BSON_APPEND_INT32(&child, "precision", (int32_t)ro->precision.value)) { - CLIENT_ERR("%sError appending to BSON", error_prefix); + CLIENT_ERR_PREFIXED("Error appending to BSON"); + return false; + } + } + + if (use_range_v2) { + if (!mc_RangeOpts_appendTrimFactor(ro, bson_iter_type(&v_iter), "trimFactor", &child, status, use_range_v2)) { return false; } } if (!bson_append_document_end(out, &child)) { - CLIENT_ERR("%sError appending to BSON", error_prefix); + CLIENT_ERR_PREFIXED("Error appending to BSON"); return false; } return true; } +#undef ERROR_PREFIX +#define ERROR_PREFIX "Error appending min to FLE2RangeInsertSpec" + bool mc_RangeOpts_appendMin(const mc_RangeOpts_t *ro, bson_type_t valueType, const char *fieldName, @@ -232,46 +279,49 @@ bool mc_RangeOpts_appendMin(const mc_RangeOpts_t *ro, if (ro->min.set) { if (bson_iter_type(&ro->min.value) != valueType) { - CLIENT_ERR("expected matching 'min' and value type. Got range option " - "'min' of type %s and value of type %s", - mc_bson_type_to_string(bson_iter_type(&ro->min.value)), - mc_bson_type_to_string(valueType)); + CLIENT_ERR_PREFIXED("expected matching 'min' and value type. Got range option " + "'min' of type %s and value of type %s", + mc_bson_type_to_string(bson_iter_type(&ro->min.value)), + mc_bson_type_to_string(valueType)); return false; } if (!bson_append_iter(out, fieldName, -1, &ro->min.value)) { - CLIENT_ERR("failed to append BSON"); + CLIENT_ERR_PREFIXED("failed to append BSON"); return false; } return true; } if (valueType == BSON_TYPE_INT32 || valueType == BSON_TYPE_INT64 || valueType == BSON_TYPE_DATE_TIME) { - CLIENT_ERR("Range option 'min' is required for type: %s", mc_bson_type_to_string(valueType)); + CLIENT_ERR_PREFIXED("Range option 'min' is required for type: %s", mc_bson_type_to_string(valueType)); return false; } else if (valueType == BSON_TYPE_DOUBLE) { if (!BSON_APPEND_DOUBLE(out, fieldName, -DBL_MAX)) { - CLIENT_ERR("failed to append BSON"); + CLIENT_ERR_PREFIXED("failed to append BSON"); return false; } } else if (valueType == BSON_TYPE_DECIMAL128) { #if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT const bson_decimal128_t min = mc_dec128_to_bson_decimal128(MC_DEC128_LARGEST_NEGATIVE); if (!BSON_APPEND_DECIMAL128(out, fieldName, &min)) { - CLIENT_ERR("failed to append BSON"); + CLIENT_ERR_PREFIXED("failed to append BSON"); return false; } #else // ↑↑↑↑↑↑↑↑ With Decimal128 / Without ↓↓↓↓↓↓↓↓↓↓ - CLIENT_ERR("unsupported BSON type (Decimal128) for range: libmongocrypt " - "was built without extended Decimal128 support"); + CLIENT_ERR_PREFIXED("unsupported BSON type (Decimal128) for range: libmongocrypt " + "was built without extended Decimal128 support"); return false; #endif // MONGOCRYPT_HAVE_DECIMAL128_SUPPORT } else { - CLIENT_ERR("unsupported BSON type: %s for range", mc_bson_type_to_string(valueType)); + CLIENT_ERR_PREFIXED("unsupported BSON type: %s for range", mc_bson_type_to_string(valueType)); return false; } return true; } +#undef ERROR_PREFIX +#define ERROR_PREFIX "Error appending max to FLE2RangeInsertSpec" + bool mc_RangeOpts_appendMax(const mc_RangeOpts_t *ro, bson_type_t valueType, const char *fieldName, @@ -284,46 +334,193 @@ bool mc_RangeOpts_appendMax(const mc_RangeOpts_t *ro, if (ro->max.set) { if (bson_iter_type(&ro->max.value) != valueType) { - CLIENT_ERR("expected matching 'max' and value type. Got range option " - "'max' of type %s and value of type %s", - mc_bson_type_to_string(bson_iter_type(&ro->max.value)), - mc_bson_type_to_string(valueType)); + CLIENT_ERR_PREFIXED("expected matching 'max' and value type. Got range option " + "'max' of type %s and value of type %s", + mc_bson_type_to_string(bson_iter_type(&ro->max.value)), + mc_bson_type_to_string(valueType)); return false; } if (!bson_append_iter(out, fieldName, -1, &ro->max.value)) { - CLIENT_ERR("failed to append BSON"); + CLIENT_ERR_PREFIXED("failed to append BSON"); return false; } return true; } if (valueType == BSON_TYPE_INT32 || valueType == BSON_TYPE_INT64 || valueType == BSON_TYPE_DATE_TIME) { - CLIENT_ERR("Range option 'max' is required for type: %s", mc_bson_type_to_string(valueType)); + CLIENT_ERR_PREFIXED("Range option 'max' is required for type: %s", mc_bson_type_to_string(valueType)); return false; } else if (valueType == BSON_TYPE_DOUBLE) { if (!BSON_APPEND_DOUBLE(out, fieldName, DBL_MAX)) { - CLIENT_ERR("failed to append BSON"); + CLIENT_ERR_PREFIXED("failed to append BSON"); return false; } } else if (valueType == BSON_TYPE_DECIMAL128) { #if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT const bson_decimal128_t max = mc_dec128_to_bson_decimal128(MC_DEC128_LARGEST_POSITIVE); if (!BSON_APPEND_DECIMAL128(out, fieldName, &max)) { - CLIENT_ERR("failed to append BSON"); + CLIENT_ERR_PREFIXED("failed to append BSON"); return false; } #else // ↑↑↑↑↑↑↑↑ With Decimal128 / Without ↓↓↓↓↓↓↓↓↓↓ - CLIENT_ERR("unsupported BSON type (Decimal128) for range: libmongocrypt " - "was built without extended Decimal128 support"); + CLIENT_ERR_PREFIXED("unsupported BSON type (Decimal128) for range: libmongocrypt " + "was built without extended Decimal128 support"); return false; #endif // MONGOCRYPT_HAVE_DECIMAL128_SUPPORT } else { - CLIENT_ERR("unsupported BSON type: %s for range", mc_bson_type_to_string(valueType)); + CLIENT_ERR_PREFIXED("unsupported BSON type: %s for range", mc_bson_type_to_string(valueType)); return false; } return true; } +#undef ERROR_PREFIX +#define ERROR_PREFIX "Error in getNumberOfBits" + +// Used to calculate max trim factor. Returns the number of bits required to represent any number in +// the domain. +bool mc_getNumberOfBits(const mc_RangeOpts_t *ro, + bson_type_t valueType, + uint32_t *bitsOut, + mongocrypt_status_t *status, + bool use_range_v2) { + BSON_ASSERT_PARAM(ro); + BSON_ASSERT_PARAM(bitsOut); + + // For each type, we use getTypeInfo to get the total number of values in the domain (-1) + // which tells us how many bits are needed to represent the whole domain. + // note - can't use a switch statement because of -Werror=switch-enum + if (valueType == BSON_TYPE_INT32) { + int32_t value = 0; + mc_optional_int32_t rmin = {false, 0}, rmax = {false, 0}; + if (ro->min.set) { + BSON_ASSERT(ro->max.set); + value = bson_iter_int32(&ro->min.value); + rmin = OPT_I32(value); + rmax = OPT_I32(bson_iter_int32(&ro->max.value)); + } + mc_getTypeInfo32_args_t args = {value, rmin, rmax}; + mc_OSTType_Int32 out; + if (!mc_getTypeInfo32(args, &out, status)) { + return false; + } + *bitsOut = 32 - (uint32_t)mc_count_leading_zeros_u32(out.max); + return true; + } else if (valueType == BSON_TYPE_INT64) { + int64_t value = 0; + mc_optional_int64_t rmin = {false, 0}, rmax = {false, 0}; + if (ro->min.set) { + BSON_ASSERT(ro->max.set); + value = bson_iter_int64(&ro->min.value); + rmin = OPT_I64(value); + rmax = OPT_I64(bson_iter_int64(&ro->max.value)); + } + mc_getTypeInfo64_args_t args = {value, rmin, rmax}; + mc_OSTType_Int64 out; + if (!mc_getTypeInfo64(args, &out, status)) { + return false; + } + *bitsOut = 64 - (uint32_t)mc_count_leading_zeros_u64(out.max); + return true; + } else if (valueType == BSON_TYPE_DATE_TIME) { + int64_t value = 0; + mc_optional_int64_t rmin = {false, 0}, rmax = {false, 0}; + if (ro->min.set) { + BSON_ASSERT(ro->max.set); + value = bson_iter_date_time(&ro->min.value); + rmin = OPT_I64(value); + rmax = OPT_I64(bson_iter_date_time(&ro->max.value)); + } + mc_getTypeInfo64_args_t args = {value, rmin, rmax}; + mc_OSTType_Int64 out; + if (!mc_getTypeInfo64(args, &out, status)) { + return false; + } + *bitsOut = 64 - (uint32_t)mc_count_leading_zeros_u64(out.max); + return true; + } else if (valueType == BSON_TYPE_DOUBLE) { + double value = 0; + mc_optional_double_t rmin = {false, 0}, rmax = {false, 0}; + mc_optional_int32_t prec = ro->precision; + if (ro->min.set) { + BSON_ASSERT(ro->max.set); + value = bson_iter_double(&ro->min.value); + rmin = OPT_DOUBLE(value); + rmax = OPT_DOUBLE(bson_iter_double(&ro->max.value)); + } + mc_getTypeInfoDouble_args_t args = {value, rmin, rmax, prec}; + mc_OSTType_Double out; + if (!mc_getTypeInfoDouble(args, &out, status, use_range_v2)) { + return false; + } + *bitsOut = 64 - (uint32_t)mc_count_leading_zeros_u64(out.max); + return true; + } +#if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT + else if (valueType == BSON_TYPE_DECIMAL128) { + mc_dec128 value = MC_DEC128_ZERO; + mc_optional_dec128_t rmin = {false, MC_DEC128_ZERO}, rmax = {false, MC_DEC128_ZERO}; + mc_optional_int32_t prec = ro->precision; + if (ro->min.set) { + BSON_ASSERT(ro->max.set); + value = mc_dec128_from_bson_iter(&ro->min.value); + rmin = OPT_MC_DEC128(value); + rmax = OPT_MC_DEC128(mc_dec128_from_bson_iter(&ro->max.value)); + } + mc_getTypeInfoDecimal128_args_t args = {value, rmin, rmax, prec}; + mc_OSTType_Decimal128 out; + if (!mc_getTypeInfoDecimal128(args, &out, status, use_range_v2)) { + return false; + } + *bitsOut = 128 - (uint32_t)mc_count_leading_zeros_u128(out.max); + return true; + } +#endif + CLIENT_ERR_PREFIXED("unsupported BSON type: %s for range", mc_bson_type_to_string(valueType)); + return false; +} + +#undef ERROR_PREFIX +#define ERROR_PREFIX "Error appending trim factor to FLE2RangeInsertSpec" + +bool mc_RangeOpts_appendTrimFactor(const mc_RangeOpts_t *ro, + bson_type_t valueType, + const char *fieldName, + bson_t *out, + mongocrypt_status_t *status, + bool use_range_v2) { + BSON_ASSERT_PARAM(ro); + BSON_ASSERT_PARAM(fieldName); + BSON_ASSERT_PARAM(out); + BSON_ASSERT(status || true); + + if (!ro->trimFactor.set) { + // A default `trimFactor` will be selected later with `trimFactorDefault` + return true; + } + + uint32_t nbits; + if (!mc_getNumberOfBits(ro, valueType, &nbits, status, use_range_v2)) { + return false; + } + // if nbits = 0, we want to allow trim factor = 0. + uint32_t test = nbits ? nbits : 1; + if (mc_cmp_greater_equal_su(ro->trimFactor.value, test)) { + CLIENT_ERR_PREFIXED("Trim factor (%d) must be less than the total number of bits (%d) used to represent " + "any element in the domain.", + ro->trimFactor.value, + nbits); + return false; + } + if (!BSON_APPEND_INT32(out, fieldName, ro->trimFactor.value)) { + CLIENT_ERR_PREFIXED("failed to append BSON"); + return false; + } + return true; +} + +#undef ERROR_PREFIX + void mc_RangeOpts_cleanup(mc_RangeOpts_t *ro) { if (!ro) { return; diff --git a/src/third_party/libmongocrypt/dist/src/mc-tokens-private.h b/src/third_party/libmongocrypt/dist/src/mc-tokens-private.h index 2812a9cf2ca..e2aad7c5039 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-tokens-private.h +++ b/src/third_party/libmongocrypt/dist/src/mc-tokens-private.h @@ -24,7 +24,7 @@ * * v is a BSON value. It is the bytes after "e_name" in "element" in * https://bsonspec.org/spec.html. - * u is a "contention factor" counter. It is a uint64_t. + * u is a "contention factor". It is a uint64_t. * HMAC is the HMAC-SHA-256 function. * Integers are represented as uint64_t in little-endian. * @@ -40,11 +40,21 @@ * ESCDerivedFromDataToken = HMAC(ESCToken, v) * ECCDerivedFromDataToken = HMAC(ECCToken, v) * - * EDCDerivedFromDataTokenAndCounter = HMAC(EDCDerivedFromDataToken, u) - * ESCDerivedFromDataTokenAndCounter = HMAC(ESCDerivedFromDataToken, u) - * ECCDerivedFromDataTokenAndCounter = HMAC(ECCDerivedFromDataToken, u) + * EDCDerivedFromDataTokenAndContentionFactor = HMAC(EDCDerivedFromDataToken, u) + * ESCDerivedFromDataTokenAndContentionFactor = HMAC(ESCDerivedFromDataToken, u) + * ECCDerivedFromDataTokenAndContentionFactor = HMAC(ECCDerivedFromDataToken, u) + * + * EDCTwiceDerivedToken = HMAC(EDCDerivedFromDataTokenAndContentionFactor, 1) + + * ESCTwiceDerivedTagToken = HMAC(ESCDerivedFromDataTokenAndContentionFactor, 1) + * ESCTwiceDerivedValueToken = HMAC(ESCDerivedFromDataTokenAndContentionFactor, 2) + + * ECCTwiceDerivedTagToken = HMAC(ECCDerivedFromDataTokenAndContentionFactor, 1) + * ECCTwiceDerivedValueToken = HMAC(ECCDerivedFromDataTokenAndContentionFactor, 2) * * Note: ECC related tokens are used in FLE2v1 only. + * Further, ECCTwiceDerivedValue(Tag|Token) have been omitted entirely. + * The above comment describing derivation is for doc purposes only. * ---------------------------------------------------------------------------- * Added in FLE2v2: * @@ -54,38 +64,35 @@ * ServerCountAndContentionFactorEncryptionToken = * HMAC(ServerDerivedFromDataToken, 1) * ServerZerosEncryptionToken = HMAC(ServerDerivedFromDataToken, 2) + * ---------------------------------------------------------------------------- + * Added in Range V2: + * + * d is a 17-byte blob of zeros. + * + * AnchorPaddingTokenRoot = HMAC(ESCToken, d) + * Server-side: + * AnchorPaddingTokenKey = HMAC(AnchorPaddingTokenRoot, 1) + * AnchorPaddingTokenValue = HMAC(AnchorPaddingTokenRoot, 2) * ======================== End: FLE 2 Token Reference ======================== */ -// TODO: replace `CONCAT` with `BSON_CONCAT` after libbson dependency is -// upgraded to 1.20.0 or higher. -#ifndef CONCAT -#define CONCAT_1(a, b) a##b -#define CONCAT(a, b) CONCAT_1(a, b) -#endif -// TODO: replace `CONCAT3` with `BSON_CONCAT3` after libbson dependency is -// upgraded to 1.20.0 or higher. -#ifndef CONCAT3 -#define CONCAT3(a, b, c) CONCAT(a, CONCAT(b, c)) -#endif - /// Declare a token type named 'Name', with constructor parameters given by the /// remaining arguments. Each constructor will also have the implicit first /// argument '_mongocrypt_crypto_t* crypto' and a final argument /// 'mongocrypt_status_t* status' -#define DECL_TOKEN_TYPE(Name, ...) DECL_TOKEN_TYPE_1(Name, CONCAT(Name, _t), __VA_ARGS__) +#define DECL_TOKEN_TYPE(Name, ...) DECL_TOKEN_TYPE_1(Name, BSON_CONCAT(Name, _t), __VA_ARGS__) #define DECL_TOKEN_TYPE_1(Prefix, T, ...) \ /* Opaque typedef the struct */ \ typedef struct T T; \ /* Data-getter */ \ - extern const _mongocrypt_buffer_t *CONCAT(Prefix, _get)(const T *t); \ + extern const _mongocrypt_buffer_t *BSON_CONCAT(Prefix, _get)(const T *t); \ /* Destructor */ \ - extern void CONCAT(Prefix, _destroy)(T * t); \ + extern void BSON_CONCAT(Prefix, _destroy)(T * t); \ /* Constructor for server to create tokens from raw buffer */ \ - extern T *CONCAT(Prefix, _new_from_buffer)(_mongocrypt_buffer_t * buf); \ + extern T *BSON_CONCAT(Prefix, _new_from_buffer)(_mongocrypt_buffer_t * buf); \ /* Constructor. Parameter list given as variadic args */ \ - extern T *CONCAT(Prefix, _new)(_mongocrypt_crypto_t * crypto, __VA_ARGS__, mongocrypt_status_t * status) + extern T *BSON_CONCAT(Prefix, _new)(_mongocrypt_crypto_t * crypto, __VA_ARGS__, mongocrypt_status_t * status) DECL_TOKEN_TYPE(mc_CollectionsLevel1Token, const _mongocrypt_buffer_t *); DECL_TOKEN_TYPE(mc_ServerTokenDerivationLevel1Token, const _mongocrypt_buffer_t *); @@ -100,16 +107,23 @@ DECL_TOKEN_TYPE(mc_EDCDerivedFromDataToken, const mc_EDCToken_t *EDCToken, const DECL_TOKEN_TYPE(mc_ECCDerivedFromDataToken, const mc_ECCToken_t *ECCToken, const _mongocrypt_buffer_t *v); DECL_TOKEN_TYPE(mc_ESCDerivedFromDataToken, const mc_ESCToken_t *ESCToken, const _mongocrypt_buffer_t *v); -DECL_TOKEN_TYPE(mc_EDCDerivedFromDataTokenAndCounter, +DECL_TOKEN_TYPE(mc_EDCDerivedFromDataTokenAndContentionFactor, const mc_EDCDerivedFromDataToken_t *EDCDerivedFromDataToken, uint64_t u); -DECL_TOKEN_TYPE(mc_ESCDerivedFromDataTokenAndCounter, +DECL_TOKEN_TYPE(mc_ESCDerivedFromDataTokenAndContentionFactor, const mc_ESCDerivedFromDataToken_t *ESCDerivedFromDataToken, uint64_t u); -DECL_TOKEN_TYPE(mc_ECCDerivedFromDataTokenAndCounter, +DECL_TOKEN_TYPE(mc_ECCDerivedFromDataTokenAndContentionFactor, const mc_ECCDerivedFromDataToken_t *ECCDerivedFromDataToken, uint64_t u); +DECL_TOKEN_TYPE(mc_EDCTwiceDerivedToken, + const mc_EDCDerivedFromDataTokenAndContentionFactor_t *EDCDerivedFromDataTokenAndContentionFactor); +DECL_TOKEN_TYPE(mc_ESCTwiceDerivedTagToken, + const mc_ESCDerivedFromDataTokenAndContentionFactor_t *ESCDerivedFromDataTokenAndContentionFactor); +DECL_TOKEN_TYPE(mc_ESCTwiceDerivedValueToken, + const mc_ESCDerivedFromDataTokenAndContentionFactor_t *ESCDerivedFromDataTokenAndContentionFactor); + DECL_TOKEN_TYPE(mc_ServerDerivedFromDataToken, const mc_ServerTokenDerivationLevel1Token_t *ServerTokenDerivationToken, const _mongocrypt_buffer_t *v); @@ -118,6 +132,8 @@ DECL_TOKEN_TYPE(mc_ServerCountAndContentionFactorEncryptionToken, const mc_ServerDerivedFromDataToken_t *serverDerivedFromDataToken); DECL_TOKEN_TYPE(mc_ServerZerosEncryptionToken, const mc_ServerDerivedFromDataToken_t *serverDerivedFromDataToken); +DECL_TOKEN_TYPE(mc_AnchorPaddingTokenRoot, const mc_ESCToken_t *ESCToken); + #undef DECL_TOKEN_TYPE #undef DECL_TOKEN_TYPE_1 diff --git a/src/third_party/libmongocrypt/dist/src/mc-tokens.c b/src/third_party/libmongocrypt/dist/src/mc-tokens.c index 2b7afa3589a..34592dca035 100644 --- a/src/third_party/libmongocrypt/dist/src/mc-tokens.c +++ b/src/third_party/libmongocrypt/dist/src/mc-tokens.c @@ -15,12 +15,13 @@ */ #include "mc-tokens-private.h" +#include "mongocrypt-buffer-private.h" /// Define a token type of the given name, with constructor parameters given as /// the remaining arguments. This macro usage should be followed by the /// constructor body, with the implicit first argument '_mongocrypt_crypto_t* /// crypto' and final argument 'mongocrypt_status_t* status' -#define DEF_TOKEN_TYPE(Name, ...) DEF_TOKEN_TYPE_1(Name, CONCAT(Name, _t), __VA_ARGS__) +#define DEF_TOKEN_TYPE(Name, ...) DEF_TOKEN_TYPE_1(Name, BSON_CONCAT(Name, _t), __VA_ARGS__) #define DEF_TOKEN_TYPE_1(Prefix, T, ...) \ /* Define the struct for the token */ \ @@ -28,9 +29,9 @@ _mongocrypt_buffer_t data; \ }; \ /* Data-getter */ \ - const _mongocrypt_buffer_t *CONCAT(Prefix, _get)(const T *self) { return &self->data; } \ + const _mongocrypt_buffer_t *BSON_CONCAT(Prefix, _get)(const T *self) { return &self->data; } \ /* Destructor */ \ - void CONCAT(Prefix, _destroy)(T * self) { \ + void BSON_CONCAT(Prefix, _destroy)(T * self) { \ if (!self) { \ return; \ } \ @@ -38,23 +39,23 @@ bson_free(self); \ } \ /* Constructor. From raw buffer */ \ - T *CONCAT(Prefix, _new_from_buffer)(_mongocrypt_buffer_t * buf) { \ + T *BSON_CONCAT(Prefix, _new_from_buffer)(_mongocrypt_buffer_t * buf) { \ BSON_ASSERT(buf->len == MONGOCRYPT_HMAC_SHA256_LEN); \ T *t = bson_malloc(sizeof(T)); \ _mongocrypt_buffer_set_to(buf, &t->data); \ return t; \ } \ /* Constructor. Parameter list given as variadic args. */ \ - T *CONCAT(Prefix, _new)(_mongocrypt_crypto_t * crypto, __VA_ARGS__, mongocrypt_status_t * status) + T *BSON_CONCAT(Prefix, _new)(_mongocrypt_crypto_t * crypto, __VA_ARGS__, mongocrypt_status_t * status) #define IMPL_TOKEN_NEW_1(Name, Key, Arg, Clean) \ { \ - CONCAT(Name, _t) *t = bson_malloc(sizeof(CONCAT(Name, _t))); \ + BSON_CONCAT(Name, _t) *t = bson_malloc(sizeof(BSON_CONCAT(Name, _t))); \ _mongocrypt_buffer_init(&t->data); \ _mongocrypt_buffer_resize(&t->data, MONGOCRYPT_HMAC_SHA256_LEN); \ \ if (!_mongocrypt_hmac_sha_256(crypto, Key, Arg, &t->data, status)) { \ - CONCAT(Name, _destroy)(t); \ + BSON_CONCAT(Name, _destroy)(t); \ Clean; \ return NULL; \ } \ @@ -97,23 +98,46 @@ IMPL_TOKEN_NEW(mc_ESCDerivedFromDataToken, mc_ESCToken_get(ESCToken), v) DEF_TOKEN_TYPE(mc_ECCDerivedFromDataToken, const mc_ECCToken_t *ECCToken, const _mongocrypt_buffer_t *v) IMPL_TOKEN_NEW(mc_ECCDerivedFromDataToken, mc_ECCToken_get(ECCToken), v) +DEF_TOKEN_TYPE(mc_EDCTwiceDerivedToken, + const mc_EDCDerivedFromDataTokenAndContentionFactor_t *EDCDerivedFromDataTokenAndContentionFactor) +IMPL_TOKEN_NEW_CONST(mc_EDCTwiceDerivedToken, + mc_EDCDerivedFromDataTokenAndContentionFactor_get(EDCDerivedFromDataTokenAndContentionFactor), + 1) + +DEF_TOKEN_TYPE(mc_ESCTwiceDerivedTagToken, + const mc_ESCDerivedFromDataTokenAndContentionFactor_t *ESCDerivedFromDataTokenAndContentionFactor) +IMPL_TOKEN_NEW_CONST(mc_ESCTwiceDerivedTagToken, + mc_ESCDerivedFromDataTokenAndContentionFactor_get(ESCDerivedFromDataTokenAndContentionFactor), + 1) +DEF_TOKEN_TYPE(mc_ESCTwiceDerivedValueToken, + const mc_ESCDerivedFromDataTokenAndContentionFactor_t *ESCDerivedFromDataTokenAndContentionFactor) +IMPL_TOKEN_NEW_CONST(mc_ESCTwiceDerivedValueToken, + mc_ESCDerivedFromDataTokenAndContentionFactor_get(ESCDerivedFromDataTokenAndContentionFactor), + 2) + DEF_TOKEN_TYPE(mc_ServerDataEncryptionLevel1Token, const _mongocrypt_buffer_t *RootKey) IMPL_TOKEN_NEW_CONST(mc_ServerDataEncryptionLevel1Token, RootKey, 3) -DEF_TOKEN_TYPE(mc_EDCDerivedFromDataTokenAndCounter, +DEF_TOKEN_TYPE(mc_EDCDerivedFromDataTokenAndContentionFactor, const mc_EDCDerivedFromDataToken_t *EDCDerivedFromDataToken, uint64_t u) -IMPL_TOKEN_NEW_CONST(mc_EDCDerivedFromDataTokenAndCounter, mc_EDCDerivedFromDataToken_get(EDCDerivedFromDataToken), u) +IMPL_TOKEN_NEW_CONST(mc_EDCDerivedFromDataTokenAndContentionFactor, + mc_EDCDerivedFromDataToken_get(EDCDerivedFromDataToken), + u) -DEF_TOKEN_TYPE(mc_ESCDerivedFromDataTokenAndCounter, +DEF_TOKEN_TYPE(mc_ESCDerivedFromDataTokenAndContentionFactor, const mc_ESCDerivedFromDataToken_t *ESCDerivedFromDataToken, uint64_t u) -IMPL_TOKEN_NEW_CONST(mc_ESCDerivedFromDataTokenAndCounter, mc_ESCDerivedFromDataToken_get(ESCDerivedFromDataToken), u) +IMPL_TOKEN_NEW_CONST(mc_ESCDerivedFromDataTokenAndContentionFactor, + mc_ESCDerivedFromDataToken_get(ESCDerivedFromDataToken), + u) -DEF_TOKEN_TYPE(mc_ECCDerivedFromDataTokenAndCounter, +DEF_TOKEN_TYPE(mc_ECCDerivedFromDataTokenAndContentionFactor, const mc_ECCDerivedFromDataToken_t *ECCDerivedFromDataToken, uint64_t u) -IMPL_TOKEN_NEW_CONST(mc_ECCDerivedFromDataTokenAndCounter, mc_ECCDerivedFromDataToken_get(ECCDerivedFromDataToken), u) +IMPL_TOKEN_NEW_CONST(mc_ECCDerivedFromDataTokenAndContentionFactor, + mc_ECCDerivedFromDataToken_get(ECCDerivedFromDataToken), + u) /* FLE2v2 */ @@ -133,3 +157,23 @@ IMPL_TOKEN_NEW_CONST(mc_ServerCountAndContentionFactorEncryptionToken, DEF_TOKEN_TYPE(mc_ServerZerosEncryptionToken, const mc_ServerDerivedFromDataToken_t *serverDerivedFromDataToken) IMPL_TOKEN_NEW_CONST(mc_ServerZerosEncryptionToken, mc_ServerDerivedFromDataToken_get(serverDerivedFromDataToken), 2) + +// d = 17 bytes of 0, AnchorPaddingTokenRoot = HMAC(ESCToken, d) +#define ANCHOR_PADDING_TOKEN_D_LENGTH 17 +const uint8_t mc_AnchorPaddingTokenDValue[ANCHOR_PADDING_TOKEN_D_LENGTH] = + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +DEF_TOKEN_TYPE(mc_AnchorPaddingTokenRoot, const mc_ESCToken_t *ESCToken) { + _mongocrypt_buffer_t to_hash; + if (!_mongocrypt_buffer_copy_from_data_and_size(&to_hash, + mc_AnchorPaddingTokenDValue, + ANCHOR_PADDING_TOKEN_D_LENGTH)) { + return NULL; + } + IMPL_TOKEN_NEW_1(mc_AnchorPaddingTokenRoot, + mc_ESCToken_get(ESCToken), + &to_hash, + _mongocrypt_buffer_cleanup(&to_hash)) +} + +#undef ANCHOR_PADDING_TOKEN_D_LENGTH diff --git a/src/third_party/libmongocrypt/dist/src/mlib/int128.h b/src/third_party/libmongocrypt/dist/src/mlib/int128.h index 1619d200f92..d43338392f7 100644 --- a/src/third_party/libmongocrypt/dist/src/mlib/int128.h +++ b/src/third_party/libmongocrypt/dist/src/mlib/int128.h @@ -200,6 +200,13 @@ static mlib_constexpr_fn mlib_int128 mlib_int128_bitor(mlib_int128 l, mlib_int12 return MLIB_INIT(mlib_int128) MLIB_INT128_FROM_PARTS(l.r.lo | r.r.lo, l.r.hi | r.r.hi); } +/** + * @brief Bitwise-and two 128-bit integers + */ +static mlib_constexpr_fn mlib_int128 mlib_int128_bitand(mlib_int128 l, mlib_int128 r) { + return MLIB_INIT(mlib_int128) MLIB_INT128_FROM_PARTS(l.r.lo & r.r.lo, l.r.hi & r.r.hi); +} + // Multiply two 64bit integers to get a 128-bit result without overflow static mlib_constexpr_fn mlib_int128 _mlibUnsignedMult128(uint64_t left, uint64_t right) { // Perform a Knuth 4.3.1M multiplication @@ -245,6 +252,16 @@ static mlib_constexpr_fn int _mlibCountLeadingZeros_u64(uint64_t bits) { return n; } +static mlib_constexpr_fn int _mlibCountLeadingZeros_u128(mlib_int128 r) { + int clz_l = _mlibCountLeadingZeros_u64(r.r.hi); + if (clz_l != 64) { + return clz_l; + } + + int clz_r = _mlibCountLeadingZeros_u64(r.r.lo); + return clz_l + clz_r; +} + /// Implementation of Knuth's algorithm 4.3.1 D for unsigned integer division static mlib_constexpr_fn void _mlibKnuth431D(uint32_t *const u, const int ulen, const uint32_t *const v, const int vlen, uint32_t *quotient) { diff --git a/src/third_party/libmongocrypt/dist/src/mlib/int128.test.cpp b/src/third_party/libmongocrypt/dist/src/mlib/int128.test.cpp index 99e66d21f0c..3a0854a3bb6 100644 --- a/src/third_party/libmongocrypt/dist/src/mlib/int128.test.cpp +++ b/src/third_party/libmongocrypt/dist/src/mlib/int128.test.cpp @@ -14,6 +14,11 @@ // Old GCC and old MSVC have partially-broken constexpr that prevents us from // properly using static_assert with from_string() #define BROKEN_CONSTEXPR +#elif (defined(_MSC_VER) && _MSC_VER >= 1930) +// Avoid internal compiler error on VS 2022 versions 17.0 and newer when +// evaluating mlib_int128_from_string via operator""_i128. Assumed to be related +// to: https://developercommunity.visualstudio.com/t/User-defined-literals-cause-ICEs/10259122 +#define BROKEN_CONSTEXPR #endif #ifndef BROKEN_CONSTEXPR diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-binary-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-binary-private.h index 08da7877943..235caf2e288 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-binary-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-binary-private.h @@ -21,11 +21,6 @@ #include "mongocrypt.h" -struct _mongocrypt_binary_t { - uint8_t *data; - uint32_t len; -}; - bool _mongocrypt_binary_to_bson(mongocrypt_binary_t *binary, bson_t *out) MONGOCRYPT_WARN_UNUSED_RESULT; #endif /* MONGOCRYPT_BINARY_PRIVATE_H */ diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-buffer.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-buffer.c index f9579157caf..cf7b1ccfce3 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-buffer.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-buffer.c @@ -19,6 +19,11 @@ #include "mongocrypt-util-private.h" #include +// Require libbson 1.16.0 or newer. Fix for CDRIVER-3360 is needed. +#if !BSON_CHECK_VERSION(1, 16, 0) +#error "libbson 1.16.0 or newer is required." +#endif + #define INT32_LEN 4 #define TYPE_LEN 1 #define NULL_BYTE_LEN 1 @@ -306,13 +311,6 @@ bool _mongocrypt_buffer_to_bson_value(_mongocrypt_buffer_t *plaintext, uint8_t t } bson_value_copy(bson_iter_value(&iter), out); - /* Due to an open libbson bug (CDRIVER-3340), give an empty - * binary payload a real address. TODO: remove this after - * CDRIVER-3340 is fixed. */ - if (out->value_type == BSON_TYPE_BINARY && 0 == out->value.v_binary.data_len) { - out->value.v_binary.data = bson_malloc(1); /* Freed in bson_value_destroy */ - } - ret = true; fail: bson_free(data); diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-collinfo.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-collinfo.c index 87dc4afca8a..a25a5ae241b 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-collinfo.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-collinfo.c @@ -61,5 +61,5 @@ void _mongocrypt_cache_collinfo_init(_mongocrypt_cache_t *cache) { cache->destroy_value = _destroy_value; _mongocrypt_mutex_init(&cache->mutex); cache->pair = NULL; - cache->expiration = CACHE_EXPIRATION_MS; + cache->expiration = CACHE_EXPIRATION_MS_DEFAULT; } diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-key.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-key.c index 601ebdf4637..24ac81f97b7 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-key.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-key.c @@ -85,6 +85,7 @@ static void _dump_attr(void *attr_in) { for (altname = attr->alt_names; NULL != altname; altname = altname->next) { printf("%s\n", _mongocrypt_key_alt_name_get_string(altname)); } + bson_free(hex); } _mongocrypt_cache_key_value_t *_mongocrypt_cache_key_value_new(_mongocrypt_key_doc_t *key_doc, @@ -128,7 +129,7 @@ void _mongocrypt_cache_key_init(_mongocrypt_cache_t *cache) { cache->dump_attr = _dump_attr; _mongocrypt_mutex_init(&cache->mutex); cache->pair = NULL; - cache->expiration = CACHE_EXPIRATION_MS; + cache->expiration = CACHE_EXPIRATION_MS_DEFAULT; } /* Since key cache may be looked up by either _id or keyAltName, diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-oauth-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-oauth-private.h index b15e2c8ef97..7a52ea5e313 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-oauth-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-oauth-private.h @@ -20,21 +20,19 @@ #include "mongocrypt-mutex-private.h" #include "mongocrypt-status-private.h" -typedef struct { - bson_t *entry; - char *access_token; - int64_t expiration_time_us; - mongocrypt_mutex_t mutex; /* global lock of cache. */ -} _mongocrypt_cache_oauth_t; +// `mc_mapof_kmsid_to_token_t` maps a KMS ID (e.g. `azure` or `azure:myname`) to an OAuth token. +typedef struct _mc_mapof_kmsid_to_token_t mc_mapof_kmsid_to_token_t; -_mongocrypt_cache_oauth_t *_mongocrypt_cache_oauth_new(void); +mc_mapof_kmsid_to_token_t *mc_mapof_kmsid_to_token_new(void); +void mc_mapof_kmsid_to_token_destroy(mc_mapof_kmsid_to_token_t *k2t); +// `mc_mapof_kmsid_to_token_get_token` returns a copy of the base64 encoded oauth token, or NULL. +// Thread-safe. +char *mc_mapof_kmsid_to_token_get_token(mc_mapof_kmsid_to_token_t *k2t, const char *kmsid); +// `mc_mapof_kmsid_to_token_add_response` overwrites an entry if `kms_id` exists. +// Thread-safe. +bool mc_mapof_kmsid_to_token_add_response(mc_mapof_kmsid_to_token_t *k2t, + const char *kmsid, + bson_t *response, + mongocrypt_status_t *status); -void _mongocrypt_cache_oauth_destroy(_mongocrypt_cache_oauth_t *cache); - -bool _mongocrypt_cache_oauth_add(_mongocrypt_cache_oauth_t *cache, bson_t *oauth_response, mongocrypt_status_t *status); - -/* Returns a copy of the base64 encoded oauth token, or NULL if nothing is - * cached. */ -char *_mongocrypt_cache_oauth_get(_mongocrypt_cache_oauth_t *cache); - -#endif /* MONGOCRYPT_CACHE_OAUTH_PRIVATE_H */ \ No newline at end of file +#endif /* MONGOCRYPT_CACHE_OAUTH_PRIVATE_H */ diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-oauth.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-oauth.c index e11d22293d6..9626e37e4ac 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-oauth.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-oauth.c @@ -16,6 +16,7 @@ #include "mongocrypt-cache-oauth-private.h" +#include "mc-array-private.h" #include "mongocrypt-private.h" /* How long before the reported "expires_in" time cache entries get evicted. @@ -24,91 +25,119 @@ */ #define MONGOCRYPT_OAUTH_CACHE_EVICTION_PERIOD_US 5000 * 1000 -_mongocrypt_cache_oauth_t *_mongocrypt_cache_oauth_new(void) { - _mongocrypt_cache_oauth_t *cache; - - cache = bson_malloc0(sizeof(_mongocrypt_cache_oauth_t)); - _mongocrypt_mutex_init(&cache->mutex); - return cache; -} - -void _mongocrypt_cache_oauth_destroy(_mongocrypt_cache_oauth_t *cache) { - BSON_ASSERT_PARAM(cache); - - _mongocrypt_mutex_cleanup(&cache->mutex); - bson_destroy(cache->entry); - bson_free(cache->access_token); - bson_free(cache); -} - -bool _mongocrypt_cache_oauth_add(_mongocrypt_cache_oauth_t *cache, - bson_t *oauth_response, - mongocrypt_status_t *status) { - bson_iter_t iter; +typedef struct { + char *kmsid; + char *access_token; int64_t expiration_time_us; - int64_t cache_time_us; - int64_t expires_in_s; - int64_t expires_in_us; +} mc_mapof_kmsid_to_token_entry_t; + +struct _mc_mapof_kmsid_to_token_t { + mc_array_t entries; + mongocrypt_mutex_t mutex; // Guards `entries`. +}; + +mc_mapof_kmsid_to_token_t *mc_mapof_kmsid_to_token_new(void) { + mc_mapof_kmsid_to_token_t *k2t = bson_malloc0(sizeof(mc_mapof_kmsid_to_token_t)); + _mc_array_init(&k2t->entries, sizeof(mc_mapof_kmsid_to_token_entry_t)); + _mongocrypt_mutex_init(&k2t->mutex); + return k2t; +} + +void mc_mapof_kmsid_to_token_destroy(mc_mapof_kmsid_to_token_t *k2t) { + if (!k2t) { + return; + } + _mongocrypt_mutex_cleanup(&k2t->mutex); + for (size_t i = 0; i < k2t->entries.len; i++) { + mc_mapof_kmsid_to_token_entry_t k2te = _mc_array_index(&k2t->entries, mc_mapof_kmsid_to_token_entry_t, i); + bson_free(k2te.kmsid); + bson_free(k2te.access_token); + } + _mc_array_destroy(&k2t->entries); + bson_free(k2t); +} + +char *mc_mapof_kmsid_to_token_get_token(mc_mapof_kmsid_to_token_t *k2t, const char *kmsid) { + BSON_ASSERT_PARAM(k2t); + BSON_ASSERT_PARAM(kmsid); + + _mongocrypt_mutex_lock(&k2t->mutex); + + for (size_t i = 0; i < k2t->entries.len; i++) { + mc_mapof_kmsid_to_token_entry_t k2te = _mc_array_index(&k2t->entries, mc_mapof_kmsid_to_token_entry_t, i); + if (0 == strcmp(k2te.kmsid, kmsid)) { + if (bson_get_monotonic_time() >= k2te.expiration_time_us) { + // Expired. + _mongocrypt_mutex_unlock(&k2t->mutex); + return NULL; + } + char *access_token = bson_strdup(k2te.access_token); + _mongocrypt_mutex_unlock(&k2t->mutex); + return access_token; + } + } + + _mongocrypt_mutex_unlock(&k2t->mutex); + return NULL; +} + +bool mc_mapof_kmsid_to_token_add_response(mc_mapof_kmsid_to_token_t *k2t, + const char *kmsid, + bson_t *response, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(k2t); + BSON_ASSERT_PARAM(kmsid); + BSON_ASSERT_PARAM(response); + + // Parse access token before locking. const char *access_token; + int64_t expiration_time_us; + { + bson_iter_t iter; + int64_t cache_time_us; + int64_t expires_in_s; + int64_t expires_in_us; - BSON_ASSERT_PARAM(cache); - BSON_ASSERT_PARAM(oauth_response); + /* The OAuth spec strongly implies that the value of expires_in is positive, + * so the overflow checks in this function don't consider negative values. */ + if (!bson_iter_init_find(&iter, response, "expires_in") || !BSON_ITER_HOLDS_INT(&iter)) { + CLIENT_ERR("OAuth response invalid, no 'expires_in' field."); + return false; + } + cache_time_us = bson_get_monotonic_time(); + expires_in_s = bson_iter_as_int64(&iter); + BSON_ASSERT(expires_in_s <= INT64_MAX / 1000 / 1000); + expires_in_us = expires_in_s * 1000 * 1000; + BSON_ASSERT(expires_in_us <= INT64_MAX - cache_time_us + && expires_in_us + cache_time_us > MONGOCRYPT_OAUTH_CACHE_EVICTION_PERIOD_US); + expiration_time_us = expires_in_us + cache_time_us - MONGOCRYPT_OAUTH_CACHE_EVICTION_PERIOD_US; - /* The OAuth spec strongly implies that the value of expires_in is positive, - * so the overflow checks in this function don't consider negative values. */ - if (!bson_iter_init_find(&iter, oauth_response, "expires_in") || !BSON_ITER_HOLDS_INT(&iter)) { - CLIENT_ERR("OAuth response invalid, no 'expires_in' field."); - return false; + if (!bson_iter_init_find(&iter, response, "access_token") || !BSON_ITER_HOLDS_UTF8(&iter)) { + CLIENT_ERR("OAuth response invalid, no 'access_token' field."); + return false; + } + access_token = bson_iter_utf8(&iter, NULL); } - cache_time_us = bson_get_monotonic_time(); - expires_in_s = bson_iter_as_int64(&iter); - BSON_ASSERT(expires_in_s <= INT64_MAX / 1000 / 1000); - expires_in_us = expires_in_s * 1000 * 1000; - BSON_ASSERT(expires_in_us <= INT64_MAX - cache_time_us - && expires_in_us + cache_time_us > MONGOCRYPT_OAUTH_CACHE_EVICTION_PERIOD_US); - expiration_time_us = expires_in_us + cache_time_us - MONGOCRYPT_OAUTH_CACHE_EVICTION_PERIOD_US; - if (!bson_iter_init_find(&iter, oauth_response, "access_token") || !BSON_ITER_HOLDS_UTF8(&iter)) { - CLIENT_ERR("OAuth response invalid, no 'access_token' field."); - return false; - } - access_token = bson_iter_utf8(&iter, NULL); + _mongocrypt_mutex_lock(&k2t->mutex); - _mongocrypt_mutex_lock(&cache->mutex); - if (expiration_time_us > cache->expiration_time_us) { - bson_destroy(cache->entry); - cache->entry = bson_copy(oauth_response); - cache->expiration_time_us = expiration_time_us; - bson_free(cache->access_token); - cache->access_token = bson_strdup(access_token); + // Check if there is an existing entry. + for (size_t i = 0; i < k2t->entries.len; i++) { + mc_mapof_kmsid_to_token_entry_t *k2te = &_mc_array_index(&k2t->entries, mc_mapof_kmsid_to_token_entry_t, i); + if (0 == strcmp(k2te->kmsid, kmsid)) { + // Update entry. + bson_free(k2te->access_token); + k2te->access_token = bson_strdup(access_token); + k2te->expiration_time_us = expiration_time_us; + _mongocrypt_mutex_unlock(&k2t->mutex); + return true; + } } - _mongocrypt_mutex_unlock(&cache->mutex); + // Create an entry. + mc_mapof_kmsid_to_token_entry_t to_put = {.kmsid = bson_strdup(kmsid), + .access_token = bson_strdup(access_token), + .expiration_time_us = expiration_time_us}; + _mc_array_append_val(&k2t->entries, to_put); + _mongocrypt_mutex_unlock(&k2t->mutex); return true; } - -/* Returns a copy of the base64 encoded oauth token, or NULL if nothing is - * cached. */ -char *_mongocrypt_cache_oauth_get(_mongocrypt_cache_oauth_t *cache) { - char *access_token; - - BSON_ASSERT_PARAM(cache); - - _mongocrypt_mutex_lock(&cache->mutex); - if (!cache->entry) { - _mongocrypt_mutex_unlock(&cache->mutex); - return NULL; - } - - if (bson_get_monotonic_time() >= cache->expiration_time_us) { - bson_destroy(cache->entry); - cache->entry = NULL; - cache->expiration_time_us = 0; - _mongocrypt_mutex_unlock(&cache->mutex); - return NULL; - } - - access_token = bson_strdup(cache->access_token); - _mongocrypt_mutex_unlock(&cache->mutex); - - return access_token; -} diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-private.h index 2c6160bc6a2..841a494d2e9 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache-private.h @@ -20,7 +20,7 @@ #include "mongocrypt-mutex-private.h" #include "mongocrypt-status-private.h" -#define CACHE_EXPIRATION_MS 60000 +#define CACHE_EXPIRATION_MS_DEFAULT 60000 /* A generic simple cache. * To avoid overusing the names "key" or "id", the cache contains @@ -74,4 +74,4 @@ void _mongocrypt_cache_set_expiration(_mongocrypt_cache_t *cache, uint64_t milli uint32_t _mongocrypt_cache_num_entries(_mongocrypt_cache_t *cache); -#endif /* MONGOCRYPT_CACHE_PRIVATE */ \ No newline at end of file +#endif /* MONGOCRYPT_CACHE_PRIVATE */ diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache.c index 489a7b9a4c2..aad4b25e9b4 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-cache.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-cache.c @@ -28,7 +28,7 @@ static bool _pair_expired(_mongocrypt_cache_t *cache, _mongocrypt_cache_pair_t * current = bson_get_monotonic_time() / 1000; BSON_ASSERT(current >= INT64_MIN + pair->last_updated); BSON_ASSERT(cache->expiration <= INT64_MAX); - return (current - pair->last_updated) > (int64_t)cache->expiration; + return cache->expiration > 0 && (current - pair->last_updated) > (int64_t)cache->expiration; } /* Return the pair after the one being destroyed. */ diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-ciphertext.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-ciphertext.c index e7dac7c4253..3b029c9ed2c 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-ciphertext.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-ciphertext.c @@ -92,7 +92,7 @@ bool _mongocrypt_ciphertext_parse_unowned(_mongocrypt_buffer_t *in, ciphertext->original_bson_type = in->data[offset]; offset += 1; - memset(&ciphertext->data, 0, sizeof(ciphertext->data)); + _mongocrypt_buffer_init(&ciphertext->data); ciphertext->data.data = in->data + offset; ciphertext->data.len = in->len - offset; diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-crypto.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-crypto.c index 5682ad54003..2d2a4c4db67 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-crypto.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-crypto.c @@ -78,7 +78,10 @@ static bool _crypto_aes_256_ctr_encrypt_decrypt_via_ecb(void *ctx, /* XOR resulting stream with original data */ for (uint32_t i = 0; i < bytes_written && ptr < args.in->len; i++, ptr++) { - out_bin.data[ptr] = in_bin.data[ptr] ^ tmp_bin.data[i]; + uint8_t *in_bin_u8 = in_bin.data; + uint8_t *out_bin_u8 = out_bin.data; + uint8_t *tmp_bin_u8 = tmp_bin.data; + out_bin_u8[ptr] = in_bin_u8[ptr] ^ tmp_bin_u8[i]; } /* Increment value in CTR buffer */ @@ -86,9 +89,10 @@ static bool _crypto_aes_256_ctr_encrypt_decrypt_via_ecb(void *ctx, /* assert rather than return since this should never happen */ BSON_ASSERT(ctr_bin.len == 0u || ctr_bin.len - 1u <= INT_MAX); for (int i = (int)ctr_bin.len - 1; i >= 0 && carry != 0; --i) { - uint32_t bpp = carry + ctr_bin.data[i]; + uint8_t *ctr_bin_u8 = ctr_bin.data; + uint32_t bpp = carry + ctr_bin_u8[i]; carry = bpp >> 8; - ctr_bin.data[i] = bpp & 0xFF; + ctr_bin_u8[i] = bpp & 0xFF; } } @@ -1384,6 +1388,8 @@ bool _mongocrypt_random_uint64(_mongocrypt_crypto_t *crypto, } memcpy(&rand_u64, rand_u64_buf.data, rand_u64_buf.len); + // Use little-endian to enable deterministic tests on big-endian machines. + rand_u64 = BSON_UINT64_FROM_LE(rand_u64); if (rand_u64 >= min) { break; diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-datakey.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-datakey.c index 2b98957fbe7..09f62cf1a67 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-datakey.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-datakey.c @@ -16,6 +16,7 @@ #include "mongocrypt-crypto-private.h" #include "mongocrypt-ctx-private.h" +#include "mongocrypt-kms-ctx-private.h" #include "mongocrypt-private.h" #include "mongocrypt.h" @@ -39,114 +40,169 @@ static mongocrypt_kms_ctx_t *_next_kms_ctx(mongocrypt_ctx_t *ctx) { BSON_ASSERT_PARAM(ctx); dkctx = (_mongocrypt_ctx_datakey_t *)ctx; - if (dkctx->kms_returned) { + if (!dkctx->kms.should_retry && dkctx->kms_returned) { return NULL; } + dkctx->kms.should_retry = false; // Reset retry state. dkctx->kms_returned = true; return &dkctx->kms; } -static bool _kms_kmip_start(mongocrypt_ctx_t *ctx) { +static bool _kms_kmip_start(mongocrypt_ctx_t *ctx, const mc_kms_creds_t *kc) { bool ret = false; _mongocrypt_ctx_datakey_t *dkctx = (_mongocrypt_ctx_datakey_t *)ctx; char *user_supplied_keyid = NULL; - _mongocrypt_endpoint_t *endpoint = NULL; + const _mongocrypt_endpoint_t *endpoint = NULL; mongocrypt_status_t *status = ctx->status; _mongocrypt_buffer_t secretdata = {0}; BSON_ASSERT_PARAM(ctx); + BSON_ASSERT_PARAM(kc); + BSON_ASSERT(kc->type == MONGOCRYPT_KMS_PROVIDER_KMIP); if (ctx->opts.kek.kms_provider != MONGOCRYPT_KMS_PROVIDER_KMIP) { CLIENT_ERR("KMS provider is not KMIP"); goto fail; } + const bool delegated = ctx->opts.kek.provider.kmip.delegated; user_supplied_keyid = ctx->opts.kek.provider.kmip.key_id; if (ctx->opts.kek.provider.kmip.endpoint) { endpoint = ctx->opts.kek.provider.kmip.endpoint; - } else if (_mongocrypt_ctx_kms_providers(ctx)->kmip.endpoint) { - endpoint = _mongocrypt_ctx_kms_providers(ctx)->kmip.endpoint; + } else if (kc->value.kmip.endpoint) { + endpoint = kc->value.kmip.endpoint; } else { CLIENT_ERR("endpoint not set for KMIP request"); goto fail; } - /* The KMIP createDataKey flow is the following: - * - * 1. Send a KMIP Register request with a new 96 byte key as a SecretData - * managed object. This returns a Unique Identifier. - * 2. Send a KMIP Activate request with the Unique Identifier. - * This returns the same Unique Identifier. - * 3. Send a KMIP Get request with the Unique Identifier. - * This returns the 96 byte SecretData. - * 4. Use the 96 byte SecretData to encrypt a new DEK. - * - * If the user set a 'keyId' to use, the flow begins at step 3. - */ - if (user_supplied_keyid && !dkctx->kmip_unique_identifier) { /* User set a 'keyId'. */ dkctx->kmip_unique_identifier = bson_strdup(user_supplied_keyid); dkctx->kmip_activated = true; - /* Fall through to Step 3. */ } - if (!dkctx->kmip_unique_identifier) { - /* User did not set a 'keyId'. */ - /* Step 1. Send a KMIP Register request with a new 96 byte SecretData. */ - _mongocrypt_buffer_init(&secretdata); - _mongocrypt_buffer_resize(&secretdata, MONGOCRYPT_KEY_LEN); - if (!_mongocrypt_random(ctx->crypt->crypto, &secretdata, MONGOCRYPT_KEY_LEN, ctx->status)) { - goto fail; + if (delegated) { + /* + * The KMIP delegated createDataKey flow is the following: + * 1. Send a KMIP Create request (symmetric key) (returns keyId) + * 2. Send a KMIP Activate request with that keyId + * 3. Send a KMIP Encrypt request to encrypt the DEK + * + * Steps 1 and 2 are skipped if the user provided a keyId + */ + + if (!dkctx->kmip_unique_identifier) { + /* User did not set a 'keyId'. */ + /* Step 1. Send a KMIP Create request for a new AES-256 symmetric key. */ + if (!_mongocrypt_kms_ctx_init_kmip_create(&dkctx->kms, endpoint, ctx->opts.kek.kmsid, &ctx->crypt->log)) { + mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); + goto fail; + } + ctx->state = MONGOCRYPT_CTX_NEED_KMS; + goto success; } - if (!_mongocrypt_kms_ctx_init_kmip_register(&dkctx->kms, - endpoint, - secretdata.data, - secretdata.len, - &ctx->crypt->log)) { - mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); + if (!dkctx->kmip_activated) { + /* Step 2. Send a KMIP Activate request. */ + if (!_mongocrypt_kms_ctx_init_kmip_activate(&dkctx->kms, + endpoint, + dkctx->kmip_unique_identifier, + ctx->opts.kek.kmsid, + &ctx->crypt->log)) { + mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); + goto fail; + } + ctx->state = MONGOCRYPT_CTX_NEED_KMS; + goto success; + } + + if (!dkctx->encrypted_key_material.data) { + /* Step 3. Have the KMS encrypt a new DEK. */ + if (!_mongocrypt_kms_ctx_init_kmip_encrypt(&dkctx->kms, + endpoint, + dkctx->kmip_unique_identifier, + ctx->opts.kek.kmsid, + &dkctx->plaintext_key_material, + &ctx->crypt->log)) { + mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); + goto fail; + } + ctx->state = MONGOCRYPT_CTX_NEED_KMS; + goto success; + } + } else { + /* The KMIP createDataKey flow is the following: + * + * 1. Send a KMIP Register request with a new 96 byte key as a SecretData + * managed object. This returns a Unique Identifier. + * 2. Send a KMIP Activate request with the Unique Identifier. + * This returns the same Unique Identifier. + * 3. Send a KMIP Get request with the Unique Identifier. + * This returns the 96 byte SecretData. + * 4. Use the 96 byte SecretData to encrypt a new DEK. + * + * If the user set a 'keyId' to use, the flow begins at step 3. + */ + if (!dkctx->kmip_unique_identifier) { + /* User did not set a 'keyId'. */ + /* Step 1. Send a KMIP Register request with a new 96 byte SecretData. */ + _mongocrypt_buffer_init(&secretdata); + _mongocrypt_buffer_resize(&secretdata, MONGOCRYPT_KEY_LEN); + if (!_mongocrypt_random(ctx->crypt->crypto, &secretdata, MONGOCRYPT_KEY_LEN, ctx->status)) { + goto fail; + } + + if (!_mongocrypt_kms_ctx_init_kmip_register(&dkctx->kms, + endpoint, + secretdata.data, + secretdata.len, + ctx->opts.kek.kmsid, + &ctx->crypt->log)) { + mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); + goto fail; + } + ctx->state = MONGOCRYPT_CTX_NEED_KMS; + goto success; + } + + if (!dkctx->kmip_activated) { + /* Step 2. Send a KMIP Activate request. */ + if (!_mongocrypt_kms_ctx_init_kmip_activate(&dkctx->kms, + endpoint, + dkctx->kmip_unique_identifier, + ctx->opts.kek.kmsid, + &ctx->crypt->log)) { + mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); + goto fail; + } + ctx->state = MONGOCRYPT_CTX_NEED_KMS; + goto success; + } + + if (!dkctx->kmip_secretdata.data) { + /* Step 3. Send a KMIP Get request with the Unique Identifier. */ + if (!_mongocrypt_kms_ctx_init_kmip_get(&dkctx->kms, + endpoint, + dkctx->kmip_unique_identifier, + ctx->opts.kek.kmsid, + &ctx->crypt->log)) { + mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); + goto fail; + } + ctx->state = MONGOCRYPT_CTX_NEED_KMS; + goto success; + } + + /* Step 4. Use the 96 byte SecretData to encrypt a new DEK. */ + if (!_mongocrypt_wrap_key(ctx->crypt->crypto, + &dkctx->kmip_secretdata, + &dkctx->plaintext_key_material, + &dkctx->encrypted_key_material, + ctx->status)) { goto fail; } - ctx->state = MONGOCRYPT_CTX_NEED_KMS; - - goto success; - } - - if (!dkctx->kmip_activated) { - /* Step 2. Send a KMIP Activate request. */ - if (!_mongocrypt_kms_ctx_init_kmip_activate(&dkctx->kms, - endpoint, - dkctx->kmip_unique_identifier, - &ctx->crypt->log)) { - mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); - goto fail; - } - ctx->state = MONGOCRYPT_CTX_NEED_KMS; - goto success; - } - - if (!dkctx->kmip_secretdata.data) { - /* Step 3. Send a KMIP Get request with the Unique Identifier. */ - if (!_mongocrypt_kms_ctx_init_kmip_get(&dkctx->kms, - endpoint, - dkctx->kmip_unique_identifier, - &ctx->crypt->log)) { - mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); - goto fail; - } - ctx->state = MONGOCRYPT_CTX_NEED_KMS; - goto success; - } - - /* Step 4. Use the 96 byte SecretData to encrypt a new DEK. */ - if (!_mongocrypt_wrap_key(ctx->crypt->crypto, - &dkctx->kmip_secretdata, - &dkctx->plaintext_key_material, - &dkctx->encrypted_key_material, - ctx->status)) { - goto fail; } if (!ctx->opts.kek.provider.kmip.key_id) { @@ -180,14 +236,23 @@ static bool _kms_start(mongocrypt_ctx_t *ctx) { dkctx = (_mongocrypt_ctx_datakey_t *)ctx; + mc_kms_creds_t kc; + if (!_mongocrypt_opts_kms_providers_lookup(kms_providers, ctx->opts.kek.kmsid, &kc)) { + mongocrypt_status_t *status = ctx->status; + CLIENT_ERR("KMS provider `%s` is not configured", ctx->opts.kek.kmsid); + _mongocrypt_ctx_fail(ctx); + goto done; + } + /* Clear out any pre-existing initialized KMS context, and zero it (so it is * safe to call cleanup again). */ _mongocrypt_kms_ctx_cleanup(&dkctx->kms); memset(&dkctx->kms, 0, sizeof(dkctx->kms)); dkctx->kms_returned = false; if (ctx->opts.kek.kms_provider == MONGOCRYPT_KMS_PROVIDER_LOCAL) { + BSON_ASSERT(kc.type == MONGOCRYPT_KMS_PROVIDER_LOCAL); if (!_mongocrypt_wrap_key(ctx->crypt->crypto, - &kms_providers->local.key, + &kc.value.local.key, &dkctx->plaintext_key_material, &dkctx->encrypted_key_material, ctx->status)) { @@ -203,8 +268,9 @@ static bool _kms_start(mongocrypt_ctx_t *ctx) { kms_providers, &ctx->opts, &dkctx->plaintext_key_material, - &ctx->crypt->log, - ctx->crypt->crypto)) { + ctx->crypt->crypto, + ctx->opts.kek.kmsid, + &ctx->crypt->log)) { mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); _mongocrypt_ctx_fail(ctx); goto done; @@ -212,27 +278,30 @@ static bool _kms_start(mongocrypt_ctx_t *ctx) { ctx->state = MONGOCRYPT_CTX_NEED_KMS; } else if (ctx->opts.kek.kms_provider == MONGOCRYPT_KMS_PROVIDER_AZURE) { - if (ctx->kms_providers.azure.access_token) { - access_token = bson_strdup(ctx->kms_providers.azure.access_token); + BSON_ASSERT(kc.type == MONGOCRYPT_KMS_PROVIDER_AZURE); + if (kc.value.azure.access_token) { + access_token = bson_strdup(kc.value.azure.access_token); } else { - access_token = _mongocrypt_cache_oauth_get(ctx->crypt->cache_oauth_azure); + access_token = mc_mapof_kmsid_to_token_get_token(ctx->crypt->cache_oauth, ctx->opts.kek.kmsid); } if (access_token) { if (!_mongocrypt_kms_ctx_init_azure_wrapkey(&dkctx->kms, - &ctx->crypt->log, kms_providers, &ctx->opts, access_token, - &dkctx->plaintext_key_material)) { + &dkctx->plaintext_key_material, + ctx->opts.kek.kmsid, + &ctx->crypt->log)) { mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); _mongocrypt_ctx_fail(ctx); goto done; } } else { if (!_mongocrypt_kms_ctx_init_azure_auth(&dkctx->kms, - &ctx->crypt->log, - kms_providers, - ctx->opts.kek.provider.azure.key_vault_endpoint)) { + &kc, + ctx->opts.kek.provider.azure.key_vault_endpoint, + ctx->opts.kek.kmsid, + &ctx->crypt->log)) { mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); _mongocrypt_ctx_fail(ctx); goto done; @@ -240,28 +309,31 @@ static bool _kms_start(mongocrypt_ctx_t *ctx) { } ctx->state = MONGOCRYPT_CTX_NEED_KMS; } else if (ctx->opts.kek.kms_provider == MONGOCRYPT_KMS_PROVIDER_GCP) { - if (NULL != ctx->kms_providers.gcp.access_token) { - access_token = bson_strdup((const char *)ctx->kms_providers.gcp.access_token); + BSON_ASSERT(kc.type == MONGOCRYPT_KMS_PROVIDER_GCP); + if (NULL != kc.value.gcp.access_token) { + access_token = bson_strdup(kc.value.gcp.access_token); } else { - access_token = _mongocrypt_cache_oauth_get(ctx->crypt->cache_oauth_gcp); + access_token = mc_mapof_kmsid_to_token_get_token(ctx->crypt->cache_oauth, ctx->opts.kek.kmsid); } if (access_token) { if (!_mongocrypt_kms_ctx_init_gcp_encrypt(&dkctx->kms, - &ctx->crypt->log, kms_providers, &ctx->opts, access_token, - &dkctx->plaintext_key_material)) { + &dkctx->plaintext_key_material, + ctx->opts.kek.kmsid, + &ctx->crypt->log)) { mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); _mongocrypt_ctx_fail(ctx); goto done; } } else { if (!_mongocrypt_kms_ctx_init_gcp_auth(&dkctx->kms, - &ctx->crypt->log, &ctx->crypt->opts, - kms_providers, - ctx->opts.kek.provider.gcp.endpoint)) { + &kc, + ctx->opts.kek.provider.gcp.endpoint, + ctx->opts.kek.kmsid, + &ctx->crypt->log)) { mongocrypt_kms_ctx_status(&dkctx->kms, ctx->status); _mongocrypt_ctx_fail(ctx); goto done; @@ -269,7 +341,7 @@ static bool _kms_start(mongocrypt_ctx_t *ctx) { } ctx->state = MONGOCRYPT_CTX_NEED_KMS; } else if (ctx->opts.kek.kms_provider == MONGOCRYPT_KMS_PROVIDER_KMIP) { - if (!_kms_kmip_start(ctx)) { + if (!_kms_kmip_start(ctx, &kc)) { goto done; } } else { @@ -305,7 +377,10 @@ static bool _kms_done(mongocrypt_ctx_t *ctx) { bson_t oauth_response; BSON_ASSERT(_mongocrypt_buffer_to_bson(&dkctx->kms.result, &oauth_response)); - if (!_mongocrypt_cache_oauth_add(ctx->crypt->cache_oauth_azure, &oauth_response, status)) { + if (!mc_mapof_kmsid_to_token_add_response(ctx->crypt->cache_oauth, + ctx->opts.kek.kmsid, + &oauth_response, + status)) { return _mongocrypt_ctx_fail(ctx); } return _kms_start(ctx); @@ -313,11 +388,15 @@ static bool _kms_done(mongocrypt_ctx_t *ctx) { bson_t oauth_response; BSON_ASSERT(_mongocrypt_buffer_to_bson(&dkctx->kms.result, &oauth_response)); - if (!_mongocrypt_cache_oauth_add(ctx->crypt->cache_oauth_gcp, &oauth_response, status)) { + if (!mc_mapof_kmsid_to_token_add_response(ctx->crypt->cache_oauth, + ctx->opts.kek.kmsid, + &oauth_response, + status)) { return _mongocrypt_ctx_fail(ctx); } return _kms_start(ctx); - } else if (dkctx->kms.req_type == MONGOCRYPT_KMS_KMIP_REGISTER) { + } else if (dkctx->kms.req_type == MONGOCRYPT_KMS_KMIP_REGISTER + || dkctx->kms.req_type == MONGOCRYPT_KMS_KMIP_CREATE) { dkctx->kmip_unique_identifier = bson_strdup((const char *)dkctx->kms.result.data); return _kms_start(ctx); } else if (dkctx->kms.req_type == MONGOCRYPT_KMS_KMIP_ACTIVATE) { @@ -326,6 +405,9 @@ static bool _kms_done(mongocrypt_ctx_t *ctx) { } else if (dkctx->kms.req_type == MONGOCRYPT_KMS_KMIP_GET) { _mongocrypt_buffer_copy_to(&dkctx->kms.result, &dkctx->kmip_secretdata); return _kms_start(ctx); + } else if (dkctx->kms.req_type == MONGOCRYPT_KMS_KMIP_ENCRYPT) { + _mongocrypt_buffer_copy_to(&dkctx->kms.result, &dkctx->encrypted_key_material); + return _kms_start(ctx); } /* Store the result. */ @@ -475,7 +557,7 @@ bool mongocrypt_ctx_datakey_init(mongocrypt_ctx_t *ctx) { } } - if (_mongocrypt_needs_credentials_for_provider(ctx->crypt, ctx->opts.kek.kms_provider)) { + if (_mongocrypt_needs_credentials_for_provider(ctx->crypt, ctx->opts.kek.kms_provider, ctx->opts.kek.kmsid_name)) { ctx->state = MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS; } else if (!_kms_start(ctx)) { goto done; @@ -484,4 +566,4 @@ bool mongocrypt_ctx_datakey_init(mongocrypt_ctx_t *ctx) { ret = true; done: return ret; -} +} \ No newline at end of file diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-decrypt.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-decrypt.c index c37e51e5b32..cd696b3e900 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-decrypt.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-decrypt.c @@ -406,7 +406,7 @@ static bool _replace_ciphertext_with_plaintext(void *ctx, static bool _finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { bson_t as_bson, final_bson; - bson_iter_t iter; + bson_iter_t iter = {0}; _mongocrypt_ctx_decrypt_t *dctx; bool res; @@ -504,6 +504,7 @@ static bool _collect_K_KeyID_from_FLE2IndexedEncryptedValueV2(void *ctx, || (in->data[0] == MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2)); mc_FLE2IndexedEncryptedValueV2_t *iev = mc_FLE2IndexedEncryptedValueV2_new(); + _mongocrypt_buffer_t S_Key = {0}; CHECK_AND_RETURN(iev); CHECK_AND_RETURN(mc_FLE2IndexedEncryptedValueV2_parse(iev, in, status)); @@ -511,7 +512,6 @@ static bool _collect_K_KeyID_from_FLE2IndexedEncryptedValueV2(void *ctx, CHECK_AND_RETURN(S_KeyId); _mongocrypt_key_broker_t *kb = ctx; - _mongocrypt_buffer_t S_Key = {0}; CHECK_AND_RETURN_KB_STATUS(_mongocrypt_key_broker_decrypted_key_by_id(kb, S_KeyId, &S_Key)); /* Decrypt InnerEncrypted to get K_KeyId. */ @@ -537,6 +537,7 @@ static bool _collect_K_KeyID_from_FLE2IndexedEncryptedValue(void *ctx, BSON_ASSERT_PARAM(in); BSON_ASSERT(in->data); bool ret = false; + _mongocrypt_buffer_t S_Key = {0}; BSON_ASSERT((in->data[0] == MC_SUBTYPE_FLE2IndexedEqualityEncryptedValue) || (in->data[0] == MC_SUBTYPE_FLE2IndexedRangeEncryptedValue)); @@ -549,7 +550,6 @@ static bool _collect_K_KeyID_from_FLE2IndexedEncryptedValue(void *ctx, CHECK_AND_RETURN(S_KeyId); _mongocrypt_key_broker_t *kb = ctx; - _mongocrypt_buffer_t S_Key = {0}; CHECK_AND_RETURN_KB_STATUS(_mongocrypt_key_broker_decrypted_key_by_id(kb, S_KeyId, &S_Key)); /* Decrypt InnerEncrypted to get K_KeyId. */ @@ -604,7 +604,7 @@ static bool _check_for_K_KeyId(mongocrypt_ctx_t *ctx) { } bson_t as_bson; - bson_iter_t iter; + bson_iter_t iter = {0}; _mongocrypt_ctx_decrypt_t *dctx = (_mongocrypt_ctx_decrypt_t *)ctx; if (!_mongocrypt_buffer_to_bson(&dctx->original_doc, &as_bson)) { return _mongocrypt_ctx_fail_w_msg(ctx, "error converting original_doc to bson"); @@ -843,7 +843,7 @@ static bool _kms_done(mongocrypt_ctx_t *ctx) { bool mongocrypt_ctx_decrypt_init(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *doc) { _mongocrypt_ctx_decrypt_t *dctx; bson_t as_bson; - bson_iter_t iter; + bson_iter_t iter = {0}; _mongocrypt_ctx_opts_spec_t opts_spec; memset(&opts_spec, 0, sizeof(opts_spec)); diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-encrypt.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-encrypt.c index b6043996d3c..a8e67402947 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-encrypt.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-encrypt.c @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "mc-efc-private.h" #include "mc-fle2-rfds-private.h" #include "mc-tokens-private.h" #include "mongocrypt-ciphertext-private.h" @@ -23,6 +24,7 @@ #include "mongocrypt-marking-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, eccCollection, and @@ -30,7 +32,7 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx, bson_t *dst, bson_t *encryptedFieldConfig, - const char *coll_name, + const char *target_coll, mongocrypt_status_t *status) { bson_iter_t iter; bool has_escCollection = false; @@ -39,7 +41,7 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx, BSON_ASSERT_PARAM(dst); BSON_ASSERT_PARAM(encryptedFieldConfig); - BSON_ASSERT_PARAM(coll_name); + BSON_ASSERT_PARAM(target_coll); if (!bson_iter_init(&iter, encryptedFieldConfig)) { CLIENT_ERR("unable to iterate encryptedFieldConfig"); @@ -63,7 +65,7 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx, } if (!has_escCollection) { - char *default_escCollection = bson_strdup_printf("enxcol_.%s.esc", coll_name); + char *default_escCollection = bson_strdup_printf("enxcol_.%s.esc", target_coll); if (!BSON_APPEND_UTF8(dst, "escCollection", default_escCollection)) { CLIENT_ERR("unable to append escCollection"); bson_free(default_escCollection); @@ -72,7 +74,7 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx, bson_free(default_escCollection); } if (!has_eccCollection && !ctx->crypt->opts.use_fle2_v2) { - char *default_eccCollection = bson_strdup_printf("enxcol_.%s.ecc", coll_name); + char *default_eccCollection = bson_strdup_printf("enxcol_.%s.ecc", target_coll); if (!BSON_APPEND_UTF8(dst, "eccCollection", default_eccCollection)) { CLIENT_ERR("unable to append eccCollection"); bson_free(default_eccCollection); @@ -81,7 +83,7 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx, bson_free(default_eccCollection); } if (!has_ecocCollection) { - char *default_ecocCollection = bson_strdup_printf("enxcol_.%s.ecoc", coll_name); + char *default_ecocCollection = bson_strdup_printf("enxcol_.%s.ecoc", target_coll); if (!BSON_APPEND_UTF8(dst, "ecocCollection", default_ecocCollection)) { CLIENT_ERR("unable to append ecocCollection"); bson_free(default_ecocCollection); @@ -94,20 +96,20 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx, static bool _fle2_append_encryptionInformation(const mongocrypt_ctx_t *ctx, bson_t *dst, - const char *ns, + const char *target_ns, bson_t *encryptedFieldConfig, bson_t *deleteTokens, - const char *coll_name, + const char *target_coll, mongocrypt_status_t *status) { bson_t encryption_information_bson; bson_t schema_bson; bson_t encrypted_field_config_bson; BSON_ASSERT_PARAM(dst); - BSON_ASSERT_PARAM(ns); + BSON_ASSERT_PARAM(target_ns); BSON_ASSERT_PARAM(encryptedFieldConfig); /* deleteTokens may be NULL */ - BSON_ASSERT_PARAM(coll_name); + BSON_ASSERT_PARAM(target_coll); if (!BSON_APPEND_DOCUMENT_BEGIN(dst, "encryptionInformation", &encryption_information_bson)) { CLIENT_ERR("unable to begin appending 'encryptionInformation'"); @@ -122,7 +124,7 @@ static bool _fle2_append_encryptionInformation(const mongocrypt_ctx_t *ctx, return false; } - if (!BSON_APPEND_DOCUMENT_BEGIN(&schema_bson, ns, &encrypted_field_config_bson)) { + if (!BSON_APPEND_DOCUMENT_BEGIN(&schema_bson, target_ns, &encrypted_field_config_bson)) { CLIENT_ERR("unable to begin appending 'encryptedFieldConfig' to " "'encryptionInformation'.'schema'"); return false; @@ -131,7 +133,7 @@ static bool _fle2_append_encryptionInformation(const mongocrypt_ctx_t *ctx, if (!_fle2_append_encryptedFieldConfig(ctx, &encrypted_field_config_bson, encryptedFieldConfig, - coll_name, + target_coll, status)) { return false; } @@ -153,8 +155,8 @@ static bool _fle2_append_encryptionInformation(const mongocrypt_ctx_t *ctx, "'encryptionInformation'"); return false; } - if (!BSON_APPEND_DOCUMENT(&delete_tokens_bson, ns, deleteTokens)) { - CLIENT_ERR("unable to append '%s' to 'deleteTokens'", ns); + if (!BSON_APPEND_DOCUMENT(&delete_tokens_bson, target_ns, deleteTokens)) { + CLIENT_ERR("unable to append '%s' to 'deleteTokens'", target_ns); return false; } if (!bson_append_document_end(&encryption_information_bson, &delete_tokens_bson)) { @@ -178,12 +180,12 @@ typedef enum { MC_TO_CSFLE, MC_TO_MONGOCRYPTD, MC_TO_MONGOD } mc_cmd_target_t; * * @param cmd_name The name of the command. * @param cmd The command being rewritten. It is an input and output. - * @param ns The . namespace for the command. + * @param target_ns The . namespace for the command. * @param encryptedFieldConfig The "encryptedFields" document for the * collection. * @param deleteTokens Delete tokens to append to "encryptionInformation". May * be NULL. - * @param coll_name The collection name. + * @param target_coll The collection name. * @param cmd_target The intended destination of the command. csfle, * mongocryptd, and mongod have different requirements for the location of * "encryptionInformation". @@ -194,10 +196,10 @@ typedef enum { MC_TO_CSFLE, MC_TO_MONGOCRYPTD, MC_TO_MONGOD } mc_cmd_target_t; static bool _fle2_insert_encryptionInformation(const mongocrypt_ctx_t *ctx, const char *cmd_name, bson_t *cmd /* in and out */, - const char *ns, + const char *target_ns, bson_t *encryptedFieldConfig, bson_t *deleteTokens, - const char *coll_name, + const char *target_coll, mc_cmd_target_t cmd_target, mongocrypt_status_t *status) { bson_t out = BSON_INITIALIZER; @@ -207,16 +209,96 @@ static bool _fle2_insert_encryptionInformation(const mongocrypt_ctx_t *ctx, BSON_ASSERT_PARAM(cmd_name); BSON_ASSERT_PARAM(cmd); - BSON_ASSERT_PARAM(ns); + BSON_ASSERT_PARAM(target_ns); BSON_ASSERT_PARAM(encryptedFieldConfig); /* deleteTokens may be NULL */ - BSON_ASSERT_PARAM(coll_name); + BSON_ASSERT_PARAM(target_coll); + + // For `bulkWrite`, append `encryptionInformation` inside the `nsInfo.0` document. + if (0 == strcmp(cmd_name, "bulkWrite")) { + // Get the single `nsInfo` document from the input command. + bson_t nsInfo; // Non-owning. + { + bson_iter_t nsInfo_iter; + if (!bson_iter_init(&nsInfo_iter, cmd)) { + CLIENT_ERR("failed to iterate command"); + goto fail; + } + if (!bson_iter_find_descendant(&nsInfo_iter, "nsInfo.0", &nsInfo_iter)) { + CLIENT_ERR("expected one namespace in `bulkWrite`, but found zero."); + goto fail; + } + if (bson_has_field(cmd, "nsInfo.1")) { + CLIENT_ERR( + "expected one namespace in `bulkWrite`, but found more than one. Only one namespace is supported."); + goto fail; + } + if (!mc_iter_document_as_bson(&nsInfo_iter, &nsInfo, status)) { + goto fail; + } + // Ensure `nsInfo` does not already have an `encryptionInformation` field. + if (bson_has_field(&nsInfo, "encryptionInformation")) { + CLIENT_ERR("unexpected `encryptionInformation` present in input `nsInfo`."); + goto fail; + } + } + + // Copy input and append `encryptionInformation` to `nsInfo`. + { + // Append everything from input except `nsInfo`. + bson_copy_to_excluding_noinit(cmd, &out, "nsInfo", NULL); + // Append `nsInfo` array. + bson_t nsInfo_array; + if (!BSON_APPEND_ARRAY_BEGIN(&out, "nsInfo", &nsInfo_array)) { + CLIENT_ERR("unable to begin appending 'nsInfo' array"); + goto fail; + } + bson_t nsInfo_array_0; + if (!BSON_APPEND_DOCUMENT_BEGIN(&nsInfo_array, "0", &nsInfo_array_0)) { + CLIENT_ERR("unable to append 'nsInfo.0' document"); + goto fail; + } + // Copy everything from input `nsInfo`. + bson_concat(&nsInfo_array_0, &nsInfo); + // And append `encryptionInformation`. + if (!_fle2_append_encryptionInformation(ctx, + &nsInfo_array_0, + target_ns, + encryptedFieldConfig, + deleteTokens, + target_coll, + status)) { + goto fail; + } + if (!bson_append_document_end(&nsInfo_array, &nsInfo_array_0)) { + CLIENT_ERR("unable to end appending 'nsInfo' document in array"); + } + if (!bson_append_array_end(&out, &nsInfo_array)) { + CLIENT_ERR("unable to end appending 'nsInfo' array"); + goto fail; + } + // Overwrite `cmd`. + bson_destroy(cmd); + if (!bson_steal(cmd, &out)) { + CLIENT_ERR("failed to steal BSON with encryptionInformation"); + goto fail; + } + } + + goto success; + } if (0 != strcmp(cmd_name, "explain") || cmd_target == MC_TO_MONGOCRYPTD) { - // All commands except "explain" expect "encryptionInformation" + // All commands except "explain" and "bulkWrite" expect "encryptionInformation" // at top-level. "explain" sent to mongocryptd expects // "encryptionInformation" at top-level. - if (!_fle2_append_encryptionInformation(ctx, cmd, ns, encryptedFieldConfig, deleteTokens, coll_name, status)) { + if (!_fle2_append_encryptionInformation(ctx, + cmd, + target_ns, + encryptedFieldConfig, + deleteTokens, + target_coll, + status)) { goto fail; } bson_destroy(&out); @@ -253,7 +335,13 @@ static bool _fle2_insert_encryptionInformation(const mongocrypt_ctx_t *ctx, bson_copy_to(&tmp, &explain); } - if (!_fle2_append_encryptionInformation(ctx, &explain, ns, encryptedFieldConfig, deleteTokens, coll_name, status)) { + if (!_fle2_append_encryptionInformation(ctx, + &explain, + target_ns, + encryptedFieldConfig, + deleteTokens, + target_coll, + status)) { goto fail; } @@ -265,7 +353,7 @@ static bool _fle2_insert_encryptionInformation(const mongocrypt_ctx_t *ctx, bson_copy_to_excluding_noinit(cmd, &out, "explain", NULL); bson_destroy(cmd); if (!bson_steal(cmd, &out)) { - CLIENT_ERR("failed to steal BSON without encryptionInformation"); + CLIENT_ERR("failed to steal BSON with encryptionInformation"); goto fail; } @@ -288,7 +376,7 @@ static bool _mongo_op_collinfo(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) BSON_ASSERT_PARAM(out); ectx = (_mongocrypt_ctx_encrypt_t *)ctx; - cmd = BCON_NEW("name", BCON_UTF8(ectx->coll_name)); + cmd = BCON_NEW("name", BCON_UTF8(ectx->target_coll)); CRYPT_TRACEF(&ectx->parent.crypt->log, "constructed: %s\n", tmp_json(cmd)); _mongocrypt_buffer_steal_from_bson(&ectx->list_collections_filter, cmd); out->data = ectx->list_collections_filter.data; @@ -328,10 +416,46 @@ static bool _set_schema_from_collinfo(mongocrypt_ctx_t *ctx, bson_t *collinfo) { if (!_mongocrypt_buffer_to_bson(&ectx->encrypted_field_config, &efc_bson)) { return _mongocrypt_ctx_fail_w_msg(ctx, "unable to create BSON from encrypted_field_config"); } - if (!mc_EncryptedFieldConfig_parse(&ectx->efc, &efc_bson, ctx->status)) { + if (!mc_EncryptedFieldConfig_parse(&ectx->efc, &efc_bson, ctx->status, ctx->crypt->opts.use_range_v2)) { _mongocrypt_ctx_fail(ctx); return false; } + } else if (0 == strcmp(ectx->cmd_name, "bulkWrite")) { + ectx->used_empty_encryptedFields = true; + // `bulkWrite` is a special case. Sending `bulkWrite` with `jsonSchema` to query analysis results in an error: + // `The bulkWrite command only supports Queryable Encryption` + // + // Add an empty encryptedFields (rather than an empty JSON schema) to ensure `bulkWrite` can be sent to query + // analysis. + bson_t empty_encryptedFields = BSON_INITIALIZER; + { + char *escCollection = bson_strdup_printf("enxcol_.%s.esc", ectx->target_coll); + char *ecocCollection = bson_strdup_printf("enxcol_.%s.ecoc", ectx->target_coll); + bson_t empty_array = BSON_INITIALIZER; + if (!BSON_APPEND_UTF8(&empty_encryptedFields, "escCollection", escCollection)) { + return _mongocrypt_ctx_fail_w_msg(ctx, "failed to append `escCollection`"); + } + if (!BSON_APPEND_UTF8(&empty_encryptedFields, "ecocCollection", ecocCollection)) { + return _mongocrypt_ctx_fail_w_msg(ctx, "failed to append `ecocCollection`"); + } + if (!BSON_APPEND_ARRAY(&empty_encryptedFields, "fields", &empty_array)) { + return _mongocrypt_ctx_fail_w_msg(ctx, "failed to append `fields`"); + } + + bson_destroy(&empty_array); + bson_free(escCollection); + bson_free(ecocCollection); + } + + if (!mc_EncryptedFieldConfig_parse(&ectx->efc, + &empty_encryptedFields, + ctx->status, + ctx->crypt->opts.use_range_v2)) { + bson_destroy(&empty_encryptedFields); + _mongocrypt_ctx_fail(ctx); + return false; + } + _mongocrypt_buffer_steal_from_bson(&ectx->encrypted_field_config, &empty_encryptedFields); } BSON_ASSERT(bson_iter_init(&iter, collinfo)); @@ -458,7 +582,7 @@ static bool _fle2_collect_keys_for_deleteTokens(mongocrypt_ctx_t *ctx) { mc_EncryptedField_t *field; for (field = ectx->efc.fields; field != NULL; field = field->next) { - if (field->has_queries) { + if (field->supported_queries) { if (!_mongocrypt_key_broker_request_id(&ctx->kb, &field->keyId)) { _mongocrypt_key_broker_status(&ctx->kb, ctx->status); _mongocrypt_ctx_fail(ctx); @@ -469,9 +593,10 @@ static bool _fle2_collect_keys_for_deleteTokens(mongocrypt_ctx_t *ctx) { return true; } -/* _fle2_collect_keys_for_compact requests keys required to produce - * compactionTokens. compactionTokens is only applicable to FLE 2. */ -static bool _fle2_collect_keys_for_compact(mongocrypt_ctx_t *ctx) { +/* _fle2_collect_keys_for_compaction requests keys required to produce + * compactionTokens or cleanupTokens. + * compactionTokens and cleanupTokens are only applicable to FLE 2. */ +static bool _fle2_collect_keys_for_compaction(mongocrypt_ctx_t *ctx) { _mongocrypt_ctx_encrypt_t *ectx = (_mongocrypt_ctx_encrypt_t *)ctx; BSON_ASSERT_PARAM(ctx); @@ -483,11 +608,12 @@ static bool _fle2_collect_keys_for_compact(mongocrypt_ctx_t *ctx) { const char *cmd_name = ectx->cmd_name; - if (0 != strcmp(cmd_name, "compactStructuredEncryptionData")) { + if (0 != strcmp(cmd_name, "compactStructuredEncryptionData") + && 0 != strcmp(cmd_name, "cleanupStructuredEncryptionData")) { return true; } - /* compactStructuredEncryptionData must not be sent to mongocryptd. */ + /* (compact/cleanup)StructuredEncryptionData must not be sent to mongocryptd. */ ectx->bypass_query_analysis = true; mc_EncryptedField_t *field; @@ -516,7 +642,7 @@ static bool _mongo_feed_collinfo(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *in) } /* Cache the received collinfo. */ - if (!_mongocrypt_cache_add_copy(&ctx->crypt->cache_collinfo, ectx->ns, &as_bson, ctx->status)) { + if (!_mongocrypt_cache_add_copy(&ctx->crypt->cache_collinfo, ectx->target_ns, &as_bson, ctx->status)) { return _mongocrypt_ctx_fail(ctx); } @@ -538,8 +664,12 @@ static bool _mongo_done_collinfo(mongocrypt_ctx_t *ctx) { if (_mongocrypt_buffer_empty(&ectx->schema)) { bson_t empty_collinfo = BSON_INITIALIZER; - /* If no collinfo was fed, cache an empty collinfo. */ - if (!_mongocrypt_cache_add_copy(&ctx->crypt->cache_collinfo, ectx->ns, &empty_collinfo, ctx->status)) { + /* If no collinfo was fed, apply and cache an empty collinfo. */ + if (!_set_schema_from_collinfo(ctx, &empty_collinfo)) { + bson_destroy(&empty_collinfo); + return false; + } + if (!_mongocrypt_cache_add_copy(&ctx->crypt->cache_collinfo, ectx->target_ns, &empty_collinfo, ctx->status)) { bson_destroy(&empty_collinfo); return _mongocrypt_ctx_fail(ctx); } @@ -550,7 +680,7 @@ static bool _mongo_done_collinfo(mongocrypt_ctx_t *ctx) { return false; } - if (!_fle2_collect_keys_for_compact(ctx)) { + if (!_fle2_collect_keys_for_compaction(ctx)) { return false; } @@ -564,6 +694,19 @@ static bool _mongo_done_collinfo(mongocrypt_ctx_t *ctx) { return _try_run_csfle_marking(ctx); } +static const char *_mongo_db_collinfo(mongocrypt_ctx_t *ctx) { + _mongocrypt_ctx_encrypt_t *ectx; + + BSON_ASSERT_PARAM(ctx); + + ectx = (_mongocrypt_ctx_encrypt_t *)ctx; + if (!ectx->target_db) { + _mongocrypt_ctx_fail_w_msg(ctx, "Expected target database for `listCollections`, but none exists."); + return NULL; + } + return ectx->target_db; +} + static bool _fle2_mongo_op_markings(mongocrypt_ctx_t *ctx, bson_t *out) { _mongocrypt_ctx_encrypt_t *ectx; bson_t cmd_bson = BSON_INITIALIZER, encrypted_field_config_bson = BSON_INITIALIZER; @@ -594,10 +737,10 @@ static bool _fle2_mongo_op_markings(mongocrypt_ctx_t *ctx, bson_t *out) { if (!_fle2_insert_encryptionInformation(ctx, cmd_name, out, - ectx->ns, + ectx->target_ns, &encrypted_field_config_bson, NULL /* deleteTokens */, - ectx->coll_name, + ectx->target_coll, ctx->crypt->csfle.okay ? MC_TO_CSFLE : MC_TO_MONGOCRYPTD, ctx->status)) { return _mongocrypt_ctx_fail(ctx); @@ -734,7 +877,7 @@ static bool _collect_key_from_marking(void *ctx, _mongocrypt_buffer_t *in, mongo static bool _mongo_feed_markings(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *in) { /* Find keys. */ bson_t as_bson; - bson_iter_t iter; + bson_iter_t iter = {0}; _mongocrypt_ctx_encrypt_t *ectx; BSON_ASSERT_PARAM(ctx); @@ -823,7 +966,7 @@ static bool _mongo_done_markings(mongocrypt_ctx_t *ctx) { /** * @brief Append $db to a command being passed to csfle. */ -static bool _add_dollar_db(const char *cmd_name, bson_t *cmd, const char *db_name, mongocrypt_status_t *status) { +static bool _add_dollar_db(const char *cmd_name, bson_t *cmd, const char *cmd_db, mongocrypt_status_t *status) { bson_t out = BSON_INITIALIZER; bson_t explain = BSON_INITIALIZER; bson_iter_t iter; @@ -831,10 +974,10 @@ static bool _add_dollar_db(const char *cmd_name, bson_t *cmd, const char *db_nam BSON_ASSERT_PARAM(cmd_name); BSON_ASSERT_PARAM(cmd); - BSON_ASSERT_PARAM(db_name); + BSON_ASSERT_PARAM(cmd_db); if (!bson_iter_init_find(&iter, cmd, "$db")) { - if (!BSON_APPEND_UTF8(cmd, "$db", db_name)) { + if (!BSON_APPEND_UTF8(cmd, "$db", cmd_db)) { CLIENT_ERR("failed to append '$db'"); goto fail; } @@ -868,7 +1011,7 @@ static bool _add_dollar_db(const char *cmd_name, bson_t *cmd, const char *db_nam bson_copy_to(&tmp, &explain); } - if (!BSON_APPEND_UTF8(&explain, "$db", db_name)) { + if (!BSON_APPEND_UTF8(&explain, "$db", cmd_db)) { CLIENT_ERR("failed to append '$db'"); goto fail; } @@ -947,7 +1090,7 @@ static bool _try_run_csfle_marking(mongocrypt_ctx_t *ctx) { const char *cmd_name = ectx->cmd_name; - if (!_add_dollar_db(cmd_name, &cmd, ectx->db_name, ctx->status)) { + if (!_add_dollar_db(cmd_name, &cmd, ectx->cmd_db, ctx->status)) { _mongocrypt_ctx_fail(ctx); goto fail_create_cmd; } @@ -975,8 +1118,12 @@ static bool _try_run_csfle_marking(mongocrypt_ctx_t *ctx) { CHECK_CSFLE_ERROR("query_analyzer_create", fail_qa_create); uint32_t marked_bson_len = 0; - uint8_t *marked_bson = - csfle.analyze_query(qa, bson_get_data(&cmd), ectx->ns, (uint32_t)strlen(ectx->ns), &marked_bson_len, status); + uint8_t *marked_bson = csfle.analyze_query(qa, + bson_get_data(&cmd), + ectx->target_ns, + (uint32_t)strlen(ectx->target_ns), + &marked_bson_len, + status); CHECK_CSFLE_ERROR("analyze_query", fail_analyze_query); // Copy out the marked document. @@ -1067,14 +1214,12 @@ fail: static bool _replace_marking_with_ciphertext(void *ctx, _mongocrypt_buffer_t *in, bson_value_t *out, mongocrypt_status_t *status) { - _mongocrypt_marking_t marking; + _mongocrypt_marking_t marking = {0}; bool ret; BSON_ASSERT_PARAM(ctx); BSON_ASSERT_PARAM(in); - memset(&marking, 0, sizeof(marking)); - if (!_mongocrypt_marking_parse_unowned(in, &marking, status)) { _mongocrypt_marking_cleanup(&marking); return false; @@ -1107,7 +1252,7 @@ static bson_t *generate_delete_tokens(_mongocrypt_crypto_t *crypto, mc_ECOCToken_t *ecoc = NULL; bool loop_ok = false; /* deleteTokens are only necessary for indexed fields. */ - if (!ef->has_queries) { + if (!ef->supported_queries) { goto loop_continue; } @@ -1223,8 +1368,11 @@ typedef struct { // must_omit_encryptionInformation returns true if the command // must omit the "encryptionInformation" field when sent to mongod / mongos. -static moe_result -must_omit_encryptionInformation(const char *command_name, const bson_t *command, mongocrypt_status_t *status) { +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. const char *eligible_commands[] = {"find", "aggregate", "distinct", "count", "insert"}; @@ -1232,10 +1380,28 @@ must_omit_encryptionInformation(const char *command_name, const bson_t *command, bool found = false; // prohibited_commands prohibit encryptionInformation on mongod / mongos. - const char *prohibited_commands[] = {"compactStructuredEncryptionData", "create", "collMod", "createIndexes"}; + const char *prohibited_commands[] = {"cleanupStructuredEncryptionData", "create", "collMod", "createIndexes"}; BSON_ASSERT_PARAM(command_name); BSON_ASSERT_PARAM(command); + BSON_ASSERT_PARAM(efc); + + 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++) { if (0 == strcmp(prohibited_commands[i], command_name)) { @@ -1254,7 +1420,7 @@ must_omit_encryptionInformation(const char *command_name, const bson_t *command, } bool has_payload_requiring_encryptionInformation = false; - bson_iter_t iter; + bson_iter_t iter = {0}; if (!bson_iter_init(&iter, command)) { CLIENT_ERR("unable to iterate command"); return (moe_result){.ok = false}; @@ -1274,35 +1440,46 @@ must_omit_encryptionInformation(const char *command_name, const bson_t *command, } /* _fle2_append_compactionTokens appends compactionTokens if command_name is - * "compactStructuredEncryptionData" */ -static bool _fle2_append_compactionTokens(_mongocrypt_crypto_t *crypto, + * "compactStructuredEncryptionData" or cleanupTokens if command_name is + * "cleanupStructuredEncryptionData" + */ +static bool _fle2_append_compactionTokens(mongocrypt_t *crypt, _mongocrypt_key_broker_t *kb, mc_EncryptedFieldConfig_t *efc, const char *command_name, bson_t *out, mongocrypt_status_t *status) { - bson_t result_compactionTokens; + bson_t result_compactionTokens = BSON_INITIALIZER; bool ret = false; - BSON_ASSERT_PARAM(crypto); + BSON_ASSERT_PARAM(crypt); BSON_ASSERT_PARAM(kb); BSON_ASSERT_PARAM(efc); BSON_ASSERT_PARAM(command_name); BSON_ASSERT_PARAM(out); + _mongocrypt_crypto_t *crypto = crypt->crypto; - if (0 != strcmp(command_name, "compactStructuredEncryptionData")) { + bool cleanup = (0 == strcmp(command_name, "cleanupStructuredEncryptionData")); + + if (0 != strcmp(command_name, "compactStructuredEncryptionData") && !cleanup) { return true; } - BSON_APPEND_DOCUMENT_BEGIN(out, "compactionTokens", &result_compactionTokens); + if (cleanup) { + BSON_APPEND_DOCUMENT_BEGIN(out, "cleanupTokens", &result_compactionTokens); + } else { + BSON_APPEND_DOCUMENT_BEGIN(out, "compactionTokens", &result_compactionTokens); + } mc_EncryptedField_t *ptr; for (ptr = efc->fields; ptr != NULL; ptr = ptr->next) { - /* Append ECOC token. */ + /* Append tokens. */ _mongocrypt_buffer_t key = {0}; _mongocrypt_buffer_t tokenkey = {0}; mc_CollectionsLevel1Token_t *cl1t = NULL; mc_ECOCToken_t *ecoct = NULL; + mc_ESCToken_t *esct = NULL; + mc_AnchorPaddingTokenRoot_t *padt = NULL; bool ecoc_ok = false; if (!_mongocrypt_key_broker_decrypted_key_by_id(kb, &ptr->keyId, &key)) { @@ -1333,10 +1510,35 @@ static bool _fle2_append_compactionTokens(_mongocrypt_crypto_t *crypto, const _mongocrypt_buffer_t *ecoct_buf = mc_ECOCToken_get(ecoct); - BSON_APPEND_BINARY(&result_compactionTokens, ptr->path, BSON_SUBTYPE_BINARY, ecoct_buf->data, ecoct_buf->len); + if (crypt->opts.use_range_v2 && (ptr->supported_queries & SUPPORTS_RANGE_QUERIES)) { + // Append the document {ecoc: , anchorPaddingToken: } + esct = mc_ESCToken_new(crypto, cl1t, status); + if (!esct) { + goto ecoc_fail; + } + padt = mc_AnchorPaddingTokenRoot_new(crypto, esct, status); + if (!padt) { + goto ecoc_fail; + } + const _mongocrypt_buffer_t *padt_buf = mc_AnchorPaddingTokenRoot_get(padt); + bson_t tokenDoc; + BSON_APPEND_DOCUMENT_BEGIN(&result_compactionTokens, ptr->path, &tokenDoc); + BSON_APPEND_BINARY(&tokenDoc, "ecoc", BSON_SUBTYPE_BINARY, ecoct_buf->data, ecoct_buf->len); + BSON_APPEND_BINARY(&tokenDoc, "anchorPaddingToken", BSON_SUBTYPE_BINARY, padt_buf->data, padt_buf->len); + bson_append_document_end(&result_compactionTokens, &tokenDoc); + } else { + // Append just + BSON_APPEND_BINARY(&result_compactionTokens, + ptr->path, + BSON_SUBTYPE_BINARY, + ecoct_buf->data, + ecoct_buf->len); + } ecoc_ok = true; ecoc_fail: + mc_AnchorPaddingTokenRoot_destroy(padt); + mc_ESCToken_destroy(esct); mc_ECOCToken_destroy(ecoct); mc_CollectionsLevel1Token_destroy(cl1t); _mongocrypt_buffer_cleanup(&key); @@ -1363,11 +1565,63 @@ _fle2_strip_encryptionInformation(const char *cmd_name, bson_t *cmd /* in and ou BSON_ASSERT_PARAM(cmd_name); BSON_ASSERT_PARAM(cmd); - if (0 != strcmp(cmd_name, "explain")) { + if (0 != strcmp(cmd_name, "explain") && 0 != strcmp(cmd_name, "bulkWrite")) { bson_copy_to_excluding_noinit(cmd, &stripped, "encryptionInformation", NULL); goto success; } + if (0 == strcmp(cmd_name, "bulkWrite")) { + // Get the single `nsInfo` document from the input command. + bson_t nsInfo; // Non-owning. + { + bson_iter_t nsInfo_iter; + if (!bson_iter_init(&nsInfo_iter, cmd)) { + CLIENT_ERR("failed to iterate command"); + goto fail; + } + if (!bson_iter_find_descendant(&nsInfo_iter, "nsInfo.0", &nsInfo_iter)) { + CLIENT_ERR("expected one namespace in `bulkWrite`, but found zero."); + goto fail; + } + if (bson_has_field(cmd, "nsInfo.1")) { + CLIENT_ERR( + "expected one namespace in `bulkWrite`, but found more than one. Only one namespace is supported."); + goto fail; + } + if (!mc_iter_document_as_bson(&nsInfo_iter, &nsInfo, status)) { + goto fail; + } + } + + // Copy input and exclude `encryptionInformation` from `nsInfo`. + { + // Append everything from input except `nsInfo`. + bson_copy_to_excluding_noinit(cmd, &stripped, "nsInfo", NULL); + // Append `nsInfo` array. + bson_t nsInfo_array; + if (!BSON_APPEND_ARRAY_BEGIN(&stripped, "nsInfo", &nsInfo_array)) { + CLIENT_ERR("unable to begin appending 'nsInfo' array"); + goto fail; + } + bson_t nsInfo_array_0; + if (!BSON_APPEND_DOCUMENT_BEGIN(&nsInfo_array, "0", &nsInfo_array_0)) { + CLIENT_ERR("unable to append 'nsInfo.0' document"); + goto fail; + } + // Copy everything from input `nsInfo` and exclude `encryptionInformation`. + bson_copy_to_excluding_noinit(&nsInfo, &nsInfo_array_0, "encryptionInformation", NULL); + if (!bson_append_document_end(&nsInfo_array, &nsInfo_array_0)) { + CLIENT_ERR("unable to end appending 'nsInfo' document in array"); + } + if (!bson_append_array_end(&stripped, &nsInfo_array)) { + CLIENT_ERR("unable to end appending 'nsInfo' array"); + goto fail; + } + } + + goto success; + } + // The 'explain' command is a special case. // 'encryptionInformation' is returned from mongocryptd and csfle nested // inside 'explain'. Example: @@ -1451,7 +1705,7 @@ static bool _fle2_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { bson_copy_to(&original_cmd_bson, &converted); } else { bson_t as_bson; - bson_iter_t iter; + bson_iter_t iter = {0}; if (!_mongocrypt_buffer_to_bson(&ectx->marked_cmd, &as_bson)) { return _mongocrypt_ctx_fail_w_msg(ctx, "malformed bson"); @@ -1488,7 +1742,11 @@ static bool _fle2_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { } } - moe_result result = must_omit_encryptionInformation(command_name, &converted, 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); @@ -1496,14 +1754,14 @@ static bool _fle2_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { } /* Append a new 'encryptionInformation'. */ - if (!result.must_omit) { + if (!result.must_omit && !ectx->used_empty_encryptedFields) { if (!_fle2_insert_encryptionInformation(ctx, command_name, &converted, - ectx->ns, + ectx->target_ns, &encrypted_field_config_bson, deleteTokens, - ectx->coll_name, + ectx->target_coll, MC_TO_MONGOD, ctx->status)) { bson_destroy(&converted); @@ -1513,12 +1771,7 @@ static bool _fle2_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { } bson_destroy(deleteTokens); - if (!_fle2_append_compactionTokens(ctx->crypt->crypto, - &ctx->kb, - &ectx->efc, - command_name, - &converted, - ctx->status)) { + if (!_fle2_append_compactionTokens(ctx->crypt, &ctx->kb, &ectx->efc, command_name, &converted, ctx->status)) { bson_destroy(&converted); return _mongocrypt_ctx_fail(ctx); } @@ -1527,7 +1780,7 @@ static bool _fle2_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { bson_iter_t iter; if (bson_iter_init_find(&iter, &original_cmd_bson, "$db")) { if (!bson_iter_init_find(&iter, &converted, "$db")) { - BSON_APPEND_UTF8(&converted, "$db", ectx->db_name); + BSON_APPEND_UTF8(&converted, "$db", ectx->cmd_db); } } @@ -1545,6 +1798,9 @@ static bool FLE2RangeFindDriverSpec_to_ciphertexts(mongocrypt_ctx_t *ctx, mongoc BSON_ASSERT_PARAM(ctx); BSON_ASSERT_PARAM(out); + bson_t with_placholders = BSON_INITIALIZER; + bson_t with_ciphertexts = BSON_INITIALIZER; + if (!ctx->opts.rangeopts.set) { _mongocrypt_ctx_fail_w_msg(ctx, "Expected RangeOpts to be set for Range Find"); goto fail; @@ -1554,8 +1810,6 @@ static bool FLE2RangeFindDriverSpec_to_ciphertexts(mongocrypt_ctx_t *ctx, mongoc goto fail; } - bson_t with_placholders = BSON_INITIALIZER; - bson_t with_ciphertexts = BSON_INITIALIZER; bson_t in_bson; if (!_mongocrypt_buffer_to_bson(&ectx->original_cmd, &in_bson)) { _mongocrypt_ctx_fail_w_msg(ctx, "unable to convert input to BSON"); @@ -1663,7 +1917,13 @@ static bool _fle2_finalize_explicit(mongocrypt_ctx_t *ctx, mongocrypt_binary_t * marking.type = MONGOCRYPT_MARKING_FLE2_ENCRYPTION; if (ctx->opts.query_type.set) { switch (ctx->opts.query_type.value) { - case MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW: + case MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_DEPRECATED: + if (ctx->crypt->opts.use_range_v2) { + _mongocrypt_ctx_fail_w_msg(ctx, "Cannot use rangePreview query type with Range V2"); + goto fail; + } + // fallthrough + case MONGOCRYPT_QUERY_TYPE_RANGE: case MONGOCRYPT_QUERY_TYPE_EQUALITY: marking.fle2.type = MONGOCRYPT_FLE2_PLACEHOLDER_TYPE_FIND; break; default: _mongocrypt_ctx_fail_w_msg(ctx, "Invalid value for EncryptOpts.queryType"); goto fail; } @@ -1674,7 +1934,13 @@ static bool _fle2_finalize_explicit(mongocrypt_ctx_t *ctx, mongocrypt_binary_t * switch (ctx->opts.index_type.value) { case MONGOCRYPT_INDEX_TYPE_EQUALITY: marking.fle2.algorithm = MONGOCRYPT_FLE2_ALGORITHM_EQUALITY; break; case MONGOCRYPT_INDEX_TYPE_NONE: marking.fle2.algorithm = MONGOCRYPT_FLE2_ALGORITHM_UNINDEXED; break; - case MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW: marking.fle2.algorithm = MONGOCRYPT_FLE2_ALGORITHM_RANGE; break; + case MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW_DEPRECATED: + if (ctx->crypt->opts.use_range_v2) { + _mongocrypt_ctx_fail_w_msg(ctx, "Cannot use rangePreview index type with Range V2"); + goto fail; + } + // fallthrough + case MONGOCRYPT_INDEX_TYPE_RANGE: marking.fle2.algorithm = MONGOCRYPT_FLE2_ALGORITHM_RANGE; break; default: // This might be unreachable because of other validation. Better safe than // sorry. @@ -1695,7 +1961,11 @@ static bool _fle2_finalize_explicit(mongocrypt_ctx_t *ctx, mongocrypt_binary_t * // RangeOpts with query_type is handled above. BSON_ASSERT(!ctx->opts.query_type.set); - if (!mc_RangeOpts_to_FLE2RangeInsertSpec(&ctx->opts.rangeopts.value, &old_v, &new_v, ctx->status)) { + if (!mc_RangeOpts_to_FLE2RangeInsertSpec(&ctx->opts.rangeopts.value, + &old_v, + &new_v, + ctx->crypt->opts.use_range_v2, + ctx->status)) { _mongocrypt_ctx_fail(ctx); goto fail; } @@ -1730,7 +2000,7 @@ static bool _fle2_finalize_explicit(mongocrypt_ctx_t *ctx, mongocrypt_binary_t * } if (ctx->opts.contention_factor.set) { - marking.fle2.maxContentionCounter = ctx->opts.contention_factor.value; + marking.fle2.maxContentionFactor = ctx->opts.contention_factor.value; } else if (ctx->opts.index_type.value == MONGOCRYPT_INDEX_TYPE_EQUALITY) { _mongocrypt_ctx_fail_w_msg(ctx, "contention factor required for indexed algorithm"); goto fail; @@ -1764,7 +2034,7 @@ fail: static bool _finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { bson_t as_bson, converted; - bson_iter_t iter; + bson_iter_t iter = {0}; _mongocrypt_ctx_encrypt_t *ectx; bool res; @@ -1811,7 +2081,7 @@ static bool _finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { bson_iter_t iter; if (bson_iter_init_find(&iter, &original_cmd_bson, "$db")) { if (!bson_iter_init_find(&iter, &converted, "$db")) { - BSON_APPEND_UTF8(&converted, "$db", ectx->db_name); + BSON_APPEND_UTF8(&converted, "$db", ectx->cmd_db); } } } else { @@ -1869,9 +2139,10 @@ static void _cleanup(mongocrypt_ctx_t *ctx) { } ectx = (_mongocrypt_ctx_encrypt_t *)ctx; - bson_free(ectx->ns); - bson_free(ectx->db_name); - bson_free(ectx->coll_name); + bson_free(ectx->target_ns); + bson_free(ectx->cmd_db); + bson_free(ectx->target_db); + bson_free(ectx->target_coll); _mongocrypt_buffer_cleanup(&ectx->list_collections_filter); _mongocrypt_buffer_cleanup(&ectx->schema); _mongocrypt_buffer_cleanup(&ectx->encrypted_field_config); @@ -1903,7 +2174,7 @@ static bool _try_schema_from_schema_map(mongocrypt_ctx_t *ctx) { return _mongocrypt_ctx_fail_w_msg(ctx, "malformed schema map"); } - if (bson_iter_init_find(&iter, &schema_map, ectx->ns)) { + if (bson_iter_init_find(&iter, &schema_map, ectx->target_ns)) { if (!_mongocrypt_buffer_copy_from_document_iter(&ectx->schema, &iter)) { return _mongocrypt_ctx_fail_w_msg(ctx, "malformed schema map"); } @@ -1939,7 +2210,7 @@ static bool _fle2_try_encrypted_field_config_from_map(mongocrypt_ctx_t *ctx) { return _mongocrypt_ctx_fail_w_msg(ctx, "unable to convert encrypted_field_config_map to BSON"); } - if (bson_iter_init_find(&iter, &encrypted_field_config_map, ectx->ns)) { + if (bson_iter_init_find(&iter, &encrypted_field_config_map, ectx->target_ns)) { if (!_mongocrypt_buffer_copy_from_document_iter(&ectx->encrypted_field_config, &iter)) { return _mongocrypt_ctx_fail_w_msg(ctx, "unable to copy encrypted_field_config from " @@ -1949,7 +2220,7 @@ static bool _fle2_try_encrypted_field_config_from_map(mongocrypt_ctx_t *ctx) { if (!_mongocrypt_buffer_to_bson(&ectx->encrypted_field_config, &efc_bson)) { return _mongocrypt_ctx_fail_w_msg(ctx, "unable to create BSON from encrypted_field_config"); } - if (!mc_EncryptedFieldConfig_parse(&ectx->efc, &efc_bson, ctx->status)) { + if (!mc_EncryptedFieldConfig_parse(&ectx->efc, &efc_bson, ctx->status, ctx->crypt->opts.use_range_v2)) { _mongocrypt_ctx_fail(ctx); return false; } @@ -1970,18 +2241,32 @@ static bool _try_schema_from_cache(mongocrypt_ctx_t *ctx) { /* Otherwise, we need a remote schema. Check if we have a response to * listCollections cached. */ - if (!_mongocrypt_cache_get(&ctx->crypt->cache_collinfo, ectx->ns /* null terminated */, (void **)&collinfo)) { + if (!_mongocrypt_cache_get(&ctx->crypt->cache_collinfo, + ectx->target_ns /* null terminated */, + (void **)&collinfo)) { return _mongocrypt_ctx_fail_w_msg(ctx, "failed to retrieve from cache"); } if (collinfo) { if (!_set_schema_from_collinfo(ctx, collinfo)) { + bson_destroy(collinfo); return _mongocrypt_ctx_fail(ctx); } ctx->state = MONGOCRYPT_CTX_NEED_MONGO_MARKINGS; } else { /* we need to get it. */ ctx->state = MONGOCRYPT_CTX_NEED_MONGO_COLLINFO; + if (ectx->target_db) { + if (!ctx->crypt->opts.use_need_mongo_collinfo_with_db_state) { + _mongocrypt_ctx_fail_w_msg( + ctx, + "Fetching remote collection information on separate databases is not supported. Try " + "upgrading driver, or specify a local schemaMap or encryptedFieldsMap."); + return false; + } + // Target database may differ from command database. Request collection info from target database. + ctx->state = MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB; + } } bson_destroy(collinfo); @@ -2138,7 +2423,7 @@ static bool explicit_encrypt_init(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *ms _mongocrypt_ctx_encrypt_t *ectx; bson_t as_bson; bson_iter_t iter; - _mongocrypt_ctx_opts_spec_t opts_spec; + _mongocrypt_ctx_opts_spec_t opts_spec = {0}; if (!ctx) { return false; @@ -2222,7 +2507,9 @@ static bool explicit_encrypt_init(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *ms return _mongocrypt_ctx_fail_w_msg(ctx, "contention factor is required for indexed algorithm"); } - if (ctx->opts.index_type.set && ctx->opts.index_type.value == MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW) { + if (ctx->opts.index_type.set + && (ctx->opts.index_type.value == MONGOCRYPT_INDEX_TYPE_RANGE + || ctx->opts.index_type.value == MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW_DEPRECATED)) { if (!ctx->opts.contention_factor.set) { return _mongocrypt_ctx_fail_w_msg(ctx, "contention factor is required for range indexed algorithm"); } @@ -2242,8 +2529,14 @@ static bool explicit_encrypt_init(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *ms bool matches = false; switch (ctx->opts.query_type.value) { - case MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW: - matches = (ctx->opts.index_type.value == MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW); + case MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_DEPRECATED: + // Don't allow deprecated query type if we are using new index type. + matches = (ctx->opts.index_type.value == MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW_DEPRECATED); + break; + case MONGOCRYPT_QUERY_TYPE_RANGE: + // New query type is compatible with both new and old index types. + matches = (ctx->opts.index_type.value == MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW_DEPRECATED + || ctx->opts.index_type.value == MONGOCRYPT_INDEX_TYPE_RANGE); break; case MONGOCRYPT_QUERY_TYPE_EQUALITY: matches = (ctx->opts.index_type.value == MONGOCRYPT_INDEX_TYPE_EQUALITY); @@ -2317,7 +2610,9 @@ bool mongocrypt_ctx_explicit_encrypt_init(mongocrypt_ctx_t *ctx, mongocrypt_bina if (!explicit_encrypt_init(ctx, msg)) { return false; } - if (ctx->opts.query_type.set && ctx->opts.query_type.value == MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW) { + if (ctx->opts.query_type.set + && (ctx->opts.query_type.value == MONGOCRYPT_QUERY_TYPE_RANGE + || ctx->opts.query_type.value == MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_DEPRECATED)) { return _mongocrypt_ctx_fail_w_msg(ctx, "Encrypt may not be used for range queries. Use EncryptExpression."); } return true; @@ -2327,22 +2622,77 @@ bool mongocrypt_ctx_explicit_encrypt_expression_init(mongocrypt_ctx_t *ctx, mong if (!explicit_encrypt_init(ctx, msg)) { return false; } - if (!ctx->opts.query_type.set || ctx->opts.query_type.value != MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW) { + if (!ctx->opts.query_type.set + || !(ctx->opts.query_type.value == MONGOCRYPT_QUERY_TYPE_RANGE + || ctx->opts.query_type.value == MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_DEPRECATED)) { return _mongocrypt_ctx_fail_w_msg(ctx, "EncryptExpression may only be used for range queries."); } return true; } -static bool -_check_cmd_for_auto_encrypt(mongocrypt_binary_t *cmd, bool *bypass, char **collname, mongocrypt_status_t *status) { +static bool _check_cmd_for_auto_encrypt_bulkWrite(mongocrypt_binary_t *cmd, + char **target_db, + char **target_coll, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(cmd); + BSON_ASSERT_PARAM(target_db); + BSON_ASSERT_PARAM(target_coll); + bson_t as_bson; - bson_iter_t iter, ns_iter; + bson_iter_t cmd_iter = {0}; + + if (!_mongocrypt_binary_to_bson(cmd, &as_bson) || !bson_iter_init(&cmd_iter, &as_bson)) { + CLIENT_ERR("invalid command BSON"); + return false; + } + + bson_iter_t ns_iter = cmd_iter; + if (!bson_iter_find_descendant(&ns_iter, "nsInfo.0.ns", &ns_iter)) { + CLIENT_ERR("failed to find namespace in `bulkWrite` command"); + return false; + } + + if (!BSON_ITER_HOLDS_UTF8(&ns_iter)) { + CLIENT_ERR("expected namespace to be UTF8, got: %s", mc_bson_type_to_string(bson_iter_type(&ns_iter))); + return false; + } + + const char *target_ns = bson_iter_utf8(&ns_iter, NULL /* length */); + // Parse `target_ns` into "." + const char *dot = strstr(target_ns, "."); + if (!dot) { + CLIENT_ERR("expected namespace to contain dot, got: %s", target_ns); + return false; + } + *target_coll = bson_strdup(dot + 1); + // Get the database from the `ns` field (which may differ from `cmd_db`). + ptrdiff_t db_len = dot - target_ns; + if ((uint64_t)db_len > SIZE_MAX) { + CLIENT_ERR("unexpected database length exceeds %zu", SIZE_MAX); + return false; + } + *target_db = bson_strndup(target_ns, (size_t)db_len); + + // Ensure only one `nsInfo` element is present. + // Query analysis (mongocryptd/crypt_shared) currently only supports one namespace. + if (bson_has_field(&as_bson, "nsInfo.1")) { + CLIENT_ERR("expected one namespace in `bulkWrite`, but found more than one. Only one namespace is supported."); + return false; + } + + return true; +} + +static bool +_check_cmd_for_auto_encrypt(mongocrypt_binary_t *cmd, bool *bypass, char **target_coll, mongocrypt_status_t *status) { + bson_t as_bson; + bson_iter_t iter = {0}, target_coll_iter; const char *cmd_name; bool eligible = false; BSON_ASSERT_PARAM(cmd); BSON_ASSERT_PARAM(bypass); - BSON_ASSERT_PARAM(collname); + BSON_ASSERT_PARAM(target_coll); *bypass = false; @@ -2366,22 +2716,22 @@ _check_cmd_for_auto_encrypt(mongocrypt_binary_t *cmd, bool *bypass, char **colln CLIENT_ERR("explain value is not a document"); return false; } - if (!bson_iter_recurse(&iter, &ns_iter)) { + if (!bson_iter_recurse(&iter, &target_coll_iter)) { CLIENT_ERR("malformed BSON for encrypt command"); return false; } - if (!bson_iter_next(&ns_iter)) { + if (!bson_iter_next(&target_coll_iter)) { CLIENT_ERR("invalid empty BSON"); return false; } } else { - memcpy(&ns_iter, &iter, sizeof(iter)); + memcpy(&target_coll_iter, &iter, sizeof(iter)); } - if (BSON_ITER_HOLDS_UTF8(&ns_iter)) { - *collname = bson_strdup(bson_iter_utf8(&ns_iter, NULL)); + if (BSON_ITER_HOLDS_UTF8(&target_coll_iter)) { + *target_coll = bson_strdup(bson_iter_utf8(&target_coll_iter, NULL)); } else { - *collname = NULL; + *target_coll = NULL; } /* check if command is eligible for auto encryption, bypassed, or ineligible. @@ -2461,6 +2811,8 @@ _check_cmd_for_auto_encrypt(mongocrypt_binary_t *cmd, bool *bypass, char **colln *bypass = true; } else if (0 == strcmp(cmd_name, "compactStructuredEncryptionData")) { eligible = true; + } else if (0 == strcmp(cmd_name, "cleanupStructuredEncryptionData")) { + eligible = true; } else if (0 == strcmp(cmd_name, "collMod")) { eligible = true; } else if (0 == strcmp(cmd_name, "hello")) { @@ -2483,11 +2835,11 @@ _check_cmd_for_auto_encrypt(mongocrypt_binary_t *cmd, bool *bypass, char **colln /* database/client commands are ineligible. */ if (eligible) { - if (!*collname) { + if (!*target_coll) { CLIENT_ERR("non-collection command not supported for auto encryption: %s", cmd_name); return false; } - if (0 == strlen(*collname)) { + if (0 == strlen(*target_coll)) { CLIENT_ERR("empty collection name on command: %s", cmd_name); return false; } @@ -2515,7 +2867,6 @@ static bool needs_ismaster_check(mongocrypt_ctx_t *ctx) { bool mongocrypt_ctx_encrypt_init(mongocrypt_ctx_t *ctx, const char *db, int32_t db_len, mongocrypt_binary_t *cmd) { _mongocrypt_ctx_encrypt_t *ectx; _mongocrypt_ctx_opts_spec_t opts_spec; - bool bypass; if (!ctx) { return false; @@ -2537,15 +2888,13 @@ bool mongocrypt_ctx_encrypt_init(mongocrypt_ctx_t *ctx, const char *db, int32_t ctx->vtable.mongo_op_collinfo = _mongo_op_collinfo; ctx->vtable.mongo_feed_collinfo = _mongo_feed_collinfo; ctx->vtable.mongo_done_collinfo = _mongo_done_collinfo; + ctx->vtable.mongo_db_collinfo = _mongo_db_collinfo; ctx->vtable.mongo_op_collinfo = _mongo_op_collinfo; ctx->vtable.mongo_op_markings = _mongo_op_markings; ctx->vtable.mongo_feed_markings = _mongo_feed_markings; ctx->vtable.mongo_done_markings = _mongo_done_markings; ctx->vtable.finalize = _finalize; ctx->vtable.cleanup = _cleanup; - ctx->vtable.mongo_op_collinfo = _mongo_op_collinfo; - ctx->vtable.mongo_feed_collinfo = _mongo_feed_collinfo; - ctx->vtable.mongo_done_collinfo = _mongo_done_collinfo; ectx->bypass_query_analysis = ctx->crypt->opts.bypass_query_analysis; if (!cmd || !cmd->data) { @@ -2559,27 +2908,38 @@ bool mongocrypt_ctx_encrypt_init(mongocrypt_ctx_t *ctx, const char *db, int32_t return _mongocrypt_ctx_fail(ctx); } - if (!_check_cmd_for_auto_encrypt(cmd, &bypass, &ectx->coll_name, ctx->status)) { - return _mongocrypt_ctx_fail(ctx); - } - - if (bypass) { - ctx->nothing_to_do = true; - ctx->state = MONGOCRYPT_CTX_READY; - return true; - } - - /* if _check_cmd_for_auto_encrypt did not bypass or error, a collection name - * must have been set. */ - if (!ectx->coll_name) { - return _mongocrypt_ctx_fail_w_msg(ctx, "unexpected error: did not bypass or error but no collection name"); - } - - if (!_mongocrypt_validate_and_copy_string(db, db_len, &ectx->db_name) || 0 == strlen(ectx->db_name)) { + if (!_mongocrypt_validate_and_copy_string(db, db_len, &ectx->cmd_db) || 0 == strlen(ectx->cmd_db)) { return _mongocrypt_ctx_fail_w_msg(ctx, "invalid db"); } - ectx->ns = bson_strdup_printf("%s.%s", ectx->db_name, ectx->coll_name); + if (0 == strcmp(ectx->cmd_name, "bulkWrite")) { + // Handle `bulkWrite` as a special case. + // `bulkWrite` includes the target namespaces in an `nsInfo` field. + // Only one target namespace is supported. + if (!_check_cmd_for_auto_encrypt_bulkWrite(cmd, &ectx->target_db, &ectx->target_coll, ctx->status)) { + return _mongocrypt_ctx_fail(ctx); + } + + ectx->target_ns = bson_strdup_printf("%s.%s", ectx->target_db, ectx->target_coll); + } else { + bool bypass; + if (!_check_cmd_for_auto_encrypt(cmd, &bypass, &ectx->target_coll, ctx->status)) { + return _mongocrypt_ctx_fail(ctx); + } + + if (bypass) { + ctx->nothing_to_do = true; + ctx->state = MONGOCRYPT_CTX_READY; + return true; + } + + /* if _check_cmd_for_auto_encrypt did not bypass or error, a collection name + * must have been set. */ + if (!ectx->target_coll) { + return _mongocrypt_ctx_fail_w_msg(ctx, "unexpected error: did not bypass or error but no collection name"); + } + ectx->target_ns = bson_strdup_printf("%s.%s", ectx->cmd_db, ectx->target_coll); + } if (ctx->opts.kek.provider.aws.region || ctx->opts.kek.provider.aws.cmk) { return _mongocrypt_ctx_fail_w_msg(ctx, "aws masterkey options must not be set"); @@ -2601,7 +2961,7 @@ bool mongocrypt_ctx_encrypt_init(mongocrypt_ctx_t *ctx, const char *db, int32_t "%s (%s=\"%s\", %s=%d, %s=\"%s\")", BSON_FUNC, "db", - ectx->db_name, + ectx->cmd_db, "db_len", db_len, "cmd", @@ -2680,6 +3040,17 @@ static bool mongocrypt_ctx_encrypt_ismaster_done(mongocrypt_ctx_t *ctx) { /* Otherwise, we need the the driver to fetch the schema. */ if (_mongocrypt_buffer_empty(&ectx->schema)) { ctx->state = MONGOCRYPT_CTX_NEED_MONGO_COLLINFO; + if (ectx->target_db) { + if (!ctx->crypt->opts.use_need_mongo_collinfo_with_db_state) { + _mongocrypt_ctx_fail_w_msg( + ctx, + "Fetching remote collection information on separate databases is not supported. Try " + "upgrading driver, or specify a local schemaMap or encryptedFieldsMap."); + return false; + } + // Target database may differ from command database. Request collection info from target database. + ctx->state = MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB; + } } } @@ -2689,7 +3060,7 @@ static bool mongocrypt_ctx_encrypt_ismaster_done(mongocrypt_ctx_t *ctx) { return false; } - if (!_fle2_collect_keys_for_compact(ctx)) { + if (!_fle2_collect_keys_for_compaction(ctx)) { return false; } diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-private.h index 1500686f901..688db865d2c 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx-private.h @@ -39,12 +39,17 @@ typedef enum { typedef enum { MONGOCRYPT_INDEX_TYPE_NONE = 1, MONGOCRYPT_INDEX_TYPE_EQUALITY = 2, - MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW = 3 + MONGOCRYPT_INDEX_TYPE_RANGE = 3, + MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW_DEPRECATED = 4 } mongocrypt_index_type_t; const char *_mongocrypt_index_type_to_string(mongocrypt_index_type_t val); -typedef enum { MONGOCRYPT_QUERY_TYPE_EQUALITY = 1, MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW = 2 } mongocrypt_query_type_t; +typedef enum { + MONGOCRYPT_QUERY_TYPE_EQUALITY = 1, + MONGOCRYPT_QUERY_TYPE_RANGE = 2, + MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_DEPRECATED = 3 +} mongocrypt_query_type_t; const char *_mongocrypt_query_type_to_string(mongocrypt_query_type_t val); @@ -58,6 +63,7 @@ typedef struct __mongocrypt_ctx_opts_t { _mongocrypt_buffer_t key_material; mongocrypt_encryption_algorithm_t algorithm; _mongocrypt_kek_t kek; + bool retry_enabled; struct { mongocrypt_index_type_t value; @@ -82,8 +88,15 @@ typedef struct __mongocrypt_ctx_opts_t { } rangeopts; } _mongocrypt_ctx_opts_t; +// `_mongocrypt_ctx_opts_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof__mongocrypt_ctx_opts_t, + BSON_ALIGNOF(_mongocrypt_ctx_opts_t) + >= BSON_MAX(BSON_ALIGNOF(_mongocrypt_key_alt_name_t), BSON_ALIGNOF(mc_RangeOpts_t))); + /* All derived contexts may override these methods. */ typedef struct { + const char *(*mongo_db_collinfo)(mongocrypt_ctx_t *ctx); bool (*mongo_op_collinfo)(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out); bool (*mongo_feed_collinfo)(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *in); bool (*mongo_done_collinfo)(mongocrypt_ctx_t *ctx); @@ -129,9 +142,22 @@ bool _mongocrypt_ctx_fail_w_msg(mongocrypt_ctx_t *ctx, const char *msg); typedef struct { mongocrypt_ctx_t parent; bool explicit; - char *coll_name; - char *db_name; - char *ns; + + // `cmd_db` is the command database (appended as `$db`). + char *cmd_db; + + // `target_ns` is the target namespace "." for the operation. May be associated with + // jsonSchema (CSFLE) or encryptedFields (QE). For `bulkWrite`, the target namespace database may differ from + // `cmd_db`. + char *target_ns; + + // `target_db` is the target database for the operation. For `bulkWrite`, the target namespace database may differ + // from `cmd_db`. If `target_db` is NULL, the target namespace database is the same as `cmd_db`. + char *target_db; + + // `target_coll` is the target namespace collection name. + char *target_coll; + _mongocrypt_buffer_t list_collections_filter; _mongocrypt_buffer_t schema; /* TODO CDRIVER-3150: audit + rename these buffers. @@ -156,13 +182,19 @@ typedef struct { * schema, and there were siblings. */ bool collinfo_has_siblings; /* encrypted_field_config is set when: - * 1. . is present in an encrypted_field_config_map. + * 1. `target_ns` is present in an encrypted_field_config_map. * 2. (TODO MONGOCRYPT-414) The collection has encryptedFields in the * response to listCollections. encrypted_field_config is true if and only if * encryption is using FLE 2.0. + * 3. The `bulkWrite` command is processed and needs an empty encryptedFields to be processed by query analysis. + * (`bulkWrite` does not support empty JSON schema). */ _mongocrypt_buffer_t encrypted_field_config; mc_EncryptedFieldConfig_t efc; + // `used_empty_encryptedFields` is true if the collection has no JSON schema or encryptedFields, + // yet an empty encryptedFields was constructed to support query analysis. + // When true, an empty encryptedFields is sent to query analysis, but not appended to the final command. + bool used_empty_encryptedFields; /* bypass_query_analysis is set to true to skip the * MONGOCRYPT_CTX_NEED_MONGO_MARKINGS state. */ bool bypass_query_analysis; @@ -177,6 +209,11 @@ typedef struct { const char *cmd_name; } _mongocrypt_ctx_encrypt_t; +// `_mongocrypt_ctx_encrypt_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof__mongocrypt_ctx_encrypt_t, + BSON_ALIGNOF(_mongocrypt_ctx_encrypt_t) >= BSON_ALIGNOF(mongocrypt_ctx_t)); + typedef struct { mongocrypt_ctx_t parent; /* TODO CDRIVER-3150: audit + rename these buffers. @@ -187,6 +224,11 @@ typedef struct { _mongocrypt_buffer_t decrypted_doc; } _mongocrypt_ctx_decrypt_t; +// `_mongocrypt_ctx_datakey_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof__mongocrypt_ctx_decrypt_t, + BSON_ALIGNOF(_mongocrypt_ctx_decrypt_t) >= BSON_ALIGNOF(mongocrypt_ctx_t)); + typedef struct { mongocrypt_ctx_t parent; mongocrypt_kms_ctx_t kms; @@ -200,6 +242,11 @@ typedef struct { _mongocrypt_buffer_t kmip_secretdata; } _mongocrypt_ctx_datakey_t; +// `_mongocrypt_ctx_datakey_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof__mongocrypt_ctx_datakey_t, + BSON_ALIGNOF(_mongocrypt_ctx_datakey_t) >= BSON_ALIGNOF(mongocrypt_ctx_t)); + typedef struct _mongocrypt_ctx_rmd_datakey_t _mongocrypt_ctx_rmd_datakey_t; struct _mongocrypt_ctx_rmd_datakey_t { @@ -217,12 +264,40 @@ typedef struct { _mongocrypt_buffer_t results; } _mongocrypt_ctx_rewrap_many_datakey_t; +// `_mongocrypt_ctx_rewrap_many_datakey_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof__mongocrypt_ctx_rewrap_many_datakey_t, + BSON_ALIGNOF(_mongocrypt_ctx_rewrap_many_datakey_t) >= BSON_ALIGNOF(mongocrypt_ctx_t)); + typedef struct { mongocrypt_ctx_t parent; _mongocrypt_buffer_t result; mc_EncryptedFieldConfig_t efc; } _mongocrypt_ctx_compact_t; +// `_mongocrypt_ctx_compact_t` inherits extended alignment from libbson. To dynamically allocate, use aligned +// allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof__mongocrypt_ctx_compact_t, + BSON_ALIGNOF(_mongocrypt_ctx_compact_t) >= BSON_ALIGNOF(mongocrypt_ctx_t)); + +#define MONGOCRYPT_CTX_ALLOC_SIZE \ + BSON_MAX(sizeof(_mongocrypt_ctx_encrypt_t), \ + BSON_MAX(sizeof(_mongocrypt_ctx_decrypt_t), \ + BSON_MAX(sizeof(_mongocrypt_ctx_datakey_t), \ + BSON_MAX(sizeof(_mongocrypt_ctx_rewrap_many_datakey_t), \ + sizeof(_mongocrypt_ctx_compact_t))))) + +#define MONGOCRYPT_CTX_ALLOC_ALIGNMENT \ + BSON_MAX(BSON_ALIGNOF(_mongocrypt_ctx_encrypt_t), \ + BSON_MAX(BSON_ALIGNOF(_mongocrypt_ctx_decrypt_t), \ + BSON_MAX(BSON_ALIGNOF(_mongocrypt_ctx_datakey_t), \ + BSON_MAX(BSON_ALIGNOF(_mongocrypt_ctx_rewrap_many_datakey_t), \ + BSON_ALIGNOF(_mongocrypt_ctx_compact_t))))) + +// `_mongocrypt_ctx_t` inherits extended alignment from libbson. To dynamically allocate, use +// aligned allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof_mongocrypt_ctx_t, BSON_ALIGNOF(mongocrypt_ctx_t) >= MONGOCRYPT_CTX_ALLOC_ALIGNMENT); + /* Used for option validation. True means required. False means prohibited. */ typedef enum { OPT_PROHIBITED = 0, OPT_REQUIRED, OPT_OPTIONAL } _mongocrypt_ctx_opt_spec_t; diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx.c index e464771269f..f0384d9ec79 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-ctx.c @@ -258,8 +258,15 @@ bool mongocrypt_ctx_setopt_algorithm(mongocrypt_ctx_t *ctx, const char *algorith } else if (mstr_eq_ignore_case(algo_str, mstrv_lit(MONGOCRYPT_ALGORITHM_UNINDEXED_STR))) { ctx->opts.index_type.value = MONGOCRYPT_INDEX_TYPE_NONE; ctx->opts.index_type.set = true; - } else if (mstr_eq_ignore_case(algo_str, mstrv_lit(MONGOCRYPT_ALGORITHM_RANGEPREVIEW_STR))) { - ctx->opts.index_type.value = MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW; + } else if (mstr_eq_ignore_case(algo_str, mstrv_lit(MONGOCRYPT_ALGORITHM_RANGE_STR))) { + ctx->opts.index_type.value = MONGOCRYPT_INDEX_TYPE_RANGE; + ctx->opts.index_type.set = true; + } else if (mstr_eq_ignore_case(algo_str, mstrv_lit(MONGOCRYPT_ALGORITHM_RANGEPREVIEW_DEPRECATED_STR))) { + if (ctx->crypt->opts.use_range_v2) { + _mongocrypt_ctx_fail_w_msg(ctx, "Algorithm 'rangePreview' is deprecated, please use 'range'"); + return false; + } + ctx->opts.index_type.value = MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW_DEPRECATED; ctx->opts.index_type.set = true; } else { char *error = bson_strdup_printf("unsupported algorithm string \"%.*s\"", @@ -275,7 +282,6 @@ bool mongocrypt_ctx_setopt_algorithm(mongocrypt_ctx_t *ctx, const char *algorith mongocrypt_ctx_t *mongocrypt_ctx_new(mongocrypt_t *crypt) { mongocrypt_ctx_t *ctx; - size_t ctx_size; if (!crypt) { return NULL; @@ -287,19 +293,17 @@ mongocrypt_ctx_t *mongocrypt_ctx_new(mongocrypt_t *crypt) { CLIENT_ERR("cannot create context from uninitialized crypt"); return NULL; } - ctx_size = sizeof(_mongocrypt_ctx_encrypt_t); - if (sizeof(_mongocrypt_ctx_decrypt_t) > ctx_size) { - ctx_size = sizeof(_mongocrypt_ctx_decrypt_t); - } - if (sizeof(_mongocrypt_ctx_datakey_t) > ctx_size) { - ctx_size = sizeof(_mongocrypt_ctx_datakey_t); - } - ctx = bson_malloc0(ctx_size); + + // Allocate with memory and alignment large enough for any possible context type. + static const size_t ctx_alignment = MONGOCRYPT_CTX_ALLOC_ALIGNMENT; + static const size_t ctx_size = MONGOCRYPT_CTX_ALLOC_SIZE; + ctx = bson_aligned_alloc0(ctx_alignment, ctx_size); BSON_ASSERT(ctx); ctx->crypt = crypt; ctx->status = mongocrypt_status_new(); ctx->opts.algorithm = MONGOCRYPT_ENCRYPTION_ALGORITHM_NONE; + ctx->opts.retry_enabled = crypt->retry_enabled; ctx->state = MONGOCRYPT_CTX_DONE; return ctx; } @@ -379,6 +383,7 @@ bool mongocrypt_ctx_mongo_op(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { } switch (ctx->state) { + case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB: case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: CHECK_AND_CALL(mongo_op_collinfo, ctx, out); case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: CHECK_AND_CALL(mongo_op_markings, ctx, out); case MONGOCRYPT_CTX_NEED_MONGO_KEYS: CHECK_AND_CALL(mongo_op_keys, ctx, out); @@ -391,6 +396,38 @@ bool mongocrypt_ctx_mongo_op(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { } } +const char *mongocrypt_ctx_mongo_db(mongocrypt_ctx_t *ctx) { + if (!ctx) { + return NULL; + } + if (!ctx->initialized) { + _mongocrypt_ctx_fail_w_msg(ctx, "ctx NULL or uninitialized"); + return NULL; + } + + switch (ctx->state) { + case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB: { + if (!ctx->vtable.mongo_db_collinfo) { + _mongocrypt_ctx_fail_w_msg(ctx, "not applicable to context"); + return NULL; + } + return ctx->vtable.mongo_db_collinfo(ctx); + } + case MONGOCRYPT_CTX_ERROR: return false; + case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: + case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: + case MONGOCRYPT_CTX_NEED_MONGO_KEYS: + case MONGOCRYPT_CTX_DONE: + case MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS: + case MONGOCRYPT_CTX_NEED_KMS: + case MONGOCRYPT_CTX_READY: + default: { + _mongocrypt_ctx_fail_w_msg(ctx, "wrong state"); + return NULL; + } + } +} + bool mongocrypt_ctx_mongo_feed(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *in) { if (!ctx) { return false; @@ -412,6 +449,7 @@ bool mongocrypt_ctx_mongo_feed(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *in) { } switch (ctx->state) { + case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB: case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: CHECK_AND_CALL(mongo_feed_collinfo, ctx, in); case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: CHECK_AND_CALL(mongo_feed_markings, ctx, in); case MONGOCRYPT_CTX_NEED_MONGO_KEYS: CHECK_AND_CALL(mongo_feed_keys, ctx, in); @@ -433,6 +471,7 @@ bool mongocrypt_ctx_mongo_done(mongocrypt_ctx_t *ctx) { } switch (ctx->state) { + case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB: case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: CHECK_AND_CALL(mongo_done_collinfo, ctx); case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: CHECK_AND_CALL(mongo_done_markings, ctx); case MONGOCRYPT_CTX_NEED_MONGO_KEYS: CHECK_AND_CALL(mongo_done_keys, ctx); @@ -471,17 +510,24 @@ mongocrypt_kms_ctx_t *mongocrypt_ctx_next_kms_ctx(mongocrypt_ctx_t *ctx) { return NULL; } + mongocrypt_kms_ctx_t *ret; switch (ctx->state) { - case MONGOCRYPT_CTX_NEED_KMS: return ctx->vtable.next_kms_ctx(ctx); + case MONGOCRYPT_CTX_NEED_KMS: ret = ctx->vtable.next_kms_ctx(ctx); break; case MONGOCRYPT_CTX_ERROR: return NULL; case MONGOCRYPT_CTX_DONE: case MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS: + case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB: case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: case MONGOCRYPT_CTX_NEED_MONGO_KEYS: case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: case MONGOCRYPT_CTX_READY: default: _mongocrypt_ctx_fail_w_msg(ctx, "wrong state"); return NULL; } + + if (ret) { + ret->retry_enabled = ctx->opts.retry_enabled; + } + return ret; } bool mongocrypt_ctx_provide_kms_providers(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *kms_providers_definition) { @@ -504,6 +550,8 @@ bool mongocrypt_ctx_provide_kms_providers(mongocrypt_ctx_t *ctx, mongocrypt_bina return false; } + _mongocrypt_opts_kms_providers_init(&ctx->per_ctx_kms_providers); + if (!_mongocrypt_parse_kms_providers(kms_providers_definition, &ctx->per_ctx_kms_providers, ctx->status, @@ -545,6 +593,7 @@ bool mongocrypt_ctx_kms_done(mongocrypt_ctx_t *ctx) { case MONGOCRYPT_CTX_ERROR: return false; case MONGOCRYPT_CTX_DONE: case MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS: + case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB: case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: case MONGOCRYPT_CTX_NEED_MONGO_KEYS: case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: @@ -575,6 +624,7 @@ bool mongocrypt_ctx_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { case MONGOCRYPT_CTX_DONE: case MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS: case MONGOCRYPT_CTX_NEED_KMS: + case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB: case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: case MONGOCRYPT_CTX_NEED_MONGO_KEYS: case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: @@ -711,6 +761,7 @@ bool mongocrypt_ctx_setopt_masterkey_local(mongocrypt_ctx_t *ctx) { } ctx->opts.kek.kms_provider = MONGOCRYPT_KMS_PROVIDER_LOCAL; + ctx->opts.kek.kmsid = bson_strdup("local"); return true; } @@ -747,9 +798,13 @@ bool _mongocrypt_ctx_init(mongocrypt_ctx_t *ctx, _mongocrypt_ctx_opts_spec_t *op if (!ctx->opts.kek.kms_provider) { return _mongocrypt_ctx_fail_w_msg(ctx, "master key required"); } - if (!ctx->crypt->opts.use_need_kms_credentials_state - && !((int)ctx->opts.kek.kms_provider & _mongocrypt_ctx_kms_providers(ctx)->configured_providers)) { - return _mongocrypt_ctx_fail_w_msg(ctx, "requested kms provider not configured"); + mc_kms_creds_t unused; + bool is_configured = + _mongocrypt_opts_kms_providers_lookup(_mongocrypt_ctx_kms_providers(ctx), ctx->opts.kek.kmsid, &unused); + if (!ctx->crypt->opts.use_need_kms_credentials_state && !is_configured) { + mongocrypt_status_t *status = ctx->status; + CLIENT_ERR("requested kms provider not configured: `%s`", ctx->opts.kek.kmsid); + return _mongocrypt_ctx_fail(ctx); } } @@ -759,9 +814,16 @@ bool _mongocrypt_ctx_init(mongocrypt_ctx_t *ctx, _mongocrypt_ctx_opts_spec_t *op /* Check that the kms provider required by the datakey is configured. */ if (ctx->opts.kek.kms_provider) { - if (!((ctx->crypt->opts.kms_providers.need_credentials | ctx->crypt->opts.kms_providers.configured_providers) - & (int)ctx->opts.kek.kms_provider)) { - return _mongocrypt_ctx_fail_w_msg(ctx, "kms provider required by datakey is not configured"); + mc_kms_creds_t unused; + bool is_configured = + _mongocrypt_opts_kms_providers_lookup(_mongocrypt_ctx_kms_providers(ctx), ctx->opts.kek.kmsid, &unused); + bool needs = _mongocrypt_needs_credentials_for_provider(ctx->crypt, + ctx->opts.kek.kms_provider, + ctx->opts.kek.kmsid_name); + if (!is_configured && !needs) { + mongocrypt_status_t *status = ctx->status; + CLIENT_ERR("requested kms provider required by datakey is not configured: `%s`", ctx->opts.kek.kmsid); + return _mongocrypt_ctx_fail(ctx); } } @@ -1002,8 +1064,15 @@ bool mongocrypt_ctx_setopt_query_type(mongocrypt_ctx_t *ctx, const char *query_t if (mstr_eq_ignore_case(qt_str, mstrv_lit(MONGOCRYPT_QUERY_TYPE_EQUALITY_STR))) { ctx->opts.query_type.value = MONGOCRYPT_QUERY_TYPE_EQUALITY; ctx->opts.query_type.set = true; - } else if (mstr_eq_ignore_case(qt_str, mstrv_lit(MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_STR))) { - ctx->opts.query_type.value = MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW; + } else if (mstr_eq_ignore_case(qt_str, mstrv_lit(MONGOCRYPT_QUERY_TYPE_RANGE_STR))) { + ctx->opts.query_type.value = MONGOCRYPT_QUERY_TYPE_RANGE; + ctx->opts.query_type.set = true; + } else if (mstr_eq_ignore_case(qt_str, mstrv_lit(MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_DEPRECATED_STR))) { + if (ctx->crypt->opts.use_range_v2) { + _mongocrypt_ctx_fail_w_msg(ctx, "Query type 'rangePreview' is deprecated, please use 'range'"); + return false; + } + ctx->opts.query_type.value = MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_DEPRECATED; ctx->opts.query_type.set = true; } else { /* don't check if qt_str.len fits in int; we want the diagnostic output */ @@ -1021,7 +1090,8 @@ const char *_mongocrypt_index_type_to_string(mongocrypt_index_type_t val) { switch (val) { case MONGOCRYPT_INDEX_TYPE_NONE: return "None"; case MONGOCRYPT_INDEX_TYPE_EQUALITY: return "Equality"; - case MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW: return "RangePreview"; + case MONGOCRYPT_INDEX_TYPE_RANGE: return "Range"; + case MONGOCRYPT_INDEX_TYPE_RANGEPREVIEW_DEPRECATED: return "RangePreview"; default: return "Unknown"; } } @@ -1029,7 +1099,8 @@ const char *_mongocrypt_index_type_to_string(mongocrypt_index_type_t val) { const char *_mongocrypt_query_type_to_string(mongocrypt_query_type_t val) { switch (val) { case MONGOCRYPT_QUERY_TYPE_EQUALITY: return "Equality"; - case MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW: return "RangePreview"; + case MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_DEPRECATED: return "RangePreview"; + case MONGOCRYPT_QUERY_TYPE_RANGE: return "Range"; default: return "Unknown"; } } @@ -1057,7 +1128,7 @@ bool mongocrypt_ctx_setopt_algorithm_range(mongocrypt_ctx_t *ctx, mongocrypt_bin return _mongocrypt_ctx_fail_w_msg(ctx, "invalid BSON"); } - if (!mc_RangeOpts_parse(&ctx->opts.rangeopts.value, &as_bson, ctx->status)) { + if (!mc_RangeOpts_parse(&ctx->opts.rangeopts.value, &as_bson, ctx->crypt->opts.use_range_v2, ctx->status)) { return _mongocrypt_ctx_fail(ctx); } diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-dll-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-dll-private.h index e52b8cbeb42..3debb7314ec 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-dll-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-dll-private.h @@ -83,7 +83,14 @@ typedef struct mcr_dll_path_result { * library, or an error string. * * @note Caller must free both `retval.path` and `retval.error_string`. + * @note Returns an error if not supported on this platform. Use + * `mcr_dll_path_supported` to check before calling. */ mcr_dll_path_result mcr_dll_path(mcr_dll dll); +/** + * @brief Return true if `mcr_dll_path` is supported on this platform. + */ +bool mcr_dll_path_supported(void); + #endif // MONGOCRYPT_DLL_PRIVATE_H diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-kek-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-kek-private.h index 8879b0f5c8f..a396b086f4d 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-kek-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-kek-private.h @@ -69,6 +69,7 @@ typedef struct { typedef struct { char *key_id; /* optional on parsing, required on appending. */ _mongocrypt_endpoint_t *endpoint; /* optional. */ + bool delegated; } _mongocrypt_kmip_kek_t; typedef struct { @@ -80,6 +81,9 @@ typedef struct { _mongocrypt_aws_kek_t aws; _mongocrypt_kmip_kek_t kmip; } provider; + + char *kmsid; + const char *kmsid_name; } _mongocrypt_kek_t; /* Parse a document describing a key encryption key. @@ -100,4 +104,4 @@ void _mongocrypt_kek_copy_to(const _mongocrypt_kek_t *src, _mongocrypt_kek_t *ds void _mongocrypt_kek_cleanup(_mongocrypt_kek_t *kek); -#endif /* MONGOCRYPT_KEK_PRIVATE_H */ \ No newline at end of file +#endif /* MONGOCRYPT_KEK_PRIVATE_H */ diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-kek.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-kek.c index 11a0bd720f4..9329200ebb5 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-kek.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-kek.c @@ -18,6 +18,126 @@ #include "mongocrypt-opts-private.h" #include "mongocrypt-private.h" +static bool _mongocrypt_azure_kek_parse(_mongocrypt_azure_kek_t *azure, + const char *kmsid, + const bson_t *def, + mongocrypt_status_t *status) { + if (!_mongocrypt_parse_required_endpoint(def, + "keyVaultEndpoint", + &azure->key_vault_endpoint, + NULL /* opts */, + status)) { + return false; + } + + if (!_mongocrypt_parse_required_utf8(def, "keyName", &azure->key_name, status)) { + return false; + } + + if (!_mongocrypt_parse_optional_utf8(def, "keyVersion", &azure->key_version, status)) { + return false; + } + + if (!_mongocrypt_check_allowed_fields(def, + NULL /* root */, + status, + "provider", + "keyVaultEndpoint", + "keyName", + "keyVersion")) { + return false; + } + return true; +} + +static bool _mongocrypt_gcp_kek_parse(_mongocrypt_gcp_kek_t *gcp, + const char *kmsid, + const bson_t *def, + mongocrypt_status_t *status) { + if (!_mongocrypt_parse_optional_endpoint(def, "endpoint", &gcp->endpoint, NULL /* opts */, status)) { + return false; + } + + if (!_mongocrypt_parse_required_utf8(def, "projectId", &gcp->project_id, status)) { + return false; + } + + if (!_mongocrypt_parse_required_utf8(def, "location", &gcp->location, status)) { + return false; + } + + if (!_mongocrypt_parse_required_utf8(def, "keyRing", &gcp->key_ring, status)) { + return false; + } + + if (!_mongocrypt_parse_required_utf8(def, "keyName", &gcp->key_name, status)) { + return false; + } + + if (!_mongocrypt_parse_optional_utf8(def, "keyVersion", &gcp->key_version, status)) { + return false; + } + if (!_mongocrypt_check_allowed_fields(def, + NULL, + status, + "provider", + "endpoint", + "projectId", + "location", + "keyRing", + "keyName", + "keyVersion")) { + return false; + } + return true; +} + +static bool _mongocrypt_aws_kek_parse(_mongocrypt_aws_kek_t *aws, + const char *kmsid, + const bson_t *def, + mongocrypt_status_t *status) { + if (!_mongocrypt_parse_required_utf8(def, "key", &aws->cmk, status)) { + return false; + } + if (!_mongocrypt_parse_required_utf8(def, "region", &aws->region, status)) { + return false; + } + if (!_mongocrypt_parse_optional_endpoint(def, "endpoint", &aws->endpoint, NULL /* opts */, status)) { + return false; + } + if (!_mongocrypt_check_allowed_fields(def, NULL, status, "provider", "key", "region", "endpoint")) { + return false; + } + + return true; +} + +static bool _mongocrypt_kmip_kek_parse(_mongocrypt_kmip_kek_t *kmip, + const char *kmsid, + const bson_t *def, + mongocrypt_status_t *status) { + _mongocrypt_endpoint_parse_opts_t opts = {0}; + + opts.allow_empty_subdomain = true; + if (!_mongocrypt_parse_optional_endpoint(def, "endpoint", &kmip->endpoint, &opts, status)) { + return false; + } + + if (!_mongocrypt_parse_optional_utf8(def, "keyId", &kmip->key_id, status)) { + return false; + } + + kmip->delegated = false; + if (!_mongocrypt_parse_optional_bool(def, "delegated", &kmip->delegated, status)) { + return false; + } + + if (!_mongocrypt_check_allowed_fields(def, NULL, status, "provider", "endpoint", "keyId", "delegated")) { + return false; + } + return true; +} + /* Possible documents to parse: * AWS * provider: "aws" @@ -55,116 +175,50 @@ bool _mongocrypt_kek_parse_owned(const bson_t *bson, _mongocrypt_kek_t *kek, mon goto done; } - if (0 == strcmp(kms_provider, "aws")) { - kek->kms_provider = MONGOCRYPT_KMS_PROVIDER_AWS; - if (!_mongocrypt_parse_required_utf8(bson, "key", &kek->provider.aws.cmk, status)) { + kek->kmsid = bson_strdup(kms_provider); + + _mongocrypt_kms_provider_t type; + if (!mc_kmsid_parse(kek->kmsid, &type, &kek->kmsid_name, status)) { + goto done; + } + + kek->kms_provider = type; + switch (type) { + default: + case MONGOCRYPT_KMS_PROVIDER_NONE: { + CLIENT_ERR("Unexpected parsing KMS type: none"); + goto done; + } + case MONGOCRYPT_KMS_PROVIDER_AWS: { + if (!_mongocrypt_aws_kek_parse(&kek->provider.aws, kek->kmsid, bson, status)) { goto done; } - if (!_mongocrypt_parse_required_utf8(bson, "region", &kek->provider.aws.region, status)) { - goto done; - } - if (!_mongocrypt_parse_optional_endpoint(bson, - "endpoint", - &kek->provider.aws.endpoint, - NULL /* opts */, - status)) { - goto done; - } - if (!_mongocrypt_check_allowed_fields(bson, NULL, status, "provider", "key", "region", "endpoint")) { - goto done; - } - } else if (0 == strcmp(kms_provider, "local")) { - kek->kms_provider = MONGOCRYPT_KMS_PROVIDER_LOCAL; + break; + } + case MONGOCRYPT_KMS_PROVIDER_LOCAL: { if (!_mongocrypt_check_allowed_fields(bson, NULL, status, "provider")) { goto done; } - } else if (0 == strcmp(kms_provider, "azure")) { - kek->kms_provider = MONGOCRYPT_KMS_PROVIDER_AZURE; - if (!_mongocrypt_parse_required_endpoint(bson, - "keyVaultEndpoint", - &kek->provider.azure.key_vault_endpoint, - NULL /* opts */, - status)) { + break; + } + case MONGOCRYPT_KMS_PROVIDER_AZURE: { + if (!_mongocrypt_azure_kek_parse(&kek->provider.azure, kek->kmsid, bson, status)) { goto done; } - - if (!_mongocrypt_parse_required_utf8(bson, "keyName", &kek->provider.azure.key_name, status)) { + break; + } + case MONGOCRYPT_KMS_PROVIDER_GCP: { + if (!_mongocrypt_gcp_kek_parse(&kek->provider.gcp, kek->kmsid, bson, status)) { goto done; } - - if (!_mongocrypt_parse_optional_utf8(bson, "keyVersion", &kek->provider.azure.key_version, status)) { + break; + } + case MONGOCRYPT_KMS_PROVIDER_KMIP: { + if (!_mongocrypt_kmip_kek_parse(&kek->provider.kmip, kek->kmsid, bson, status)) { goto done; } - - if (!_mongocrypt_check_allowed_fields(bson, - NULL, - status, - "provider", - "keyVaultEndpoint", - "keyName", - "keyVersion")) { - goto done; - } - } else if (0 == strcmp(kms_provider, "gcp")) { - kek->kms_provider = MONGOCRYPT_KMS_PROVIDER_GCP; - if (!_mongocrypt_parse_optional_endpoint(bson, - "endpoint", - &kek->provider.gcp.endpoint, - NULL /* opts */, - status)) { - goto done; - } - - if (!_mongocrypt_parse_required_utf8(bson, "projectId", &kek->provider.gcp.project_id, status)) { - goto done; - } - - if (!_mongocrypt_parse_required_utf8(bson, "location", &kek->provider.gcp.location, status)) { - goto done; - } - - if (!_mongocrypt_parse_required_utf8(bson, "keyRing", &kek->provider.gcp.key_ring, status)) { - goto done; - } - - if (!_mongocrypt_parse_required_utf8(bson, "keyName", &kek->provider.gcp.key_name, status)) { - goto done; - } - - if (!_mongocrypt_parse_optional_utf8(bson, "keyVersion", &kek->provider.gcp.key_version, status)) { - goto done; - } - if (!_mongocrypt_check_allowed_fields(bson, - NULL, - status, - "provider", - "endpoint", - "projectId", - "location", - "keyRing", - "keyName", - "keyVersion")) { - goto done; - } - } else if (0 == strcmp(kms_provider, "kmip")) { - kek->kms_provider = MONGOCRYPT_KMS_PROVIDER_KMIP; - _mongocrypt_endpoint_parse_opts_t opts = {0}; - - opts.allow_empty_subdomain = true; - if (!_mongocrypt_parse_optional_endpoint(bson, "endpoint", &kek->provider.kmip.endpoint, &opts, status)) { - goto done; - } - - if (!_mongocrypt_parse_optional_utf8(bson, "keyId", &kek->provider.kmip.key_id, status)) { - goto done; - } - - if (!_mongocrypt_check_allowed_fields(bson, NULL, status, "provider", "endpoint", "keyId")) { - goto done; - } - } else { - CLIENT_ERR("unrecognized KMS provider: %s", kms_provider); - goto done; + break; + } } ret = true; @@ -177,24 +231,22 @@ bool _mongocrypt_kek_append(const _mongocrypt_kek_t *kek, bson_t *bson, mongocry BSON_ASSERT_PARAM(kek); BSON_ASSERT_PARAM(bson); + BSON_APPEND_UTF8(bson, "provider", kek->kmsid); if (kek->kms_provider == MONGOCRYPT_KMS_PROVIDER_AWS) { - BSON_APPEND_UTF8(bson, "provider", "aws"); BSON_APPEND_UTF8(bson, "region", kek->provider.aws.region); BSON_APPEND_UTF8(bson, "key", kek->provider.aws.cmk); if (kek->provider.aws.endpoint) { BSON_APPEND_UTF8(bson, "endpoint", kek->provider.aws.endpoint->host_and_port); } } else if (kek->kms_provider == MONGOCRYPT_KMS_PROVIDER_LOCAL) { - BSON_APPEND_UTF8(bson, "provider", "local"); + // Only `provider` is needed. } else if (kek->kms_provider == MONGOCRYPT_KMS_PROVIDER_AZURE) { - BSON_APPEND_UTF8(bson, "provider", "azure"); BSON_APPEND_UTF8(bson, "keyVaultEndpoint", kek->provider.azure.key_vault_endpoint->host_and_port); BSON_APPEND_UTF8(bson, "keyName", kek->provider.azure.key_name); if (kek->provider.azure.key_version) { BSON_APPEND_UTF8(bson, "keyVersion", kek->provider.azure.key_version); } } else if (kek->kms_provider == MONGOCRYPT_KMS_PROVIDER_GCP) { - BSON_APPEND_UTF8(bson, "provider", "gcp"); BSON_APPEND_UTF8(bson, "projectId", kek->provider.gcp.project_id); BSON_APPEND_UTF8(bson, "location", kek->provider.gcp.location); BSON_APPEND_UTF8(bson, "keyRing", kek->provider.gcp.key_ring); @@ -206,11 +258,14 @@ bool _mongocrypt_kek_append(const _mongocrypt_kek_t *kek, bson_t *bson, mongocry BSON_APPEND_UTF8(bson, "endpoint", kek->provider.gcp.endpoint->host_and_port); } } else if (kek->kms_provider == MONGOCRYPT_KMS_PROVIDER_KMIP) { - BSON_APPEND_UTF8(bson, "provider", "kmip"); if (kek->provider.kmip.endpoint) { BSON_APPEND_UTF8(bson, "endpoint", kek->provider.kmip.endpoint->host_and_port); } + if (kek->provider.kmip.delegated) { + BSON_APPEND_BOOL(bson, "delegated", kek->provider.kmip.delegated); + } + /* "keyId" is required in the final data key document for the "kmip" KMS * provider. It may be set from the "kmip.keyId" in the BSON document set * in mongocrypt_ctx_setopt_key_encryption_key, Otherwise, libmongocrypt @@ -249,11 +304,13 @@ void _mongocrypt_kek_copy_to(const _mongocrypt_kek_t *src, _mongocrypt_kek_t *ds } else if (src->kms_provider == MONGOCRYPT_KMS_PROVIDER_KMIP) { dst->provider.kmip.endpoint = _mongocrypt_endpoint_copy(src->provider.kmip.endpoint); dst->provider.kmip.key_id = bson_strdup(src->provider.kmip.key_id); + dst->provider.kmip.delegated = src->provider.kmip.delegated; } else { BSON_ASSERT(src->kms_provider == MONGOCRYPT_KMS_PROVIDER_NONE || src->kms_provider == MONGOCRYPT_KMS_PROVIDER_LOCAL); } dst->kms_provider = src->kms_provider; + dst->kmsid = bson_strdup(src->kmsid); } void _mongocrypt_kek_cleanup(_mongocrypt_kek_t *kek) { @@ -283,5 +340,6 @@ void _mongocrypt_kek_cleanup(_mongocrypt_kek_t *kek) { BSON_ASSERT(kek->kms_provider == MONGOCRYPT_KMS_PROVIDER_NONE || kek->kms_provider == MONGOCRYPT_KMS_PROVIDER_LOCAL); } + bson_free(kek->kmsid); return; } diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-key-broker-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-key-broker-private.h index b4ca3b74dad..630dd9c0952 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-key-broker-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-key-broker-private.h @@ -87,11 +87,7 @@ typedef struct _key_returned_t { struct _key_returned_t *next; } key_returned_t; -typedef struct _auth_request_t { - mongocrypt_kms_ctx_t kms; - bool returned; - bool initialized; -} auth_request_t; +typedef struct _mc_mapof_kmsid_to_authrequest_t mc_mapof_kmsid_to_authrequest_t; typedef struct { key_broker_state_t state; @@ -109,8 +105,7 @@ typedef struct { mongocrypt_t *crypt; key_returned_t *decryptor_iter; - auth_request_t auth_request_azure; - auth_request_t auth_request_gcp; + mc_mapof_kmsid_to_authrequest_t *auth_requests; } _mongocrypt_key_broker_t; void _mongocrypt_key_broker_init(_mongocrypt_key_broker_t *kb, mongocrypt_t *crypt); diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-key-broker.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-key-broker.c index 77ce882efb8..7af66c4e594 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-key-broker.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-key-broker.c @@ -14,9 +14,87 @@ * limitations under the License. */ +#include "mc-array-private.h" #include "mongocrypt-key-broker-private.h" #include "mongocrypt-private.h" +typedef struct _auth_request_t { + mongocrypt_kms_ctx_t kms; + bool returned; + char *kmsid; +} auth_request_t; + +auth_request_t *auth_request_new() { + return bson_malloc0(sizeof(auth_request_t)); +} + +void auth_request_destroy(auth_request_t *ar) { + if (!ar) { + return; + } + _mongocrypt_kms_ctx_cleanup(&ar->kms); + bson_free(ar->kmsid); + bson_free(ar); +} + +struct _mc_mapof_kmsid_to_authrequest_t { + mc_array_t entries; +}; + +mc_mapof_kmsid_to_authrequest_t *mc_mapof_kmsid_to_authrequest_new(void) { + mc_mapof_kmsid_to_authrequest_t *k2a = bson_malloc0(sizeof(mc_mapof_kmsid_to_authrequest_t)); + _mc_array_init(&k2a->entries, sizeof(auth_request_t *)); + return k2a; +} + +void mc_mapof_kmsid_to_authrequest_destroy(mc_mapof_kmsid_to_authrequest_t *k2a) { + if (!k2a) { + return; + } + for (size_t i = 0; i < k2a->entries.len; i++) { + auth_request_t *ar = _mc_array_index(&k2a->entries, auth_request_t *, i); + auth_request_destroy(ar); + } + _mc_array_destroy(&k2a->entries); + bson_free(k2a); +} + +bool mc_mapof_kmsid_to_authrequest_has(const mc_mapof_kmsid_to_authrequest_t *k2a, const char *kmsid) { + BSON_ASSERT_PARAM(k2a); + BSON_ASSERT_PARAM(kmsid); + for (size_t i = 0; i < k2a->entries.len; i++) { + auth_request_t *ar = _mc_array_index(&k2a->entries, auth_request_t *, i); + if (0 == strcmp(ar->kmsid, kmsid)) { + return true; + } + } + return false; +} + +size_t mc_mapof_kmsid_to_authrequest_len(const mc_mapof_kmsid_to_authrequest_t *k2a) { + BSON_ASSERT_PARAM(k2a); + return k2a->entries.len; +} + +bool mc_mapof_kmsid_to_authrequest_empty(const mc_mapof_kmsid_to_authrequest_t *k2a) { + BSON_ASSERT_PARAM(k2a); + return k2a->entries.len == 0; +} + +// `mc_mapof_kmsid_to_authrequest_put` moves `to_put` into the map and takes ownership of `to_put`. +// No checking is done to prohibit duplicate entries. +void mc_mapof_kmsid_to_authrequest_put(mc_mapof_kmsid_to_authrequest_t *k2a, auth_request_t *to_put) { + BSON_ASSERT_PARAM(k2a); + + _mc_array_append_val(&k2a->entries, to_put); +} + +auth_request_t *mc_mapof_kmsid_to_authrequest_at(mc_mapof_kmsid_to_authrequest_t *k2a, size_t i) { + BSON_ASSERT_PARAM(k2a); + + return _mc_array_index(&k2a->entries, auth_request_t *, i); +} + void _mongocrypt_key_broker_init(_mongocrypt_key_broker_t *kb, mongocrypt_t *crypt) { BSON_ASSERT_PARAM(kb); BSON_ASSERT_PARAM(crypt); @@ -25,6 +103,7 @@ void _mongocrypt_key_broker_init(_mongocrypt_key_broker_t *kb, mongocrypt_t *cry kb->crypt = crypt; kb->state = KB_REQUESTING; kb->status = mongocrypt_status_new(); + kb->auth_requests = mc_mapof_kmsid_to_authrequest_new(); } /* @@ -481,8 +560,12 @@ bool _mongocrypt_key_broker_add_doc(_mongocrypt_key_broker_t *kb, /* Check that the returned key doc's provider matches. */ kek_provider = key_doc->kek.kms_provider; - if (0 == ((int)kek_provider & kms_providers->configured_providers)) { - _key_broker_fail_w_msg(kb, "client not configured with KMS provider necessary to decrypt"); + + mc_kms_creds_t kc; + if (!_mongocrypt_opts_kms_providers_lookup(kms_providers, key_doc->kek.kmsid, &kc)) { + mongocrypt_status_t *status = kb->status; + CLIENT_ERR("KMS provider `%s` is not configured", key_doc->kek.kmsid); + _key_broker_fail(kb); goto done; } @@ -490,8 +573,9 @@ bool _mongocrypt_key_broker_add_doc(_mongocrypt_key_broker_t *kb, * HTTP KMS request. */ BSON_ASSERT(kb->crypt); if (kek_provider == MONGOCRYPT_KMS_PROVIDER_LOCAL) { + BSON_ASSERT(kc.type == MONGOCRYPT_KMS_PROVIDER_LOCAL); if (!_mongocrypt_unwrap_key(kb->crypt->crypto, - &kms_providers->local.key, + &kc.value.local.key, &key_returned->doc->key_material, &key_returned->decrypted_key_material, kb->status)) { @@ -506,38 +590,45 @@ bool _mongocrypt_key_broker_add_doc(_mongocrypt_key_broker_t *kb, if (!_mongocrypt_kms_ctx_init_aws_decrypt(&key_returned->kms, kms_providers, key_doc, - &kb->crypt->log, - kb->crypt->crypto)) { + kb->crypt->crypto, + key_doc->kek.kmsid, + &kb->crypt->log)) { mongocrypt_kms_ctx_status(&key_returned->kms, kb->status); _key_broker_fail(kb); goto done; } } else if (kek_provider == MONGOCRYPT_KMS_PROVIDER_AZURE) { - if (kms_providers->azure.access_token) { - access_token = bson_strdup(kms_providers->azure.access_token); + BSON_ASSERT(kc.type == MONGOCRYPT_KMS_PROVIDER_AZURE); + if (kc.value.azure.access_token) { + access_token = bson_strdup(kc.value.azure.access_token); } else { - access_token = _mongocrypt_cache_oauth_get(kb->crypt->cache_oauth_azure); + access_token = mc_mapof_kmsid_to_token_get_token(kb->crypt->cache_oauth, key_doc->kek.kmsid); } if (!access_token) { key_returned->needs_auth = true; /* Create an oauth request if one does not exist. */ - if (!kb->auth_request_azure.initialized) { - if (!_mongocrypt_kms_ctx_init_azure_auth(&kb->auth_request_azure.kms, - &kb->crypt->log, - kms_providers, + if (!mc_mapof_kmsid_to_authrequest_has(kb->auth_requests, key_doc->kek.kmsid)) { + auth_request_t *ar = auth_request_new(); + if (!_mongocrypt_kms_ctx_init_azure_auth(&ar->kms, + &kc, /* The key vault endpoint is used to determine the scope. */ - key_doc->kek.provider.azure.key_vault_endpoint)) { - mongocrypt_kms_ctx_status(&kb->auth_request_azure.kms, kb->status); + key_doc->kek.provider.azure.key_vault_endpoint, + key_doc->kek.kmsid, + &kb->crypt->log)) { + mongocrypt_kms_ctx_status(&ar->kms, kb->status); _key_broker_fail(kb); + auth_request_destroy(ar); goto done; } - kb->auth_request_azure.initialized = true; + ar->kmsid = bson_strdup(key_doc->kek.kmsid); + mc_mapof_kmsid_to_authrequest_put(kb->auth_requests, ar); } } else { if (!_mongocrypt_kms_ctx_init_azure_unwrapkey(&key_returned->kms, kms_providers, access_token, key_doc, + key_returned->doc->kek.kmsid, &kb->crypt->log)) { mongocrypt_kms_ctx_status(&key_returned->kms, kb->status); _key_broker_fail(kb); @@ -545,31 +636,37 @@ bool _mongocrypt_key_broker_add_doc(_mongocrypt_key_broker_t *kb, } } } else if (kek_provider == MONGOCRYPT_KMS_PROVIDER_GCP) { - if (NULL != kms_providers->gcp.access_token) { - access_token = bson_strdup(kms_providers->gcp.access_token); + BSON_ASSERT(kc.type == MONGOCRYPT_KMS_PROVIDER_GCP); + if (NULL != kc.value.gcp.access_token) { + access_token = bson_strdup(kc.value.gcp.access_token); } else { - access_token = _mongocrypt_cache_oauth_get(kb->crypt->cache_oauth_gcp); + access_token = mc_mapof_kmsid_to_token_get_token(kb->crypt->cache_oauth, key_doc->kek.kmsid); } if (!access_token) { key_returned->needs_auth = true; /* Create an oauth request if one does not exist. */ - if (!kb->auth_request_gcp.initialized) { - if (!_mongocrypt_kms_ctx_init_gcp_auth(&kb->auth_request_gcp.kms, - &kb->crypt->log, + if (!mc_mapof_kmsid_to_authrequest_has(kb->auth_requests, key_doc->kek.kmsid)) { + auth_request_t *ar = auth_request_new(); + if (!_mongocrypt_kms_ctx_init_gcp_auth(&ar->kms, &kb->crypt->opts, - kms_providers, - key_doc->kek.provider.gcp.endpoint)) { - mongocrypt_kms_ctx_status(&kb->auth_request_gcp.kms, kb->status); + &kc, + key_doc->kek.provider.gcp.endpoint, + key_doc->kek.kmsid, + &kb->crypt->log)) { + mongocrypt_kms_ctx_status(&ar->kms, kb->status); _key_broker_fail(kb); + auth_request_destroy(ar); goto done; } - kb->auth_request_gcp.initialized = true; + ar->kmsid = bson_strdup(key_doc->kek.kmsid); + mc_mapof_kmsid_to_authrequest_put(kb->auth_requests, ar); } } else { if (!_mongocrypt_kms_ctx_init_gcp_decrypt(&key_returned->kms, kms_providers, access_token, key_doc, + key_returned->doc->kek.kmsid, &kb->crypt->log)) { mongocrypt_kms_ctx_status(&key_returned->kms, kb->status); _key_broker_fail(kb); @@ -577,6 +674,7 @@ bool _mongocrypt_key_broker_add_doc(_mongocrypt_key_broker_t *kb, } } } else if (kek_provider == MONGOCRYPT_KMS_PROVIDER_KMIP) { + BSON_ASSERT(kc.type == MONGOCRYPT_KMS_PROVIDER_KMIP); char *unique_identifier; _mongocrypt_endpoint_t *endpoint; @@ -589,17 +687,33 @@ bool _mongocrypt_key_broker_add_doc(_mongocrypt_key_broker_t *kb, if (key_returned->doc->kek.provider.kmip.endpoint) { endpoint = key_returned->doc->kek.provider.kmip.endpoint; - } else if (kms_providers->kmip.endpoint) { - endpoint = kms_providers->kmip.endpoint; + } else if (kc.value.kmip.endpoint) { + endpoint = kc.value.kmip.endpoint; } else { _key_broker_fail_w_msg(kb, "endpoint not set for KMIP request"); goto done; } - if (!_mongocrypt_kms_ctx_init_kmip_get(&key_returned->kms, endpoint, unique_identifier, &kb->crypt->log)) { - mongocrypt_kms_ctx_status(&key_returned->kms, kb->status); - _key_broker_fail(kb); - goto done; + if (key_returned->doc->kek.provider.kmip.delegated) { + if (!_mongocrypt_kms_ctx_init_kmip_decrypt(&key_returned->kms, + endpoint, + key_doc->kek.kmsid, + key_doc, + &kb->crypt->log)) { + mongocrypt_kms_ctx_status(&key_returned->kms, kb->status); + _key_broker_fail(kb); + goto done; + } + } else { + if (!_mongocrypt_kms_ctx_init_kmip_get(&key_returned->kms, + endpoint, + unique_identifier, + key_doc->kek.kmsid, + &kb->crypt->log)) { + mongocrypt_kms_ctx_status(&key_returned->kms, kb->status); + _key_broker_fail(kb); + goto done; + } } } else { _key_broker_fail_w_msg(kb, "unrecognized kms provider"); @@ -683,25 +797,40 @@ mongocrypt_kms_ctx_t *_mongocrypt_key_broker_next_kms(_mongocrypt_key_broker_t * } if (kb->state == KB_AUTHENTICATING) { - if (!kb->auth_request_azure.initialized && !kb->auth_request_gcp.initialized) { + if (mc_mapof_kmsid_to_authrequest_empty(kb->auth_requests)) { _key_broker_fail_w_msg(kb, "unexpected, attempting to authenticate but " "KMS request not initialized"); return NULL; } - if (kb->auth_request_azure.initialized && !kb->auth_request_azure.returned) { - kb->auth_request_azure.returned = true; - return &kb->auth_request_azure.kms; - } - if (kb->auth_request_gcp.initialized && !kb->auth_request_gcp.returned) { - kb->auth_request_gcp.returned = true; - return &kb->auth_request_gcp.kms; + // Return the first not-yet-returned auth request. + for (size_t i = 0; i < mc_mapof_kmsid_to_authrequest_len(kb->auth_requests); i++) { + auth_request_t *ar = mc_mapof_kmsid_to_authrequest_at(kb->auth_requests, i); + + if (ar->kms.should_retry) { + ar->kms.should_retry = false; + ar->returned = true; + return &ar->kms; + } + + if (ar->returned) { + continue; + } + ar->returned = true; + return &ar->kms; } return NULL; } + // Check if any requests need retry + for (key_returned_t *ptr = kb->keys_returned; ptr != NULL; ptr = ptr->next) { + if (ptr->kms.should_retry) { + ptr->kms.should_retry = false; + return &ptr->kms; + } + } while (kb->decryptor_iter) { if (!kb->decryptor_iter->decrypted) { key_returned_t *key_returned; @@ -731,30 +860,20 @@ bool _mongocrypt_key_broker_kms_done(_mongocrypt_key_broker_t *kb, _mongocrypt_o bson_t oauth_response; _mongocrypt_buffer_t oauth_response_buf; - if (kb->auth_request_azure.initialized) { - if (!_mongocrypt_kms_ctx_result(&kb->auth_request_azure.kms, &oauth_response_buf)) { - mongocrypt_kms_ctx_status(&kb->auth_request_azure.kms, kb->status); + // Apply tokens from oauth responses to oauth token cache. + for (size_t i = 0; i < mc_mapof_kmsid_to_authrequest_len(kb->auth_requests); i++) { + auth_request_t *ar = mc_mapof_kmsid_to_authrequest_at(kb->auth_requests, i); + + if (!_mongocrypt_kms_ctx_result(&ar->kms, &oauth_response_buf)) { + mongocrypt_kms_ctx_status(&ar->kms, kb->status); return _key_broker_fail(kb); } /* Cache returned tokens. */ BSON_ASSERT(_mongocrypt_buffer_to_bson(&oauth_response_buf, &oauth_response)); - if (!_mongocrypt_cache_oauth_add(kb->crypt->cache_oauth_azure, &oauth_response, kb->status)) { - return false; - } - } - - if (kb->auth_request_gcp.initialized) { - if (!_mongocrypt_kms_ctx_result(&kb->auth_request_gcp.kms, &oauth_response_buf)) { - mongocrypt_kms_ctx_status(&kb->auth_request_gcp.kms, kb->status); + if (!mc_mapof_kmsid_to_token_add_response(kb->crypt->cache_oauth, ar->kmsid, &oauth_response, kb->status)) { return _key_broker_fail(kb); } - - /* Cache returned tokens. */ - BSON_ASSERT(_mongocrypt_buffer_to_bson(&oauth_response_buf, &oauth_response)); - if (!_mongocrypt_cache_oauth_add(kb->crypt->cache_oauth_gcp, &oauth_response, kb->status)) { - return false; - } } /* Auth should be finished, create any remaining KMS requests. */ @@ -765,11 +884,20 @@ bool _mongocrypt_key_broker_kms_done(_mongocrypt_key_broker_t *kb, _mongocrypt_o continue; } + mc_kms_creds_t kc; + if (!_mongocrypt_opts_kms_providers_lookup(kms_providers, key_returned->doc->kek.kmsid, &kc)) { + mongocrypt_status_t *status = kb->status; + CLIENT_ERR("KMS provider `%s` is not configured", key_returned->doc->kek.kmsid); + return _key_broker_fail(kb); + } + if (key_returned->doc->kek.kms_provider == MONGOCRYPT_KMS_PROVIDER_AZURE) { - if (kms_providers->azure.access_token) { - access_token = bson_strdup(kms_providers->azure.access_token); + BSON_ASSERT(kc.type == MONGOCRYPT_KMS_PROVIDER_AZURE); + if (kc.value.azure.access_token) { + access_token = bson_strdup(kc.value.azure.access_token); } else { - access_token = _mongocrypt_cache_oauth_get(kb->crypt->cache_oauth_azure); + access_token = + mc_mapof_kmsid_to_token_get_token(kb->crypt->cache_oauth, key_returned->doc->kek.kmsid); } if (!access_token) { @@ -780,6 +908,7 @@ bool _mongocrypt_key_broker_kms_done(_mongocrypt_key_broker_t *kb, _mongocrypt_o kms_providers, access_token, key_returned->doc, + key_returned->doc->kek.kmsid, &kb->crypt->log)) { mongocrypt_kms_ctx_status(&key_returned->kms, kb->status); bson_free(access_token); @@ -789,10 +918,12 @@ bool _mongocrypt_key_broker_kms_done(_mongocrypt_key_broker_t *kb, _mongocrypt_o key_returned->needs_auth = false; bson_free(access_token); } else if (key_returned->doc->kek.kms_provider == MONGOCRYPT_KMS_PROVIDER_GCP) { - if (NULL != kms_providers->gcp.access_token) { - access_token = bson_strdup(kms_providers->gcp.access_token); + BSON_ASSERT(kc.type == MONGOCRYPT_KMS_PROVIDER_GCP); + if (kc.value.gcp.access_token) { + access_token = bson_strdup(kc.value.gcp.access_token); } else { - access_token = _mongocrypt_cache_oauth_get(kb->crypt->cache_oauth_gcp); + access_token = + mc_mapof_kmsid_to_token_get_token(kb->crypt->cache_oauth, key_returned->doc->kek.kmsid); } if (!access_token) { @@ -803,6 +934,7 @@ bool _mongocrypt_key_broker_kms_done(_mongocrypt_key_broker_t *kb, _mongocrypt_o kms_providers, access_token, key_returned->doc, + key_returned->doc->kek.kmsid, &kb->crypt->log)) { mongocrypt_kms_ctx_status(&key_returned->kms, kb->status); bson_free(access_token); @@ -850,11 +982,16 @@ bool _mongocrypt_key_broker_kms_done(_mongocrypt_key_broker_t *kb, _mongocrypt_o return _key_broker_fail(kb); } - if (!_mongocrypt_unwrap_key(kb->crypt->crypto, - &kek, - &key_returned->doc->key_material, - &key_returned->decrypted_key_material, - kb->status)) { + if (key_returned->doc->kek.provider.kmip.delegated) { + if (!_mongocrypt_kms_ctx_result(&key_returned->kms, &key_returned->decrypted_key_material)) { + mongocrypt_kms_ctx_status(&key_returned->kms, kb->status); + return _key_broker_fail(kb); + } + } else if (!_mongocrypt_unwrap_key(kb->crypt->crypto, + &kek, + &key_returned->doc->key_material, + &key_returned->decrypted_key_material, + kb->status)) { _key_broker_fail(kb); _mongocrypt_buffer_cleanup(&kek); return false; @@ -1008,8 +1145,7 @@ void _mongocrypt_key_broker_cleanup(_mongocrypt_key_broker_t *kb) { _destroy_keys_returned(kb->keys_returned); _destroy_keys_returned(kb->keys_cached); _destroy_key_requests(kb->key_requests); - _mongocrypt_kms_ctx_cleanup(&kb->auth_request_azure.kms); - _mongocrypt_kms_ctx_cleanup(&kb->auth_request_gcp.kms); + mc_mapof_kmsid_to_authrequest_destroy(kb->auth_requests); } void _mongocrypt_key_broker_add_test_key(_mongocrypt_key_broker_t *kb, const _mongocrypt_buffer_t *key_id) { @@ -1026,6 +1162,7 @@ void _mongocrypt_key_broker_add_test_key(_mongocrypt_key_broker_t *kb, const _mo key_returned->decrypted = true; _mongocrypt_buffer_init(&key_returned->decrypted_key_material); _mongocrypt_buffer_resize(&key_returned->decrypted_key_material, MONGOCRYPT_KEY_LEN); + // Initialize test key material with all zeros. memset(key_returned->decrypted_key_material.data, 0, MONGOCRYPT_KEY_LEN); _mongocrypt_key_destroy(key_doc); /* Hijack state and move directly to DONE. */ diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-key-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-key-private.h index 3831d2f5c91..5b681a09154 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-key-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-key-private.h @@ -27,6 +27,11 @@ typedef struct __mongocrypt_key_alt_name_t { bson_value_t value; } _mongocrypt_key_alt_name_t; +// `_mongocrypt_key_alt_name_t` inherits extended alignment from libbson. To dynamically allocate, use aligned +// allocation (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof__mongocrypt_key_alt_name_t, + BSON_ALIGNOF(_mongocrypt_key_alt_name_t) >= BSON_ALIGNOF(bson_value_t)); + typedef struct { bson_t bson; /* original BSON for this key. */ _mongocrypt_buffer_t id; @@ -37,6 +42,10 @@ typedef struct { _mongocrypt_kek_t kek; } _mongocrypt_key_doc_t; +// `_mongocrypt_key_doc_t` inherits extended alignment from libbson. To dynamically allocate, use aligned allocation +// (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof__mongocrypt_key_doc_t, BSON_ALIGNOF(_mongocrypt_key_doc_t) >= BSON_ALIGNOF(bson_t)); + _mongocrypt_key_alt_name_t *_mongocrypt_key_alt_name_new(const bson_value_t *value); bool _mongocrypt_key_alt_name_from_iter(const bson_iter_t *iter, diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-key.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-key.c index 25421d9eea9..003ee6d2bb4 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-key.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-key.c @@ -127,7 +127,7 @@ bool _mongocrypt_key_alt_name_from_iter(const bson_iter_t *iter_in, /* Takes ownership of all fields. */ bool _mongocrypt_key_parse_owned(const bson_t *bson, _mongocrypt_key_doc_t *out, mongocrypt_status_t *status) { - bson_iter_t iter; + bson_iter_t iter = {0}; bool has_id = false, has_key_material = false, has_status = false, has_creation_date = false, has_update_date = false, has_master_key = false; @@ -271,7 +271,10 @@ bool _mongocrypt_key_parse_owned(const bson_t *bson, _mongocrypt_key_doc_t *out, _mongocrypt_key_doc_t *_mongocrypt_key_new(void) { _mongocrypt_key_doc_t *key_doc; - key_doc = (_mongocrypt_key_doc_t *)bson_malloc0(sizeof *key_doc); + key_doc = BSON_ALIGNED_ALLOC(_mongocrypt_key_doc_t); + // Use two sets of braces to avoid erroneous missing-braces warning in GCC. Refer: + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119 + *key_doc = (_mongocrypt_key_doc_t){{0}}; bson_init(&key_doc->bson); return key_doc; @@ -389,7 +392,8 @@ _mongocrypt_key_alt_name_t *_mongocrypt_key_alt_name_create(const char *name, .. _mongocrypt_key_alt_name_t *_mongocrypt_key_alt_name_new(const bson_value_t *value) { BSON_ASSERT_PARAM(value); - _mongocrypt_key_alt_name_t *name = bson_malloc0(sizeof(*name)); + _mongocrypt_key_alt_name_t *name = BSON_ALIGNED_ALLOC(_mongocrypt_key_alt_name_t); + *name = (_mongocrypt_key_alt_name_t){0}; BSON_ASSERT(name); bson_value_copy(value, &name->value); diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-kms-ctx-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-kms-ctx-private.h index e1b66f10427..2a14c795c99 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-kms-ctx-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-kms-ctx-private.h @@ -23,6 +23,7 @@ #include "mongocrypt-compat.h" #include "mongocrypt-crypto-private.h" #include "mongocrypt-endpoint-private.h" +#include "mongocrypt-key-private.h" #include "mongocrypt-opts-private.h" #include "mongocrypt.h" @@ -39,7 +40,10 @@ typedef enum { MONGOCRYPT_KMS_GCP_DECRYPT, MONGOCRYPT_KMS_KMIP_REGISTER, MONGOCRYPT_KMS_KMIP_ACTIVATE, - MONGOCRYPT_KMS_KMIP_GET + MONGOCRYPT_KMS_KMIP_GET, + MONGOCRYPT_KMS_KMIP_CREATE, + MONGOCRYPT_KMS_KMIP_ENCRYPT, + MONGOCRYPT_KMS_KMIP_DECRYPT, } _kms_request_type_t; struct _mongocrypt_kms_ctx_t { @@ -51,76 +55,113 @@ struct _mongocrypt_kms_ctx_t { _mongocrypt_buffer_t result; char *endpoint; _mongocrypt_log_t *log; + char *kmsid; + int64_t sleep_usec; + int attempts; + bool retry_enabled; + bool should_retry; }; +static const int kms_max_attempts = 3; + bool _mongocrypt_kms_ctx_init_aws_decrypt(mongocrypt_kms_ctx_t *kms, _mongocrypt_opts_kms_providers_t *kms_providers, _mongocrypt_key_doc_t *key, - _mongocrypt_log_t *log, - _mongocrypt_crypto_t *crypto) MONGOCRYPT_WARN_UNUSED_RESULT; + _mongocrypt_crypto_t *crypto, + const char *kmsid, + _mongocrypt_log_t *log) MONGOCRYPT_WARN_UNUSED_RESULT; bool _mongocrypt_kms_ctx_init_aws_encrypt(mongocrypt_kms_ctx_t *kms, _mongocrypt_opts_kms_providers_t *kms_providers, struct __mongocrypt_ctx_opts_t *ctx_opts, _mongocrypt_buffer_t *decrypted_key_material, - _mongocrypt_log_t *log, - _mongocrypt_crypto_t *crypto) MONGOCRYPT_WARN_UNUSED_RESULT; + _mongocrypt_crypto_t *crypto, + const char *kmsid, + _mongocrypt_log_t *log) MONGOCRYPT_WARN_UNUSED_RESULT; bool _mongocrypt_kms_ctx_result(mongocrypt_kms_ctx_t *kms, _mongocrypt_buffer_t *out) MONGOCRYPT_WARN_UNUSED_RESULT; void _mongocrypt_kms_ctx_cleanup(mongocrypt_kms_ctx_t *kms); bool _mongocrypt_kms_ctx_init_azure_auth(mongocrypt_kms_ctx_t *kms, - _mongocrypt_log_t *log, - _mongocrypt_opts_kms_providers_t *kms_providers, - _mongocrypt_endpoint_t *key_vault_endpoint) MONGOCRYPT_WARN_UNUSED_RESULT; + const mc_kms_creds_t *kc, + _mongocrypt_endpoint_t *key_vault_endpoint, + const char *kmsid, + _mongocrypt_log_t *log) MONGOCRYPT_WARN_UNUSED_RESULT; bool _mongocrypt_kms_ctx_init_azure_wrapkey(mongocrypt_kms_ctx_t *kms, - _mongocrypt_log_t *log, _mongocrypt_opts_kms_providers_t *kms_providers, struct __mongocrypt_ctx_opts_t *ctx_opts, const char *access_token, - _mongocrypt_buffer_t *plaintext_key_material) MONGOCRYPT_WARN_UNUSED_RESULT; + _mongocrypt_buffer_t *plaintext_key_material, + const char *kmsid, + _mongocrypt_log_t *log) MONGOCRYPT_WARN_UNUSED_RESULT; bool _mongocrypt_kms_ctx_init_azure_unwrapkey(mongocrypt_kms_ctx_t *kms, _mongocrypt_opts_kms_providers_t *kms_providers, const char *access_token, _mongocrypt_key_doc_t *key, + const char *kmsid, _mongocrypt_log_t *log) MONGOCRYPT_WARN_UNUSED_RESULT; bool _mongocrypt_kms_ctx_init_gcp_auth(mongocrypt_kms_ctx_t *kms, - _mongocrypt_log_t *log, _mongocrypt_opts_t *crypt_opts, - _mongocrypt_opts_kms_providers_t *kms_providers, - _mongocrypt_endpoint_t *kms_endpoint) MONGOCRYPT_WARN_UNUSED_RESULT; + const mc_kms_creds_t *kc, + _mongocrypt_endpoint_t *kms_endpoint, + const char *kmsid, + _mongocrypt_log_t *log) MONGOCRYPT_WARN_UNUSED_RESULT; bool _mongocrypt_kms_ctx_init_gcp_encrypt(mongocrypt_kms_ctx_t *kms, - _mongocrypt_log_t *log, _mongocrypt_opts_kms_providers_t *kms_providers, struct __mongocrypt_ctx_opts_t *ctx_opts, const char *access_token, - _mongocrypt_buffer_t *plaintext_key_material) MONGOCRYPT_WARN_UNUSED_RESULT; + _mongocrypt_buffer_t *plaintext_key_material, + const char *kmsid, + _mongocrypt_log_t *log) MONGOCRYPT_WARN_UNUSED_RESULT; bool _mongocrypt_kms_ctx_init_gcp_decrypt(mongocrypt_kms_ctx_t *kms, _mongocrypt_opts_kms_providers_t *kms_providers, const char *access_token, _mongocrypt_key_doc_t *key, + const char *kmsid, _mongocrypt_log_t *log) MONGOCRYPT_WARN_UNUSED_RESULT; bool _mongocrypt_kms_ctx_init_kmip_register(mongocrypt_kms_ctx_t *kms, const _mongocrypt_endpoint_t *endpoint, const uint8_t *secretdata, uint32_t secretdata_len, + + const char *kmsid, _mongocrypt_log_t *log) MONGOCRYPT_WARN_UNUSED_RESULT; bool _mongocrypt_kms_ctx_init_kmip_activate(mongocrypt_kms_ctx_t *kms, const _mongocrypt_endpoint_t *endpoint, const char *unique_identifier, + const char *kmsid, _mongocrypt_log_t *log) MONGOCRYPT_WARN_UNUSED_RESULT; bool _mongocrypt_kms_ctx_init_kmip_get(mongocrypt_kms_ctx_t *kms, const _mongocrypt_endpoint_t *endpoint, const char *unique_identifier, + const char *kmsid, _mongocrypt_log_t *log) MONGOCRYPT_WARN_UNUSED_RESULT; +bool _mongocrypt_kms_ctx_init_kmip_create(mongocrypt_kms_ctx_t *kms, + const _mongocrypt_endpoint_t *endpoint, + const char *kmsid, + _mongocrypt_log_t *log); + +bool _mongocrypt_kms_ctx_init_kmip_encrypt(mongocrypt_kms_ctx_t *kms, + const _mongocrypt_endpoint_t *endpoint, + const char *unique_identifier, + const char *kmsid, + _mongocrypt_buffer_t *plaintext, + _mongocrypt_log_t *log); + +bool _mongocrypt_kms_ctx_init_kmip_decrypt(mongocrypt_kms_ctx_t *kms, + const _mongocrypt_endpoint_t *endpoint, + const char *kmsid, + _mongocrypt_key_doc_t *key, + _mongocrypt_log_t *log); + #endif /* MONGOCRYPT_KMX_CTX_PRIVATE_H */ diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-kms-ctx.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-kms-ctx.c index 6886b557d47..1d431177b0e 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-kms-ctx.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-kms-ctx.c @@ -14,15 +14,21 @@ * limitations under the License. */ +#include "kms_message/kms_kmip_request.h" #include "mongocrypt-binary-private.h" #include "mongocrypt-buffer-private.h" +#include "mongocrypt-crypto-private.h" #include "mongocrypt-ctx-private.h" +#include "mongocrypt-endpoint-private.h" +#include "mongocrypt-kek-private.h" #include "mongocrypt-kms-ctx-private.h" +#include "mongocrypt-log-private.h" #include "mongocrypt-opts-private.h" #include "mongocrypt-private.h" #include "mongocrypt-status-private.h" #include "mongocrypt-util-private.h" #include "mongocrypt.h" +#include #include #include #include @@ -118,11 +124,16 @@ _set_kms_crypto_hooks(_mongocrypt_crypto_t *crypto, ctx_with_status_t *ctx_with_ static bool is_kms(_kms_request_type_t kms_type) { return kms_type == MONGOCRYPT_KMS_KMIP_REGISTER || kms_type == MONGOCRYPT_KMS_KMIP_ACTIVATE - || kms_type == MONGOCRYPT_KMS_KMIP_GET; + || kms_type == MONGOCRYPT_KMS_KMIP_GET || kms_type == MONGOCRYPT_KMS_KMIP_ENCRYPT + || kms_type == MONGOCRYPT_KMS_KMIP_DECRYPT || kms_type == MONGOCRYPT_KMS_KMIP_CREATE; } -static void _init_common(mongocrypt_kms_ctx_t *kms, _mongocrypt_log_t *log, _kms_request_type_t kms_type) { +static void +_init_common(mongocrypt_kms_ctx_t *kms, _mongocrypt_log_t *log, _kms_request_type_t kms_type, const char *kmsid) { BSON_ASSERT_PARAM(kms); + BSON_ASSERT_PARAM(kmsid); + + kms->kmsid = bson_strdup(kmsid); if (is_kms(kms_type)) { kms->parser = kms_kmip_response_parser_new(NULL /* reserved */); @@ -133,13 +144,17 @@ static void _init_common(mongocrypt_kms_ctx_t *kms, _mongocrypt_log_t *log, _kms kms->status = mongocrypt_status_new(); kms->req_type = kms_type; _mongocrypt_buffer_init(&kms->result); + kms->sleep_usec = 0; + kms->attempts = 0; + kms->should_retry = false; } bool _mongocrypt_kms_ctx_init_aws_decrypt(mongocrypt_kms_ctx_t *kms, _mongocrypt_opts_kms_providers_t *kms_providers, _mongocrypt_key_doc_t *key, - _mongocrypt_log_t *log, - _mongocrypt_crypto_t *crypto) { + _mongocrypt_crypto_t *crypto, + const char *kmsid, + _mongocrypt_log_t *log) { BSON_ASSERT_PARAM(kms); BSON_ASSERT_PARAM(key); BSON_ASSERT_PARAM(kms_providers); @@ -150,7 +165,7 @@ bool _mongocrypt_kms_ctx_init_aws_decrypt(mongocrypt_kms_ctx_t *kms, ctx_with_status_t ctx_with_status; bool ret = false; - _init_common(kms, log, MONGOCRYPT_KMS_AWS_DECRYPT); + _init_common(kms, log, MONGOCRYPT_KMS_AWS_DECRYPT, kmsid); status = kms->status; ctx_with_status.ctx = crypto; ctx_with_status.status = mongocrypt_status_new(); @@ -170,17 +185,19 @@ bool _mongocrypt_kms_ctx_init_aws_decrypt(mongocrypt_kms_ctx_t *kms, goto done; } - if (0 == (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_AWS)) { - CLIENT_ERR("aws kms not configured"); + mc_kms_creds_t kc; + if (!_mongocrypt_opts_kms_providers_lookup(kms_providers, key->kek.kmsid, &kc)) { + CLIENT_ERR("KMS provider `%s` is not configured", key->kek.kmsid); goto done; } + BSON_ASSERT(kc.type == MONGOCRYPT_KMS_PROVIDER_AWS); - if (!kms_providers->aws.access_key_id) { + if (!kc.value.aws.access_key_id) { CLIENT_ERR("aws access key id not provided"); goto done; } - if (!kms_providers->aws.secret_access_key) { + if (!kc.value.aws.secret_access_key) { CLIENT_ERR("aws secret access key not provided"); goto done; } @@ -201,8 +218,8 @@ bool _mongocrypt_kms_ctx_init_aws_decrypt(mongocrypt_kms_ctx_t *kms, goto done; } - if (kms_providers->aws.session_token) { - if (!kms_request_add_header_field(kms->req, "X-Amz-Security-Token", kms_providers->aws.session_token)) { + if (kc.value.aws.session_token) { + if (!kms_request_add_header_field(kms->req, "X-Amz-Security-Token", kc.value.aws.session_token)) { CLIENT_ERR("failed to set session token: %s", kms_request_get_error(kms->req)); _mongocrypt_status_append(status, ctx_with_status.status); goto done; @@ -230,12 +247,12 @@ bool _mongocrypt_kms_ctx_init_aws_decrypt(mongocrypt_kms_ctx_t *kms, goto done; } - if (!kms_request_set_access_key_id(kms->req, kms_providers->aws.access_key_id)) { + if (!kms_request_set_access_key_id(kms->req, kc.value.aws.access_key_id)) { CLIENT_ERR("failed to set aws access key id: %s", kms_request_get_error(kms->req)); _mongocrypt_status_append(status, ctx_with_status.status); goto done; } - if (!kms_request_set_secret_key(kms->req, kms_providers->aws.secret_access_key)) { + if (!kms_request_set_secret_key(kms->req, kc.value.aws.secret_access_key)) { CLIENT_ERR("failed to set aws secret access key: %s", kms_request_get_error(kms->req)); _mongocrypt_status_append(status, ctx_with_status.status); goto done; @@ -270,8 +287,9 @@ bool _mongocrypt_kms_ctx_init_aws_encrypt(mongocrypt_kms_ctx_t *kms, _mongocrypt_opts_kms_providers_t *kms_providers, _mongocrypt_ctx_opts_t *ctx_opts, _mongocrypt_buffer_t *plaintext_key_material, - _mongocrypt_log_t *log, - _mongocrypt_crypto_t *crypto) { + _mongocrypt_crypto_t *crypto, + const char *kmsid, + _mongocrypt_log_t *log) { BSON_ASSERT_PARAM(kms); BSON_ASSERT_PARAM(ctx_opts); BSON_ASSERT_PARAM(kms_providers); @@ -283,7 +301,7 @@ bool _mongocrypt_kms_ctx_init_aws_encrypt(mongocrypt_kms_ctx_t *kms, ctx_with_status_t ctx_with_status; bool ret = false; - _init_common(kms, log, MONGOCRYPT_KMS_AWS_ENCRYPT); + _init_common(kms, log, MONGOCRYPT_KMS_AWS_ENCRYPT, kmsid); status = kms->status; ctx_with_status.ctx = crypto; ctx_with_status.status = mongocrypt_status_new(); @@ -303,17 +321,19 @@ bool _mongocrypt_kms_ctx_init_aws_encrypt(mongocrypt_kms_ctx_t *kms, goto done; } - if (0 == (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_AWS)) { - CLIENT_ERR("aws kms not configured"); + mc_kms_creds_t kc; + if (!_mongocrypt_opts_kms_providers_lookup(kms_providers, ctx_opts->kek.kmsid, &kc)) { + CLIENT_ERR("KMS provider `%s` is not configured", ctx_opts->kek.kmsid); goto done; } + BSON_ASSERT(kc.type == MONGOCRYPT_KMS_PROVIDER_AWS); - if (!kms_providers->aws.access_key_id) { + if (!kc.value.aws.access_key_id) { CLIENT_ERR("aws access key id not provided"); goto done; } - if (!kms_providers->aws.secret_access_key) { + if (!kc.value.aws.secret_access_key) { CLIENT_ERR("aws secret access key not provided"); goto done; } @@ -337,8 +357,8 @@ bool _mongocrypt_kms_ctx_init_aws_encrypt(mongocrypt_kms_ctx_t *kms, goto done; } - if (kms_providers->aws.session_token) { - if (!kms_request_add_header_field(kms->req, "X-Amz-Security-Token", kms_providers->aws.session_token)) { + if (kc.value.aws.session_token) { + if (!kms_request_add_header_field(kms->req, "X-Amz-Security-Token", kc.value.aws.session_token)) { CLIENT_ERR("failed to set session token: %s", kms_request_get_error(kms->req)); _mongocrypt_status_append(status, ctx_with_status.status); goto done; @@ -366,12 +386,12 @@ bool _mongocrypt_kms_ctx_init_aws_encrypt(mongocrypt_kms_ctx_t *kms, goto done; } - if (!kms_request_set_access_key_id(kms->req, kms_providers->aws.access_key_id)) { + if (!kms_request_set_access_key_id(kms->req, kc.value.aws.access_key_id)) { CLIENT_ERR("failed to set aws access key id: %s", kms_request_get_error(kms->req)); _mongocrypt_status_append(status, ctx_with_status.status); goto done; } - if (!kms_request_set_secret_key(kms->req, kms_providers->aws.secret_access_key)) { + if (!kms_request_set_secret_key(kms->req, kc.value.aws.secret_access_key)) { CLIENT_ERR("failed to set aws secret access key: %s", kms_request_get_error(kms->req)); _mongocrypt_status_append(status, ctx_with_status.status); goto done; @@ -412,11 +432,21 @@ uint32_t mongocrypt_kms_ctx_bytes_needed(mongocrypt_kms_ctx_t *kms) { if (!mongocrypt_status_ok(kms->status) || !_mongocrypt_buffer_empty(&kms->result)) { return 0; } + if (kms->should_retry) { + return 0; + } want_bytes = kms_response_parser_wants_bytes(kms->parser, DEFAULT_MAX_KMS_BYTE_REQUEST); BSON_ASSERT(want_bytes >= 0); return (uint32_t)want_bytes; } +int64_t mongocrypt_kms_ctx_usleep(mongocrypt_kms_ctx_t *kms) { + if (!kms) { + return 0; + } + return kms->sleep_usec; +} + static void _handle_non200_http_status(int http_status, const char *body, size_t body_len, mongocrypt_status_t *status) { BSON_ASSERT_PARAM(body); @@ -440,6 +470,56 @@ _handle_non200_http_status(int http_status, const char *body, size_t body_len, m CLIENT_ERR("Error in KMS response. HTTP status=%d. Response body=\n%s", http_status, body); } +static int64_t backoff_time_usec(int64_t attempts) { + static bool seeded = false; + if (!seeded) { + srand((uint32_t)time(NULL)); + seeded = true; + } + + /* Exponential backoff with jitter. */ + const int64_t base = 200000; /* 0.2 seconds */ + const int64_t max = 20000000; /* 20 seconds */ + BSON_ASSERT(attempts > 0); + int64_t backoff = base * ((int64_t)1 << (attempts - 1)); + if (backoff > max) { + backoff = max; + } + + /* Full jitter: between 1 and current max */ + return (int64_t)((double)rand() / (double)RAND_MAX * (double)backoff) + 1; +} + +static bool should_retry_http(int http_status, _kms_request_type_t t) { + static const int retryable_aws[] = {408, 429, 500, 502, 503, 509}; + static const int retryable_azure[] = {408, 429, 500, 502, 503, 504}; + if (t == MONGOCRYPT_KMS_AWS_ENCRYPT || t == MONGOCRYPT_KMS_AWS_DECRYPT) { + for (size_t i = 0; i < sizeof(retryable_aws) / sizeof(retryable_aws[0]); i++) { + if (http_status == retryable_aws[i]) { + return true; + } + } + } else if (t == MONGOCRYPT_KMS_AZURE_WRAPKEY || t == MONGOCRYPT_KMS_AZURE_UNWRAPKEY + || t == MONGOCRYPT_KMS_AZURE_OAUTH) { + for (size_t i = 0; i < sizeof(retryable_azure) / sizeof(retryable_azure[0]); i++) { + if (http_status == retryable_azure[i]) { + return true; + } + } + } else if (t == MONGOCRYPT_KMS_GCP_ENCRYPT || t == MONGOCRYPT_KMS_GCP_DECRYPT || t == MONGOCRYPT_KMS_GCP_OAUTH) { + if (http_status == 408 || http_status == 429 || http_status / 500 == 1) { + return true; + } + } + return false; +} + +static void set_retry(mongocrypt_kms_ctx_t *kms) { + kms->should_retry = true; + kms->attempts++; + kms->sleep_usec = backoff_time_usec(kms->attempts); +} + /* An AWS KMS context has received full response. Parse out the result or error. */ static bool _ctx_done_aws(mongocrypt_kms_ctx_t *kms, const char *json_field) { @@ -464,8 +544,27 @@ static bool _ctx_done_aws(mongocrypt_kms_ctx_t *kms, const char *json_field) { /* Parse out the {en|de}crypted result. */ http_status = kms_response_parser_status(kms->parser); response = kms_response_parser_get_response(kms->parser); + if (!response) { + CLIENT_ERR("Failed to get response from parser: %s", kms_response_parser_error(kms->parser)); + goto fail; + } body = kms_response_get_body(response, &body_len); + if (kms->retry_enabled && should_retry_http(http_status, kms->req_type)) { + if (kms->attempts >= kms_max_attempts) { + // Wrap error to indicate maximum retries occurred. + _handle_non200_http_status(http_status, body, body_len, status); + CLIENT_ERR("KMS request failed after maximum of %d retries: %s", + kms_max_attempts, + mongocrypt_status_message(status, NULL)); + goto fail; + } else { + ret = true; + set_retry(kms); + goto fail; + } + } + if (http_status != 200) { _handle_non200_http_status(http_status, body, body_len, status); goto fail; @@ -541,8 +640,27 @@ static bool _ctx_done_oauth(mongocrypt_kms_ctx_t *kms) { /* Parse out the oauth token result (or error). */ http_status = kms_response_parser_status(kms->parser); response = kms_response_parser_get_response(kms->parser); + if (!response) { + CLIENT_ERR("Failed to get response from parser: %s", kms_response_parser_error(kms->parser)); + goto fail; + } body = kms_response_get_body(response, &body_len); + if (kms->retry_enabled && should_retry_http(http_status, kms->req_type)) { + if (kms->attempts >= kms_max_attempts) { + // Wrap error to indicate maximum retries occurred. + _handle_non200_http_status(http_status, body, body_len, status); + CLIENT_ERR("KMS request failed after maximum of %d retries: %s", + kms_max_attempts, + mongocrypt_status_message(status, NULL)); + goto fail; + } else { + ret = true; + set_retry(kms); + goto fail; + } + } + if (body_len == 0) { CLIENT_ERR("Empty KMS response. HTTP status=%d", http_status); goto fail; @@ -614,8 +732,27 @@ static bool _ctx_done_azure_wrapkey_unwrapkey(mongocrypt_kms_ctx_t *kms) { /* Parse out the oauth token result (or error). */ http_status = kms_response_parser_status(kms->parser); response = kms_response_parser_get_response(kms->parser); + if (!response) { + CLIENT_ERR("Failed to get response from parser: %s", kms_response_parser_error(kms->parser)); + goto fail; + } body = kms_response_get_body(response, &body_len); + if (kms->retry_enabled && should_retry_http(http_status, kms->req_type)) { + if (kms->attempts >= kms_max_attempts) { + // Wrap error to indicate maximum retries occurred. + _handle_non200_http_status(http_status, body, body_len, status); + CLIENT_ERR("KMS request failed after maximum of %d retries: %s", + kms_max_attempts, + mongocrypt_status_message(status, NULL)); + goto fail; + } else { + ret = true; + set_retry(kms); + goto fail; + } + } + if (body_len == 0) { CLIENT_ERR("Empty KMS response. HTTP status=%d", http_status); goto fail; @@ -704,8 +841,27 @@ static bool _ctx_done_gcp(mongocrypt_kms_ctx_t *kms, const char *json_field) { /* Parse out the {en|de}crypted result. */ http_status = kms_response_parser_status(kms->parser); response = kms_response_parser_get_response(kms->parser); + if (!response) { + CLIENT_ERR("Failed to get response from parser: %s", kms_response_parser_error(kms->parser)); + goto fail; + } body = kms_response_get_body(response, &body_len); + if (kms->retry_enabled && should_retry_http(http_status, kms->req_type)) { + if (kms->attempts >= kms_max_attempts) { + // Wrap error to indicate maximum retries occurred. + _handle_non200_http_status(http_status, body, body_len, status); + CLIENT_ERR("KMS request failed after maximum of %d retries: %s", + kms_max_attempts, + mongocrypt_status_message(status, NULL)); + goto fail; + } else { + ret = true; + set_retry(kms); + goto fail; + } + } + if (http_status != 200) { _handle_non200_http_status(http_status, body, body_len, status); goto fail; @@ -826,6 +982,193 @@ done: return ret; } +static bool _ctx_done_kmip_create(mongocrypt_kms_ctx_t *kms_ctx) { + BSON_ASSERT_PARAM(kms_ctx); + + kms_response_t *res = NULL; + + mongocrypt_status_t *status = kms_ctx->status; + bool ret = false; + char *uid; + + res = kms_response_parser_get_response(kms_ctx->parser); + if (!res) { + CLIENT_ERR("Error getting KMIP response: %s", kms_response_parser_error(kms_ctx->parser)); + goto done; + } + + uid = kms_kmip_response_get_unique_identifier(res); + if (!uid) { + CLIENT_ERR("Error getting UniqueIdentifer from KMIP Create response: %s", kms_response_get_error(res)); + goto done; + } + + if (!_mongocrypt_buffer_steal_from_string(&kms_ctx->result, uid)) { + CLIENT_ERR("Error storing KMS UniqueIdentifer result"); + bson_free(uid); + goto done; + } + ret = true; + +done: + kms_response_destroy(res); + return ret; +} + +static bool _ctx_done_kmip_encrypt(mongocrypt_kms_ctx_t *kms_ctx) { + BSON_ASSERT_PARAM(kms_ctx); + + kms_response_t *res = NULL; + + mongocrypt_status_t *status = kms_ctx->status; + bool ret = false; + uint8_t *ciphertext; + size_t ciphertext_len; + uint8_t *iv; + size_t iv_len; + _mongocrypt_buffer_t data_buf, iv_buf; + _mongocrypt_buffer_init(&data_buf); + _mongocrypt_buffer_init(&iv_buf); + + res = kms_response_parser_get_response(kms_ctx->parser); + if (!res) { + CLIENT_ERR("Error getting KMIP response: %s", kms_response_parser_error(kms_ctx->parser)); + goto done; + } + + ciphertext = kms_kmip_response_get_data(res, &ciphertext_len); + if (!ciphertext) { + CLIENT_ERR("Error getting data from KMIP Encrypt response: %s", kms_response_get_error(res)); + goto done; + } + + iv = kms_kmip_response_get_iv(res, &iv_len); + if (!iv) { + CLIENT_ERR("Error getting IV from KMIP Encrypt response: %s", kms_response_get_error(res)); + bson_free(ciphertext); + goto done; + } + + if (iv_len != MONGOCRYPT_IV_LEN) { + CLIENT_ERR("KMIP IV response has unexpected length: %zu", iv_len); + bson_free(ciphertext); + bson_free(iv); + goto done; + } + + if (!_mongocrypt_buffer_steal_from_data_and_size(&data_buf, ciphertext, ciphertext_len)) { + CLIENT_ERR("Error storing KMS Encrypt result"); + bson_free(ciphertext); + bson_free(iv); + goto done; + } + + if (!_mongocrypt_buffer_steal_from_data_and_size(&iv_buf, iv, iv_len)) { + CLIENT_ERR("Error storing KMS Encrypt IV"); + bson_free(ciphertext); + bson_free(iv); + goto done; + } + + const _mongocrypt_buffer_t results_buf[2] = {iv_buf, data_buf}; + if (!_mongocrypt_buffer_concat(&kms_ctx->result, results_buf, 2)) { + CLIENT_ERR("Error concatenating IV and ciphertext"); + goto done; + } + + ret = true; + +done: + kms_response_destroy(res); + _mongocrypt_buffer_cleanup(&iv_buf); + _mongocrypt_buffer_cleanup(&data_buf); + return ret; +} + +static bool _ctx_done_kmip_decrypt(mongocrypt_kms_ctx_t *kms_ctx) { + BSON_ASSERT_PARAM(kms_ctx); + + kms_response_t *res = NULL; + + mongocrypt_status_t *status = kms_ctx->status; + bool ret = false; + uint8_t *ciphertext; + size_t ciphertext_len; + + res = kms_response_parser_get_response(kms_ctx->parser); + if (!res) { + CLIENT_ERR("Error getting KMIP response: %s", kms_response_parser_error(kms_ctx->parser)); + goto done; + } + + ciphertext = kms_kmip_response_get_data(res, &ciphertext_len); + if (!ciphertext) { + CLIENT_ERR("Error getting data from KMIP Decrypt response: %s", kms_response_get_error(res)); + goto done; + } + + if (!_mongocrypt_buffer_steal_from_data_and_size(&kms_ctx->result, ciphertext, ciphertext_len)) { + CLIENT_ERR("Error storing KMS Decrypt result"); + bson_free(ciphertext); + goto done; + } + + ret = true; + +done: + kms_response_destroy(res); + return ret; +} + +bool mongocrypt_kms_ctx_fail(mongocrypt_kms_ctx_t *kms) { + if (!kms || !kms->retry_enabled) { + return false; + } + + kms->should_retry = false; + mongocrypt_status_t *status = kms->status; + + if (!kms->retry_enabled) { + CLIENT_ERR("KMS request failed due to network error"); + return false; + } + + if (kms->attempts >= kms_max_attempts) { + CLIENT_ERR("KMS request failed after %d retries due to a network error", kms_max_attempts); + return false; + } + + // Check if request type is retryable. Some requests are non-idempotent and cannot be safely retried. + _kms_request_type_t retryable_types[] = {MONGOCRYPT_KMS_AZURE_OAUTH, + MONGOCRYPT_KMS_GCP_OAUTH, + MONGOCRYPT_KMS_AWS_ENCRYPT, + MONGOCRYPT_KMS_AWS_DECRYPT, + MONGOCRYPT_KMS_AZURE_WRAPKEY, + MONGOCRYPT_KMS_AZURE_UNWRAPKEY, + MONGOCRYPT_KMS_GCP_ENCRYPT, + MONGOCRYPT_KMS_GCP_DECRYPT}; + bool is_retryable = false; + for (size_t i = 0; i < sizeof(retryable_types) / sizeof(retryable_types[0]); i++) { + if (retryable_types[i] == kms->req_type) { + is_retryable = true; + break; + } + } + if (!is_retryable) { + CLIENT_ERR("KMS request failed due to network error"); + return false; + } + + // Mark KMS context as retryable. Return again in `mongocrypt_ctx_next_kms_ctx`. + set_retry(kms); + + // Reset intermediate state of parser. + if (kms->parser) { + kms_response_parser_reset(kms->parser); + } + return true; +} + bool mongocrypt_kms_ctx_feed(mongocrypt_kms_ctx_t *kms, mongocrypt_binary_t *bytes) { if (!kms) { return false; @@ -889,6 +1232,9 @@ bool mongocrypt_kms_ctx_feed(mongocrypt_kms_ctx_t *kms, mongocrypt_binary_t *byt case MONGOCRYPT_KMS_KMIP_REGISTER: return _ctx_done_kmip_register(kms); case MONGOCRYPT_KMS_KMIP_ACTIVATE: return _ctx_done_kmip_activate(kms); case MONGOCRYPT_KMS_KMIP_GET: return _ctx_done_kmip_get(kms); + case MONGOCRYPT_KMS_KMIP_ENCRYPT: return _ctx_done_kmip_encrypt(kms); + case MONGOCRYPT_KMS_KMIP_DECRYPT: return _ctx_done_kmip_decrypt(kms); + case MONGOCRYPT_KMS_KMIP_CREATE: return _ctx_done_kmip_create(kms); } } return true; @@ -948,6 +1294,7 @@ void _mongocrypt_kms_ctx_cleanup(mongocrypt_kms_ctx_t *kms) { _mongocrypt_buffer_cleanup(&kms->msg); _mongocrypt_buffer_cleanup(&kms->result); bson_free(kms->endpoint); + bson_free(kms->kmsid); } bool mongocrypt_kms_ctx_message(mongocrypt_kms_ctx_t *kms, mongocrypt_binary_t *msg) { @@ -979,24 +1326,27 @@ bool mongocrypt_kms_ctx_endpoint(mongocrypt_kms_ctx_t *kms, const char **endpoin } bool _mongocrypt_kms_ctx_init_azure_auth(mongocrypt_kms_ctx_t *kms, - _mongocrypt_log_t *log, - _mongocrypt_opts_kms_providers_t *kms_providers, - _mongocrypt_endpoint_t *key_vault_endpoint) { + const mc_kms_creds_t *kc, + _mongocrypt_endpoint_t *key_vault_endpoint, + const char *kmsid, + _mongocrypt_log_t *log) { BSON_ASSERT_PARAM(kms); - BSON_ASSERT_PARAM(kms_providers); + BSON_ASSERT_PARAM(kc); kms_request_opt_t *opt = NULL; mongocrypt_status_t *status; - _mongocrypt_endpoint_t *identity_platform_endpoint; + const _mongocrypt_endpoint_t *identity_platform_endpoint; char *scope = NULL; const char *hostname; char *request_string; bool ret = false; - _init_common(kms, log, MONGOCRYPT_KMS_AZURE_OAUTH); + _init_common(kms, log, MONGOCRYPT_KMS_AZURE_OAUTH, kmsid); status = kms->status; - identity_platform_endpoint = kms_providers->azure.identity_platform_endpoint; + BSON_ASSERT(kc->type == MONGOCRYPT_KMS_PROVIDER_AZURE); + + identity_platform_endpoint = kc->value.azure.identity_platform_endpoint; if (identity_platform_endpoint) { kms->endpoint = bson_strdup(identity_platform_endpoint->host_and_port); @@ -1022,9 +1372,9 @@ bool _mongocrypt_kms_ctx_init_azure_auth(mongocrypt_kms_ctx_t *kms, kms_request_opt_set_provider(opt, KMS_REQUEST_PROVIDER_AZURE); kms->req = kms_azure_request_oauth_new(hostname, scope, - kms_providers->azure.tenant_id, - kms_providers->azure.client_id, - kms_providers->azure.client_secret, + kc->value.azure.tenant_id, + kc->value.azure.client_id, + kc->value.azure.client_secret, opt); if (kms_request_get_error(kms->req)) { CLIENT_ERR("error constructing KMS message: %s", kms_request_get_error(kms->req)); @@ -1049,11 +1399,12 @@ fail: } bool _mongocrypt_kms_ctx_init_azure_wrapkey(mongocrypt_kms_ctx_t *kms, - _mongocrypt_log_t *log, _mongocrypt_opts_kms_providers_t *kms_providers, struct __mongocrypt_ctx_opts_t *ctx_opts, const char *access_token, - _mongocrypt_buffer_t *plaintext_key_material) { + _mongocrypt_buffer_t *plaintext_key_material, + const char *kmsid, + _mongocrypt_log_t *log) { BSON_ASSERT_PARAM(kms); BSON_ASSERT_PARAM(ctx_opts); BSON_ASSERT_PARAM(plaintext_key_material); @@ -1066,7 +1417,7 @@ bool _mongocrypt_kms_ctx_init_azure_wrapkey(mongocrypt_kms_ctx_t *kms, char *request_string; bool ret = false; - _init_common(kms, log, MONGOCRYPT_KMS_AZURE_WRAPKEY); + _init_common(kms, log, MONGOCRYPT_KMS_AZURE_WRAPKEY, kmsid); status = kms->status; BSON_ASSERT(ctx_opts->kek.provider.azure.key_vault_endpoint); @@ -1114,6 +1465,7 @@ bool _mongocrypt_kms_ctx_init_azure_unwrapkey(mongocrypt_kms_ctx_t *kms, _mongocrypt_opts_kms_providers_t *kms_providers, const char *access_token, _mongocrypt_key_doc_t *key, + const char *kmsid, _mongocrypt_log_t *log) { BSON_ASSERT_PARAM(kms); BSON_ASSERT_PARAM(key); @@ -1126,7 +1478,7 @@ bool _mongocrypt_kms_ctx_init_azure_unwrapkey(mongocrypt_kms_ctx_t *kms, char *request_string; bool ret = false; - _init_common(kms, log, MONGOCRYPT_KMS_AZURE_UNWRAPKEY); + _init_common(kms, log, MONGOCRYPT_KMS_AZURE_UNWRAPKEY, kmsid); status = kms->status; BSON_ASSERT(key->kek.provider.azure.key_vault_endpoint); @@ -1212,17 +1564,18 @@ static bool _sign_rsaes_pkcs1_v1_5_trampoline(void *ctx, } bool _mongocrypt_kms_ctx_init_gcp_auth(mongocrypt_kms_ctx_t *kms, - _mongocrypt_log_t *log, _mongocrypt_opts_t *crypt_opts, - _mongocrypt_opts_kms_providers_t *kms_providers, - _mongocrypt_endpoint_t *kms_endpoint) { + const mc_kms_creds_t *kc, + _mongocrypt_endpoint_t *kms_endpoint, + const char *kmsid, + _mongocrypt_log_t *log) { BSON_ASSERT_PARAM(kms); - BSON_ASSERT_PARAM(kms_providers); + BSON_ASSERT_PARAM(kc); BSON_ASSERT_PARAM(crypt_opts); kms_request_opt_t *opt = NULL; mongocrypt_status_t *status; - _mongocrypt_endpoint_t *auth_endpoint; + const _mongocrypt_endpoint_t *auth_endpoint; char *scope = NULL; char *audience = NULL; const char *hostname; @@ -1230,12 +1583,14 @@ bool _mongocrypt_kms_ctx_init_gcp_auth(mongocrypt_kms_ctx_t *kms, bool ret = false; ctx_with_status_t ctx_with_status; - _init_common(kms, log, MONGOCRYPT_KMS_GCP_OAUTH); + _init_common(kms, log, MONGOCRYPT_KMS_GCP_OAUTH, kmsid); status = kms->status; ctx_with_status.ctx = crypt_opts; ctx_with_status.status = mongocrypt_status_new(); - auth_endpoint = kms_providers->gcp.endpoint; + BSON_ASSERT(kc->type == MONGOCRYPT_KMS_PROVIDER_GCP); + + auth_endpoint = kc->value.gcp.endpoint; if (auth_endpoint) { kms->endpoint = bson_strdup(auth_endpoint->host_and_port); hostname = auth_endpoint->host; @@ -1262,11 +1617,11 @@ bool _mongocrypt_kms_ctx_init_gcp_auth(mongocrypt_kms_ctx_t *kms, kms_request_opt_set_crypto_hook_sign_rsaes_pkcs1_v1_5(opt, _sign_rsaes_pkcs1_v1_5_trampoline, &ctx_with_status); } kms->req = kms_gcp_request_oauth_new(hostname, - kms_providers->gcp.email, + kc->value.gcp.email, audience, scope, - (const char *)kms_providers->gcp.private_key.data, - kms_providers->gcp.private_key.len, + (const char *)kc->value.gcp.private_key.data, + kc->value.gcp.private_key.len, opt); if (kms_request_get_error(kms->req)) { CLIENT_ERR("error constructing KMS message: %s", kms_request_get_error(kms->req)); @@ -1295,11 +1650,12 @@ fail: } bool _mongocrypt_kms_ctx_init_gcp_encrypt(mongocrypt_kms_ctx_t *kms, - _mongocrypt_log_t *log, _mongocrypt_opts_kms_providers_t *kms_providers, struct __mongocrypt_ctx_opts_t *ctx_opts, const char *access_token, - _mongocrypt_buffer_t *plaintext_key_material) { + _mongocrypt_buffer_t *plaintext_key_material, + const char *kmsid, + _mongocrypt_log_t *log) { BSON_ASSERT_PARAM(kms); BSON_ASSERT_PARAM(ctx_opts); BSON_ASSERT_PARAM(kms_providers); @@ -1314,7 +1670,7 @@ bool _mongocrypt_kms_ctx_init_gcp_encrypt(mongocrypt_kms_ctx_t *kms, char *request_string; bool ret = false; - _init_common(kms, log, MONGOCRYPT_KMS_GCP_ENCRYPT); + _init_common(kms, log, MONGOCRYPT_KMS_GCP_ENCRYPT, kmsid); status = kms->status; if (ctx_opts->kek.provider.gcp.endpoint) { @@ -1368,6 +1724,7 @@ bool _mongocrypt_kms_ctx_init_gcp_decrypt(mongocrypt_kms_ctx_t *kms, _mongocrypt_opts_kms_providers_t *kms_providers, const char *access_token, _mongocrypt_key_doc_t *key, + const char *kmsid, _mongocrypt_log_t *log) { BSON_ASSERT_PARAM(kms); BSON_ASSERT_PARAM(kms_providers); @@ -1382,7 +1739,7 @@ bool _mongocrypt_kms_ctx_init_gcp_decrypt(mongocrypt_kms_ctx_t *kms, char *request_string; bool ret = false; - _init_common(kms, log, MONGOCRYPT_KMS_GCP_DECRYPT); + _init_common(kms, log, MONGOCRYPT_KMS_GCP_DECRYPT, kmsid); status = kms->status; if (key->kek.provider.gcp.endpoint) { @@ -1435,6 +1792,7 @@ bool _mongocrypt_kms_ctx_init_kmip_register(mongocrypt_kms_ctx_t *kms_ctx, const _mongocrypt_endpoint_t *endpoint, const uint8_t *secretdata, uint32_t secretdata_len, + const char *kmsid, _mongocrypt_log_t *log) { BSON_ASSERT_PARAM(kms_ctx); BSON_ASSERT_PARAM(endpoint); @@ -1445,7 +1803,7 @@ bool _mongocrypt_kms_ctx_init_kmip_register(mongocrypt_kms_ctx_t *kms_ctx, const uint8_t *reqdata; size_t reqlen; - _init_common(kms_ctx, log, MONGOCRYPT_KMS_KMIP_REGISTER); + _init_common(kms_ctx, log, MONGOCRYPT_KMS_KMIP_REGISTER, kmsid); status = kms_ctx->status; kms_ctx->endpoint = bson_strdup(endpoint->host_and_port); @@ -1471,6 +1829,7 @@ done: bool _mongocrypt_kms_ctx_init_kmip_activate(mongocrypt_kms_ctx_t *kms_ctx, const _mongocrypt_endpoint_t *endpoint, const char *unique_identifier, + const char *kmsid, _mongocrypt_log_t *log) { BSON_ASSERT_PARAM(kms_ctx); BSON_ASSERT_PARAM(endpoint); @@ -1481,7 +1840,7 @@ bool _mongocrypt_kms_ctx_init_kmip_activate(mongocrypt_kms_ctx_t *kms_ctx, size_t reqlen; const uint8_t *reqdata; - _init_common(kms_ctx, log, MONGOCRYPT_KMS_KMIP_ACTIVATE); + _init_common(kms_ctx, log, MONGOCRYPT_KMS_KMIP_ACTIVATE, kmsid); status = kms_ctx->status; kms_ctx->endpoint = bson_strdup(endpoint->host_and_port); @@ -1507,6 +1866,7 @@ done: bool _mongocrypt_kms_ctx_init_kmip_get(mongocrypt_kms_ctx_t *kms_ctx, const _mongocrypt_endpoint_t *endpoint, const char *unique_identifier, + const char *kmsid, _mongocrypt_log_t *log) { BSON_ASSERT_PARAM(kms_ctx); BSON_ASSERT_PARAM(endpoint); @@ -1517,7 +1877,7 @@ bool _mongocrypt_kms_ctx_init_kmip_get(mongocrypt_kms_ctx_t *kms_ctx, size_t reqlen; const uint8_t *reqdata; - _init_common(kms_ctx, log, MONGOCRYPT_KMS_KMIP_GET); + _init_common(kms_ctx, log, MONGOCRYPT_KMS_KMIP_GET, kmsid); status = kms_ctx->status; kms_ctx->endpoint = bson_strdup(endpoint->host_and_port); @@ -1540,6 +1900,129 @@ done: return ret; } +bool _mongocrypt_kms_ctx_init_kmip_create(mongocrypt_kms_ctx_t *kms_ctx, + const _mongocrypt_endpoint_t *endpoint, + const char *kmsid, + _mongocrypt_log_t *log) { + BSON_ASSERT_PARAM(kms_ctx); + BSON_ASSERT_PARAM(endpoint); + bool ret = false; + + _init_common(kms_ctx, log, MONGOCRYPT_KMS_KMIP_CREATE, kmsid); + mongocrypt_status_t *status = kms_ctx->status; + kms_ctx->endpoint = bson_strdup(endpoint->host_and_port); + _mongocrypt_apply_default_port(&kms_ctx->endpoint, DEFAULT_KMIP_PORT); + + kms_ctx->req = kms_kmip_request_create_new(NULL /* reserved */); + + if (kms_request_get_error(kms_ctx->req)) { + CLIENT_ERR("Error creating KMIP create request: %s", kms_request_get_error(kms_ctx->req)); + goto done; + } + + size_t reqlen; + const uint8_t *reqdata = kms_request_to_bytes(kms_ctx->req, &reqlen); + if (!_mongocrypt_buffer_copy_from_data_and_size(&kms_ctx->msg, reqdata, reqlen)) { + CLIENT_ERR("Error storing KMS request payload"); + goto done; + } + + ret = true; +done: + return ret; +} + +bool _mongocrypt_kms_ctx_init_kmip_encrypt(mongocrypt_kms_ctx_t *kms_ctx, + const _mongocrypt_endpoint_t *endpoint, + const char *unique_identifier, + const char *kmsid, + _mongocrypt_buffer_t *plaintext, + _mongocrypt_log_t *log) { + BSON_ASSERT_PARAM(kms_ctx); + BSON_ASSERT_PARAM(endpoint); + BSON_ASSERT_PARAM(plaintext); + bool ret = false; + + _init_common(kms_ctx, log, MONGOCRYPT_KMS_KMIP_ENCRYPT, kmsid); + mongocrypt_status_t *status = kms_ctx->status; + kms_ctx->endpoint = bson_strdup(endpoint->host_and_port); + _mongocrypt_apply_default_port(&kms_ctx->endpoint, DEFAULT_KMIP_PORT); + + kms_ctx->req = + kms_kmip_request_encrypt_new(NULL /* reserved */, unique_identifier, plaintext->data, plaintext->len); + + if (kms_request_get_error(kms_ctx->req)) { + CLIENT_ERR("Error creating KMIP encrypt request: %s", kms_request_get_error(kms_ctx->req)); + goto done; + } + + size_t reqlen; + const uint8_t *reqdata = kms_request_to_bytes(kms_ctx->req, &reqlen); + if (!_mongocrypt_buffer_copy_from_data_and_size(&kms_ctx->msg, reqdata, reqlen)) { + CLIENT_ERR("Error storing KMS request payload"); + goto done; + } + + ret = true; +done: + return ret; +} + +bool _mongocrypt_kms_ctx_init_kmip_decrypt(mongocrypt_kms_ctx_t *kms_ctx, + const _mongocrypt_endpoint_t *endpoint, + const char *kmsid, + _mongocrypt_key_doc_t *key, + _mongocrypt_log_t *log) { + BSON_ASSERT_PARAM(kms_ctx); + BSON_ASSERT_PARAM(endpoint); + BSON_ASSERT_PARAM(key); + bool ret = false; + + _init_common(kms_ctx, log, MONGOCRYPT_KMS_KMIP_DECRYPT, kmsid); + mongocrypt_status_t *status = kms_ctx->status; + kms_ctx->endpoint = bson_strdup(endpoint->host_and_port); + _mongocrypt_apply_default_port(&kms_ctx->endpoint, DEFAULT_KMIP_PORT); + + _mongocrypt_buffer_t iv; + if (!_mongocrypt_buffer_from_subrange(&iv, &key->key_material, 0, MONGOCRYPT_IV_LEN)) { + CLIENT_ERR("Error getting IV from key material"); + goto done; + } + _mongocrypt_buffer_t ciphertext; + if (!_mongocrypt_buffer_from_subrange(&ciphertext, + &key->key_material, + MONGOCRYPT_IV_LEN, + key->key_material.len - MONGOCRYPT_IV_LEN)) { + CLIENT_ERR("Error getting ciphertext from key material"); + goto done; + } + + BSON_ASSERT(key->kek.kms_provider == MONGOCRYPT_KMS_PROVIDER_KMIP); + BSON_ASSERT(key->kek.provider.kmip.delegated); + kms_ctx->req = kms_kmip_request_decrypt_new(NULL /* reserved */, + key->kek.provider.kmip.key_id, + ciphertext.data, + ciphertext.len, + iv.data, + iv.len); + + if (kms_request_get_error(kms_ctx->req)) { + CLIENT_ERR("Error creating KMIP decrypt request: %s", kms_request_get_error(kms_ctx->req)); + goto done; + } + + size_t reqlen; + const uint8_t *reqdata = kms_request_to_bytes(kms_ctx->req, &reqlen); + if (!_mongocrypt_buffer_copy_from_data_and_size(&kms_ctx->msg, reqdata, reqlen)) { + CLIENT_ERR("Error storing KMS request payload"); + goto done; + } + + ret = true; +done: + return ret; +} + static const char *set_and_ret(const char *what, uint32_t *len) { BSON_ASSERT_PARAM(what); @@ -1553,18 +2036,5 @@ const char *mongocrypt_kms_ctx_get_kms_provider(mongocrypt_kms_ctx_t *kms, uint3 BSON_ASSERT_PARAM(kms); /* len is checked in set_and_ret () before it is used */ - switch (kms->req_type) { - default: BSON_ASSERT(false && "unknown KMS request type"); - case MONGOCRYPT_KMS_AWS_ENCRYPT: - case MONGOCRYPT_KMS_AWS_DECRYPT: return set_and_ret("aws", len); - case MONGOCRYPT_KMS_AZURE_OAUTH: - case MONGOCRYPT_KMS_AZURE_WRAPKEY: - case MONGOCRYPT_KMS_AZURE_UNWRAPKEY: return set_and_ret("azure", len); - case MONGOCRYPT_KMS_GCP_OAUTH: - case MONGOCRYPT_KMS_GCP_ENCRYPT: - case MONGOCRYPT_KMS_GCP_DECRYPT: return set_and_ret("gcp", len); - case MONGOCRYPT_KMS_KMIP_REGISTER: - case MONGOCRYPT_KMS_KMIP_ACTIVATE: - case MONGOCRYPT_KMS_KMIP_GET: return set_and_ret("kmip", len); - } + return set_and_ret(kms->kmsid, len); } diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-log.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-log.c index 3aee7725a1e..072104aba73 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-log.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-log.c @@ -37,7 +37,6 @@ void _mongocrypt_log_cleanup(_mongocrypt_log_t *log) { } _mongocrypt_mutex_cleanup(&log->mutex); - memset(log, 0, sizeof(*log)); } void _mongocrypt_stdout_log_fn(mongocrypt_log_level_t level, const char *message, uint32_t message_len, void *ctx) { diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-marking-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-marking-private.h index 16dbc1d2e0f..f759c15a797 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-marking-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-marking-private.h @@ -45,6 +45,10 @@ typedef struct { }; } _mongocrypt_marking_t; +// `_mongocrypt_marking_t` inherits extended alignment from libbson. To dynamically allocate, use aligned allocation +// (e.g. BSON_ALIGNED_ALLOC) +BSON_STATIC_ASSERT2(alignof__mongocrypt_marking_t, BSON_ALIGNOF(_mongocrypt_marking_t) >= BSON_ALIGNOF(bson_iter_t)); + void _mongocrypt_marking_init(_mongocrypt_marking_t *marking); void _mongocrypt_marking_cleanup(_mongocrypt_marking_t *marking); @@ -63,6 +67,7 @@ bool _mongocrypt_marking_to_ciphertext(void *ctx, mc_mincover_t *mc_get_mincover_from_FLE2RangeFindSpec(mc_FLE2RangeFindSpec_t *findSpec, size_t sparsity, - mongocrypt_status_t *status) MONGOCRYPT_WARN_UNUSED_RESULT; + mongocrypt_status_t *status, + bool use_range_v2) MONGOCRYPT_WARN_UNUSED_RESULT; #endif /* MONGOCRYPT_MARKING_PRIVATE_H */ diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-marking.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-marking.c index 443356abc9e..d974b106dbe 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-marking.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-marking.c @@ -24,6 +24,7 @@ #include "mc-fle2-insert-update-payload-private.h" #include "mc-fle2-payload-uev-private.h" #include "mc-fle2-payload-uev-v2-private.h" +#include "mc-optional-private.h" #include "mc-range-edge-generation-private.h" #include "mc-range-encoding-private.h" #include "mc-range-mincover-private.h" @@ -33,6 +34,7 @@ #include "mongocrypt-crypto-private.h" #include "mongocrypt-key-broker-private.h" #include "mongocrypt-marking-private.h" +#include "mongocrypt-private.h" #include "mongocrypt-util-private.h" // mc_bson_type_to_string #include "mongocrypt.h" @@ -40,7 +42,7 @@ static bool _mongocrypt_marking_parse_fle1_placeholder(const bson_t *in, _mongocrypt_marking_t *out, mongocrypt_status_t *status) { - bson_iter_t iter; + bson_iter_t iter = {0}; bool has_ki = false, has_ka = false, has_a = false, has_v = false; BSON_ASSERT_PARAM(in); @@ -199,22 +201,24 @@ void _mongocrypt_marking_cleanup(_mongocrypt_marking_t *marking) { * Calculates: * E?CToken = HMAC(collectionLevel1Token, n) * E?CDerivedFromDataToken = HMAC(E?CToken, value) - * E?CDerivedFromDataTokenAndCounter = HMAC(E?CDerivedFromDataToken, c) + * E?CDerivedFromDataTokenAndContentionFactor = HMAC(E?CDerivedFromDataToken, cf) * * E?C = EDC|ESC|ECC * n = 1 for EDC, 2 for ESC, 3 for ECC - * c = maxContentionCounter + * cf = contentionFactor * - * E?CDerivedFromDataTokenAndCounter is saved to out, - * which is initialized even on failure. + * If {useContentionFactor} is False, E?CDerivedFromDataToken is saved to out, and {contentionFactor} is ignored. + * Otherwise, E?CDerivedFromDataTokenAndContentionFactor is saved to out using {contentionFactor}. + * + * Note that {out} is initialized even on failure. */ #define DERIVE_TOKEN_IMPL(Name) \ static bool _fle2_derive_##Name##_token(_mongocrypt_crypto_t *crypto, \ _mongocrypt_buffer_t *out, \ const mc_CollectionsLevel1Token_t *level1Token, \ const _mongocrypt_buffer_t *value, \ - bool useCounter, \ - int64_t counter, \ + bool useContentionFactor, \ + int64_t contentionFactor, \ mongocrypt_status_t *status) { \ BSON_ASSERT_PARAM(crypto); \ BSON_ASSERT_PARAM(out); \ @@ -235,24 +239,29 @@ void _mongocrypt_marking_cleanup(_mongocrypt_marking_t *marking) { return false; \ } \ \ - if (!useCounter) { \ + if (!useContentionFactor) { \ /* FindEqualityPayload uses *fromDataToken */ \ _mongocrypt_buffer_copy_to(mc_##Name##DerivedFromDataToken_get(fromDataToken), out); \ mc_##Name##DerivedFromDataToken_destroy(fromDataToken); \ return true; \ } \ \ - BSON_ASSERT(counter >= 0); \ - /* InsertUpdatePayload continues through *fromDataTokenAndCounter */ \ - mc_##Name##DerivedFromDataTokenAndCounter_t *fromTokenAndCounter = \ - mc_##Name##DerivedFromDataTokenAndCounter_new(crypto, fromDataToken, (uint64_t)counter, status); \ + BSON_ASSERT(contentionFactor >= 0); \ + /* InsertUpdatePayload continues through *fromDataTokenAndContentionFactor */ \ + mc_##Name##DerivedFromDataTokenAndContentionFactor_t *fromTokenAndContentionFactor = \ + mc_##Name##DerivedFromDataTokenAndContentionFactor_new(crypto, \ + fromDataToken, \ + (uint64_t)contentionFactor, \ + status); \ mc_##Name##DerivedFromDataToken_destroy(fromDataToken); \ - if (!fromTokenAndCounter) { \ + if (!fromTokenAndContentionFactor) { \ return false; \ } \ \ - _mongocrypt_buffer_copy_to(mc_##Name##DerivedFromDataTokenAndCounter_get(fromTokenAndCounter), out); \ - mc_##Name##DerivedFromDataTokenAndCounter_destroy(fromTokenAndCounter); \ + _mongocrypt_buffer_copy_to( \ + mc_##Name##DerivedFromDataTokenAndContentionFactor_get(fromTokenAndContentionFactor), \ + out); \ + mc_##Name##DerivedFromDataTokenAndContentionFactor_destroy(fromTokenAndContentionFactor); \ \ return true; \ } @@ -366,32 +375,61 @@ static bool _fle2_placeholder_aes_aead_encrypt(_mongocrypt_key_broker_t *kb, return true; } -// p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndCounter || -// ECCDerivedFromDataTokenAndCounter) +// FLE V1: p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndContentionFactor || +// ECCDerivedFromDataTokenAndContentionFactor) +// FLE V2: p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndContentionFactor) +// Range V2: p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndContentionFactor || isLeaf) static bool _fle2_derive_encrypted_token(_mongocrypt_crypto_t *crypto, _mongocrypt_buffer_t *out, + bool use_range_v2, const mc_CollectionsLevel1Token_t *collectionsLevel1Token, const _mongocrypt_buffer_t *escDerivedToken, const _mongocrypt_buffer_t *eccDerivedToken, + mc_optional_bool_t is_leaf, mongocrypt_status_t *status) { mc_ECOCToken_t *ecocToken = mc_ECOCToken_new(crypto, collectionsLevel1Token, status); if (!ecocToken) { return false; } + bool ok = false; _mongocrypt_buffer_t tmp; _mongocrypt_buffer_init(&tmp); const _mongocrypt_buffer_t *p = &tmp; if (!eccDerivedToken) { // FLE2v2 - p = escDerivedToken; + if (use_range_v2 && is_leaf.set) { + // Range V2; concat isLeaf + _mongocrypt_buffer_t isLeafBuf; + if (!_mongocrypt_buffer_copy_from_data_and_size(&isLeafBuf, (uint8_t[]){is_leaf.value}, 1)) { + CLIENT_ERR("failed to create is_leaf buffer"); + goto fail; + } + if (!_mongocrypt_buffer_concat(&tmp, (_mongocrypt_buffer_t[]){*escDerivedToken, isLeafBuf}, 2)) { + CLIENT_ERR("failed to allocate buffer"); + _mongocrypt_buffer_cleanup(&isLeafBuf); + goto fail; + } + _mongocrypt_buffer_cleanup(&isLeafBuf); + } else { + p = escDerivedToken; + } + } else { // FLE2v1 const _mongocrypt_buffer_t tokens[] = {*escDerivedToken, *eccDerivedToken}; - _mongocrypt_buffer_concat(&tmp, tokens, 2); + if (!_mongocrypt_buffer_concat(&tmp, tokens, 2)) { + CLIENT_ERR("failed to allocate buffer"); + goto fail; + } } - const bool ok = _fle2_placeholder_aes_ctr_encrypt(crypto, mc_ECOCToken_get(ecocToken), p, out, status); + if (!_fle2_placeholder_aes_ctr_encrypt(crypto, mc_ECOCToken_get(ecocToken), p, out, status)) { + goto fail; + } + + ok = true; +fail: _mongocrypt_buffer_cleanup(&tmp); mc_ECOCToken_destroy(ecocToken); return ok; @@ -422,7 +460,8 @@ static void _FLE2EncryptedPayloadCommon_cleanup(_FLE2EncryptedPayloadCommon_t *c _mongocrypt_buffer_cleanup(&common->escDerivedToken); _mongocrypt_buffer_cleanup(&common->eccDerivedToken); _mongocrypt_buffer_cleanup(&common->serverDerivedFromDataToken); - memset(common, 0, sizeof(*common)); + // Zero out memory so `_FLE2EncryptedPayloadCommon_cleanup` is safe to call twice. + *common = (_FLE2EncryptedPayloadCommon_t){{0}}; } // _get_tokenKey returns the tokenKey identified by indexKeyId. @@ -466,8 +505,8 @@ static bool _mongocrypt_fle2_placeholder_common(_mongocrypt_key_broker_t *kb, _FLE2EncryptedPayloadCommon_t *ret, const _mongocrypt_buffer_t *indexKeyId, const _mongocrypt_buffer_t *value, - bool useCounter, - int64_t maxContentionCounter, + bool useContentionFactor, + int64_t contentionFactor, mongocrypt_status_t *status) { BSON_ASSERT_PARAM(kb); BSON_ASSERT_PARAM(ret); @@ -498,8 +537,8 @@ static bool _mongocrypt_fle2_placeholder_common(_mongocrypt_key_broker_t *kb, &ret->edcDerivedToken, ret->collectionsLevel1Token, value, - useCounter, - maxContentionCounter, + useContentionFactor, + contentionFactor, status)) { goto fail; } @@ -508,8 +547,8 @@ static bool _mongocrypt_fle2_placeholder_common(_mongocrypt_key_broker_t *kb, &ret->escDerivedToken, ret->collectionsLevel1Token, value, - useCounter, - maxContentionCounter, + useContentionFactor, + contentionFactor, status)) { goto fail; } @@ -535,8 +574,8 @@ static bool _mongocrypt_fle2_placeholder_common(_mongocrypt_key_broker_t *kb, &ret->eccDerivedToken, ret->collectionsLevel1Token, value, - useCounter, - maxContentionCounter, + useContentionFactor, + contentionFactor, status)) { goto fail; } @@ -566,6 +605,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_common_v1(_mongocrypt_ BSON_ASSERT_PARAM(value_iter); BSON_ASSERT(kb->crypt); BSON_ASSERT(kb->crypt->opts.use_fle2_v2 == false); + BSON_ASSERT(kb->crypt->opts.use_range_v2 == false); BSON_ASSERT(placeholder->type == MONGOCRYPT_FLE2_PLACEHOLDER_TYPE_INSERT); _mongocrypt_crypto_t *crypto = kb->crypt->crypto; @@ -573,10 +613,10 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_common_v1(_mongocrypt_ bool res = false; *contentionFactor = 0; - if (placeholder->maxContentionCounter > 0) { + if (placeholder->maxContentionFactor > 0) { /* Choose a random contentionFactor in the inclusive range [0, - * placeholder->maxContentionCounter] */ - if (!_mongocrypt_random_int64(crypto, placeholder->maxContentionCounter + 1, contentionFactor, status)) { + * placeholder->maxContentionFactor] */ + if (!_mongocrypt_random_int64(crypto, placeholder->maxContentionFactor + 1, contentionFactor, status)) { goto fail; } } @@ -586,7 +626,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_common_v1(_mongocrypt_ common, &placeholder->index_key_id, &value, - true, /* derive tokens using counter */ + true, /* derive tokens using contentionFactor */ *contentionFactor, status)) { goto fail; @@ -599,13 +639,15 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_common_v1(_mongocrypt_ // c := ECCDerivedToken _mongocrypt_buffer_steal(&out->eccDerivedToken, &common->eccDerivedToken); - // p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndCounter || - // ECCDerivedFromDataTokenAndCounter) + // p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndContentionFactor || + // ECCDerivedFromDataTokenAndContentionFactor) if (!_fle2_derive_encrypted_token(crypto, &out->encryptedTokens, + false, // Can't use range V2 with FLE V1 common->collectionsLevel1Token, &out->escDerivedToken, &out->eccDerivedToken, + (mc_optional_bool_t){0}, // Unset is_leaf as it's not used in V1 status)) { goto fail; } @@ -719,10 +761,10 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_common(_mongocrypt_key bool res = false; out->contentionFactor = 0; // k - if (placeholder->maxContentionCounter > 0) { + if (placeholder->maxContentionFactor > 0) { /* Choose a random contentionFactor in the inclusive range [0, - * placeholder->maxContentionCounter] */ - if (!_mongocrypt_random_int64(crypto, placeholder->maxContentionCounter + 1, &out->contentionFactor, status)) { + * placeholder->maxContentionFactor] */ + if (!_mongocrypt_random_int64(crypto, placeholder->maxContentionFactor + 1, &out->contentionFactor, status)) { goto fail; } } @@ -732,7 +774,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_common(_mongocrypt_key common, &placeholder->index_key_id, &value, - true, /* derive tokens using counter */ + true, /* derive tokens using contentionFactor */ out->contentionFactor, status)) { goto fail; @@ -744,13 +786,18 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_common(_mongocrypt_key _mongocrypt_buffer_steal(&out->escDerivedToken, &common->escDerivedToken); BSON_ASSERT(common->eccDerivedToken.data == NULL); - // p := EncryptCBC(ECOCToken, ESCDerivedFromDataTokenAndCounter) - if (!_fle2_derive_encrypted_token(crypto, - &out->encryptedTokens, - common->collectionsLevel1Token, - &out->escDerivedToken, - NULL, // unused in v2 - status)) { + // p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndContentionFactor) + // Or in Range V2, when using range: p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndContentionFactor || 0x00) + if (!_fle2_derive_encrypted_token( + crypto, + &out->encryptedTokens, + kb->crypt->opts.use_range_v2, + common->collectionsLevel1Token, + &out->escDerivedToken, + NULL, // unused in v2 + // If this is a range insert, we append isLeaf to the encryptedTokens. Otherwise, we don't. + placeholder->algorithm == MONGOCRYPT_FLE2_ALGORITHM_RANGE ? OPT_BOOL(false) : (mc_optional_bool_t){0}, + status)) { goto fail; } @@ -850,7 +897,8 @@ fail: // get_edges creates and returns edges from an FLE2RangeInsertSpec. Returns NULL // on error. -static mc_edges_t *get_edges(mc_FLE2RangeInsertSpec_t *insertSpec, size_t sparsity, mongocrypt_status_t *status) { +static mc_edges_t * +get_edges(mc_FLE2RangeInsertSpec_t *insertSpec, size_t sparsity, mongocrypt_status_t *status, bool use_range_v2) { BSON_ASSERT_PARAM(insertSpec); bson_type_t value_type = bson_iter_type(&insertSpec->v); @@ -859,28 +907,36 @@ static mc_edges_t *get_edges(mc_FLE2RangeInsertSpec_t *insertSpec, size_t sparsi return mc_getEdgesInt32((mc_getEdgesInt32_args_t){.value = bson_iter_int32(&insertSpec->v), .min = OPT_I32(bson_iter_int32(&insertSpec->min)), .max = OPT_I32(bson_iter_int32(&insertSpec->max)), - .sparsity = sparsity}, - status); + .sparsity = sparsity, + .trimFactor = insertSpec->trimFactor}, + status, + use_range_v2); } else if (value_type == BSON_TYPE_INT64) { return mc_getEdgesInt64((mc_getEdgesInt64_args_t){.value = bson_iter_int64(&insertSpec->v), .min = OPT_I64(bson_iter_int64(&insertSpec->min)), .max = OPT_I64(bson_iter_int64(&insertSpec->max)), - .sparsity = sparsity}, - status); + .sparsity = sparsity, + .trimFactor = insertSpec->trimFactor}, + status, + use_range_v2); } else if (value_type == BSON_TYPE_DATE_TIME) { return mc_getEdgesInt64((mc_getEdgesInt64_args_t){.value = bson_iter_date_time(&insertSpec->v), .min = OPT_I64(bson_iter_date_time(&insertSpec->min)), .max = OPT_I64(bson_iter_date_time(&insertSpec->max)), - .sparsity = sparsity}, - status); + .sparsity = sparsity, + .trimFactor = insertSpec->trimFactor}, + status, + use_range_v2); } else if (value_type == BSON_TYPE_DOUBLE) { - mc_getEdgesDouble_args_t args = {.value = bson_iter_double(&insertSpec->v), .sparsity = sparsity}; + mc_getEdgesDouble_args_t args = {.value = bson_iter_double(&insertSpec->v), + .sparsity = sparsity, + .trimFactor = insertSpec->trimFactor}; if (insertSpec->precision.set) { // If precision is set, pass min/max/precision to mc_getEdgesDouble. // Do not pass min/max if precision is not set. All three must be set @@ -890,7 +946,7 @@ static mc_edges_t *get_edges(mc_FLE2RangeInsertSpec_t *insertSpec, size_t sparsi args.precision = insertSpec->precision; } - return mc_getEdgesDouble(args, status); + return mc_getEdgesDouble(args, status, use_range_v2); } else if (value_type == BSON_TYPE_DECIMAL128) { @@ -899,6 +955,7 @@ static mc_edges_t *get_edges(mc_FLE2RangeInsertSpec_t *insertSpec, size_t sparsi mc_getEdgesDecimal128_args_t args = { .value = value, .sparsity = sparsity, + .trimFactor = insertSpec->trimFactor, }; if (insertSpec->precision.set) { const mc_dec128 min = mc_dec128_from_bson_iter(&insertSpec->min); @@ -907,7 +964,7 @@ static mc_edges_t *get_edges(mc_FLE2RangeInsertSpec_t *insertSpec, size_t sparsi args.max = OPT_MC_DEC128(max); args.precision = insertSpec->precision; } - return mc_getEdgesDecimal128(args, status); + return mc_getEdgesDecimal128(args, status, use_range_v2); #else // ↑↑↑↑↑↑↑↑ With Decimal128 / Without ↓↓↓↓↓↓↓↓↓↓ CLIENT_ERR("unsupported BSON type (Decimal128) for range: libmongocrypt " "was built without extended Decimal128 support"); @@ -939,6 +996,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange_v1( BSON_ASSERT_PARAM(status); BSON_ASSERT(kb->crypt); BSON_ASSERT(kb->crypt->opts.use_fle2_v2 == false); + BSON_ASSERT(kb->crypt->opts.use_range_v2 == false); BSON_ASSERT(marking->type == MONGOCRYPT_MARKING_FLE2_ENCRYPTION); BSON_ASSERT(marking->fle2.algorithm == MONGOCRYPT_FLE2_ALGORITHM_RANGE); @@ -952,7 +1010,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange_v1( // Parse the value ("v"), min ("min"), and max ("max") from // FLE2EncryptionPlaceholder for range insert. mc_FLE2RangeInsertSpec_t insertSpec; - if (!mc_FLE2RangeInsertSpec_parse(&insertSpec, &placeholder->v_iter, status)) { + if (!mc_FLE2RangeInsertSpec_parse(&insertSpec, &placeholder->v_iter, kb->crypt->opts.use_range_v2, status)) { goto fail; } @@ -970,7 +1028,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange_v1( // g:= array { BSON_ASSERT(placeholder->sparsity >= 0 && (uint64_t)placeholder->sparsity <= (uint64_t)SIZE_MAX); - edges = get_edges(&insertSpec, (size_t)placeholder->sparsity, status); + edges = get_edges(&insertSpec, (size_t)placeholder->sparsity, status, kb->crypt->opts.use_range_v2); if (!edges) { goto fail; } @@ -993,7 +1051,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange_v1( &edge_tokens, &placeholder->index_key_id, &edge_buf, - true, /* derive tokens using counter */ + true, /* derive tokens using contentionFactor */ contentionFactor, status)) { goto fail_loop; @@ -1006,13 +1064,15 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange_v1( // c := ECCDerivedToken _mongocrypt_buffer_steal(&etc.eccDerivedToken, &edge_tokens.eccDerivedToken); - // p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndCounter || - // ECCDerivedFromDataTokenAndCounter) + // p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndContentionFactor || + // ECCDerivedFromDataTokenAndContentionFactor) if (!_fle2_derive_encrypted_token(kb->crypt->crypto, &etc.encryptedTokens, + false, // Range V2 is incompatible with FLE V1 edge_tokens.collectionsLevel1Token, &etc.escDerivedToken, &etc.eccDerivedToken, + (mc_optional_bool_t){0}, // Dummy value for isLeaf, unused in FLE V1 status)) { goto fail_loop; } @@ -1071,6 +1131,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange(_mo BSON_ASSERT_PARAM(ciphertext); BSON_ASSERT(kb->crypt); BSON_ASSERT(marking->type == MONGOCRYPT_MARKING_FLE2_ENCRYPTION); + const bool use_range_v2 = kb->crypt->opts.use_range_v2; if (!kb->crypt->opts.use_fle2_v2) { return _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange_v1(kb, marking, ciphertext, status); @@ -1086,7 +1147,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange(_mo // Parse the value ("v"), min ("min"), and max ("max") from // FLE2EncryptionPlaceholder for range insert. mc_FLE2RangeInsertSpec_t insertSpec; - if (!mc_FLE2RangeInsertSpec_parse(&insertSpec, &placeholder->v_iter, status)) { + if (!mc_FLE2RangeInsertSpec_parse(&insertSpec, &placeholder->v_iter, use_range_v2, status)) { goto fail; } @@ -1102,7 +1163,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange(_mo // g:= array { BSON_ASSERT(placeholder->sparsity >= 0 && (uint64_t)placeholder->sparsity <= (uint64_t)SIZE_MAX); - edges = get_edges(&insertSpec, (size_t)placeholder->sparsity, status); + edges = get_edges(&insertSpec, (size_t)placeholder->sparsity, status, kb->crypt->opts.use_range_v2); if (!edges) { goto fail; } @@ -1111,6 +1172,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange(_mo // Create an EdgeTokenSet from each edge. bool loop_ok = false; const char *edge = mc_edges_get(edges, i); + bool is_leaf = mc_edges_is_leaf(edges, edge); _mongocrypt_buffer_t edge_buf = {0}; _FLE2EncryptedPayloadCommon_t edge_tokens = {{0}}; _mongocrypt_buffer_t encryptedTokens = {0}; @@ -1125,7 +1187,7 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange(_mo &edge_tokens, &placeholder->index_key_id, &edge_buf, - true, /* derive tokens using counter */ + true, /* derive tokens using contentionFactor */ payload.contentionFactor, status)) { goto fail_loop; @@ -1140,12 +1202,15 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange(_mo // l := serverDerivedFromDataToken _mongocrypt_buffer_steal(&etc.serverDerivedFromDataToken, &edge_tokens.serverDerivedFromDataToken); - // p := EncryptCBC(ECOCToken, ESCDerivedFromDataTokenAndCounter) + // p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndContentionFactor) + // Or in Range V2: p := EncryptCTR(ECOCToken, ESCDerivedFromDataTokenAndContentionFactor || isLeaf) if (!_fle2_derive_encrypted_token(kb->crypt->crypto, &etc.encryptedTokens, + kb->crypt->opts.use_range_v2, edge_tokens.collectionsLevel1Token, &etc.escDerivedToken, NULL, // ecc unsed in FLE2v2 + OPT_BOOL(is_leaf), status)) { goto fail_loop; } @@ -1163,10 +1228,17 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForRange(_mo } } + // Include "range" payload fields introduced in SERVER-91889. + payload.sparsity = OPT_I64(placeholder->sparsity); + payload.precision = insertSpec.precision; + payload.trimFactor = OPT_I32(mc_edges_get_used_trimFactor(edges)); + bson_value_copy(bson_iter_value(&insertSpec.min), &payload.indexMin); + bson_value_copy(bson_iter_value(&insertSpec.max), &payload.indexMax); + { bson_t out; bson_init(&out); - mc_FLE2InsertUpdatePayloadV2_serializeForRange(&payload, &out); + mc_FLE2InsertUpdatePayloadV2_serializeForRange(&payload, &out, use_range_v2); _mongocrypt_buffer_steal_from_bson(&ciphertext->data, &out); } // Do not set ciphertext->original_bson_type and ciphertext->key_id. They are @@ -1185,7 +1257,7 @@ fail: /** * Payload subtype 5: FLE2FindEqualityPayload * - * {d: EDC, s: ESC, c: ECC, e: serverToken, cm: contentionCounter} + * {d: EDC, s: ESC, c: ECC, e: serverToken, cm: maxContentionFactor} */ static bool _mongocrypt_fle2_placeholder_to_find_ciphertext_v1(_mongocrypt_key_broker_t *kb, _mongocrypt_marking_t *marking, @@ -1213,8 +1285,8 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertext_v1(_mongocrypt_key_b &common, &placeholder->index_key_id, &value, - false, /* derive tokens without counter */ - placeholder->maxContentionCounter, + false, /* derive tokens without contentionFactor */ + placeholder->maxContentionFactor, /* ignored */ status)) { goto fail; } @@ -1230,7 +1302,7 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertext_v1(_mongocrypt_key_b _mongocrypt_buffer_copy_to(mc_ServerDataEncryptionLevel1Token_get(common.serverDataEncryptionLevel1Token), &payload.serverEncryptionToken); - payload.maxContentionCounter = placeholder->maxContentionCounter; + payload.maxContentionFactor = placeholder->maxContentionFactor; { bson_t out; @@ -1255,7 +1327,7 @@ fail: * Payload subtype 12: FLE2FindEqualityPayloadV2 * Delegates to ..._find_ciphertext_v1 when crypt->opts.use_fle2_v2 == false. * - * {d: EDC, s: ESC, l: serverDerivedFromDataToken, cm: contentionCounter} + * {d: EDC, s: ESC, l: serverDerivedFromDataToken, cm: maxContentionFactor} */ static bool _mongocrypt_fle2_placeholder_to_find_ciphertext(_mongocrypt_key_broker_t *kb, _mongocrypt_marking_t *marking, @@ -1287,8 +1359,8 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertext(_mongocrypt_key_brok &common, &placeholder->index_key_id, &value, - false, /* derive tokens without counter */ - placeholder->maxContentionCounter, + false, /* derive tokens without contentionFactor */ + placeholder->maxContentionFactor, /* ignored */ status)) { goto fail; } @@ -1301,8 +1373,8 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertext(_mongocrypt_key_brok // l := serverDerivedFromDataToken _mongocrypt_buffer_steal(&payload.serverDerivedFromDataToken, &common.serverDerivedFromDataToken); - // cm := maxContentionCounter - payload.maxContentionCounter = placeholder->maxContentionCounter; + // cm := maxContentionFactor + payload.maxContentionFactor = placeholder->maxContentionFactor; { bson_t out; @@ -1329,8 +1401,10 @@ static bool isInfinite(bson_iter_t *iter) { // mc_get_mincover_from_FLE2RangeFindSpec creates and returns a mincover from an // FLE2RangeFindSpec. Returns NULL on error. -mc_mincover_t * -mc_get_mincover_from_FLE2RangeFindSpec(mc_FLE2RangeFindSpec_t *findSpec, size_t sparsity, mongocrypt_status_t *status) { +mc_mincover_t *mc_get_mincover_from_FLE2RangeFindSpec(mc_FLE2RangeFindSpec_t *findSpec, + size_t sparsity, + mongocrypt_status_t *status, + bool use_range_v2) { BSON_ASSERT_PARAM(findSpec); BSON_ASSERT(findSpec->edgesInfo.set); @@ -1381,6 +1455,7 @@ mc_get_mincover_from_FLE2RangeFindSpec(mc_FLE2RangeFindSpec_t *findSpec, size_t BSON_ASSERT(bson_iter_type(&upperBound) == BSON_TYPE_INT32); BSON_ASSERT(bson_iter_type(&findSpec->edgesInfo.value.indexMin) == BSON_TYPE_INT32); BSON_ASSERT(bson_iter_type(&findSpec->edgesInfo.value.indexMax) == BSON_TYPE_INT32); + return mc_getMincoverInt32( (mc_getMincoverInt32_args_t){.lowerBound = bson_iter_int32(&lowerBound), .includeLowerBound = includeLowerBound, @@ -1388,8 +1463,10 @@ mc_get_mincover_from_FLE2RangeFindSpec(mc_FLE2RangeFindSpec_t *findSpec, size_t .includeUpperBound = includeUpperBound, .min = OPT_I32(bson_iter_int32(&findSpec->edgesInfo.value.indexMin)), .max = OPT_I32(bson_iter_int32(&findSpec->edgesInfo.value.indexMax)), - .sparsity = sparsity}, - status); + .sparsity = sparsity, + .trimFactor = findSpec->edgesInfo.value.trimFactor}, + status, + use_range_v2); case BSON_TYPE_INT64: BSON_ASSERT(bson_iter_type(&lowerBound) == BSON_TYPE_INT64); @@ -1403,8 +1480,10 @@ mc_get_mincover_from_FLE2RangeFindSpec(mc_FLE2RangeFindSpec_t *findSpec, size_t .includeUpperBound = includeUpperBound, .min = OPT_I64(bson_iter_int64(&findSpec->edgesInfo.value.indexMin)), .max = OPT_I64(bson_iter_int64(&findSpec->edgesInfo.value.indexMax)), - .sparsity = sparsity}, - status); + .sparsity = sparsity, + .trimFactor = findSpec->edgesInfo.value.trimFactor}, + status, + use_range_v2); case BSON_TYPE_DATE_TIME: BSON_ASSERT(bson_iter_type(&lowerBound) == BSON_TYPE_DATE_TIME); BSON_ASSERT(bson_iter_type(&upperBound) == BSON_TYPE_DATE_TIME); @@ -1417,8 +1496,10 @@ mc_get_mincover_from_FLE2RangeFindSpec(mc_FLE2RangeFindSpec_t *findSpec, size_t .includeUpperBound = includeUpperBound, .min = OPT_I64(bson_iter_date_time(&findSpec->edgesInfo.value.indexMin)), .max = OPT_I64(bson_iter_date_time(&findSpec->edgesInfo.value.indexMax)), - .sparsity = sparsity}, - status); + .sparsity = sparsity, + .trimFactor = findSpec->edgesInfo.value.trimFactor}, + status, + use_range_v2); case BSON_TYPE_DOUBLE: { BSON_ASSERT(bson_iter_type(&lowerBound) == BSON_TYPE_DOUBLE); BSON_ASSERT(bson_iter_type(&upperBound) == BSON_TYPE_DOUBLE); @@ -1429,7 +1510,8 @@ mc_get_mincover_from_FLE2RangeFindSpec(mc_FLE2RangeFindSpec_t *findSpec, size_t .includeLowerBound = includeLowerBound, .upperBound = bson_iter_double(&upperBound), .includeUpperBound = includeUpperBound, - .sparsity = sparsity}; + .sparsity = sparsity, + .trimFactor = findSpec->edgesInfo.value.trimFactor}; if (findSpec->edgesInfo.value.precision.set) { // If precision is set, pass min/max/precision to mc_getMincoverDouble. // Do not pass min/max if precision is not set. All three must be set @@ -1438,7 +1520,7 @@ mc_get_mincover_from_FLE2RangeFindSpec(mc_FLE2RangeFindSpec_t *findSpec, size_t args.max = OPT_DOUBLE(bson_iter_double(&findSpec->edgesInfo.value.indexMax)); args.precision = findSpec->edgesInfo.value.precision; } - return mc_getMincoverDouble(args, status); + return mc_getMincoverDouble(args, status, use_range_v2); } case BSON_TYPE_DECIMAL128: { #if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT @@ -1447,19 +1529,18 @@ mc_get_mincover_from_FLE2RangeFindSpec(mc_FLE2RangeFindSpec_t *findSpec, size_t BSON_ASSERT(bson_iter_type(&findSpec->edgesInfo.value.indexMin) == BSON_TYPE_DECIMAL128); BSON_ASSERT(bson_iter_type(&findSpec->edgesInfo.value.indexMax) == BSON_TYPE_DECIMAL128); - mc_getMincoverDecimal128_args_t args = { - .lowerBound = mc_dec128_from_bson_iter(&lowerBound), - .includeLowerBound = includeLowerBound, - .upperBound = mc_dec128_from_bson_iter(&upperBound), - .includeUpperBound = includeUpperBound, - .sparsity = sparsity, - }; + mc_getMincoverDecimal128_args_t args = {.lowerBound = mc_dec128_from_bson_iter(&lowerBound), + .includeLowerBound = includeLowerBound, + .upperBound = mc_dec128_from_bson_iter(&upperBound), + .includeUpperBound = includeUpperBound, + .sparsity = sparsity, + .trimFactor = findSpec->edgesInfo.value.trimFactor}; if (findSpec->edgesInfo.value.precision.set) { args.min = OPT_MC_DEC128(mc_dec128_from_bson_iter(&findSpec->edgesInfo.value.indexMin)); args.max = OPT_MC_DEC128(mc_dec128_from_bson_iter(&findSpec->edgesInfo.value.indexMax)); args.precision = findSpec->edgesInfo.value.precision; } - return mc_getMincoverDecimal128(args, status); + return mc_getMincoverDecimal128(args, status, use_range_v2); #else // ↑↑↑↑↑↑↑↑ With Decimal128 / Without ↓↓↓↓↓↓↓↓↓↓ CLIENT_ERR("FLE2 find is not supported for Decimal128: libmongocrypt " "was built without Decimal128 support"); @@ -1491,7 +1572,7 @@ mc_get_mincover_from_FLE2RangeFindSpec(mc_FLE2RangeFindSpec_t *findSpec, size_t /** * Payload subtype 10: FLE2FindRangePayload * - * {e: serverToken, cm: contentionCounter, + * {e: serverToken, cm: maxContentionFactor, * g: [{d: EDC, s: ESC, c: ECC}, ...]} */ static bool _mongocrypt_fle2_placeholder_to_find_ciphertextForRange_v1(_mongocrypt_key_broker_t *kb, @@ -1503,6 +1584,7 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertextForRange_v1(_mongocry BSON_ASSERT_PARAM(ciphertext); BSON_ASSERT(kb->crypt); + const bool use_range_v2 = kb->crypt->opts.use_range_v2; _mongocrypt_crypto_t *crypto = kb->crypt->crypto; mc_FLE2EncryptionPlaceholder_t *placeholder = &marking->fle2; mc_FLE2FindRangePayload_t payload; @@ -1520,13 +1602,13 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertextForRange_v1(_mongocry // Parse the query bounds and index bounds from FLE2EncryptionPlaceholder for // range find. mc_FLE2RangeFindSpec_t findSpec; - if (!mc_FLE2RangeFindSpec_parse(&findSpec, &placeholder->v_iter, status)) { + if (!mc_FLE2RangeFindSpec_parse(&findSpec, &placeholder->v_iter, use_range_v2, status)) { goto fail; } if (findSpec.edgesInfo.set) { - // cm := Queryable Encryption max counter - payload.payload.value.maxContentionCounter = placeholder->maxContentionCounter; + // cm := Queryable Encryption max contentionFactor + payload.payload.value.maxContentionFactor = placeholder->maxContentionFactor; // e := ServerDataEncryptionLevel1Token { @@ -1547,7 +1629,8 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertextForRange_v1(_mongocry // g:= array { BSON_ASSERT(placeholder->sparsity >= 0 && (uint64_t)placeholder->sparsity <= (uint64_t)SIZE_MAX); - mincover = mc_get_mincover_from_FLE2RangeFindSpec(&findSpec, (size_t)placeholder->sparsity, status); + mincover = + mc_get_mincover_from_FLE2RangeFindSpec(&findSpec, (size_t)placeholder->sparsity, status, use_range_v2); if (!mincover) { goto fail; } @@ -1569,8 +1652,8 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertextForRange_v1(_mongocry &edge_tokens, &placeholder->index_key_id, &edge_buf, - false, /* derive tokens using counter */ - placeholder->maxContentionCounter, + false, /* derive tokens using contentionFactor */ + placeholder->maxContentionFactor, /* ignored */ status)) { goto fail_loop; } @@ -1626,7 +1709,7 @@ fail: * Delegates to ..._find_ciphertextForRange_v1 * when crypt->opts.use_fle2_v2 is false * - * {cm: contentionCounter, + * {cm: maxContentionFactor, * g: [{d: EDC, s: ESC, l: serverDerivedFromDataToken}, ...]} */ static bool _mongocrypt_fle2_placeholder_to_find_ciphertextForRange(_mongocrypt_key_broker_t *kb, @@ -1641,6 +1724,7 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertextForRange(_mongocrypt_ return _mongocrypt_fle2_placeholder_to_find_ciphertextForRange_v1(kb, marking, ciphertext, status); } + const bool use_range_v2 = kb->crypt->opts.use_range_v2; mc_FLE2EncryptionPlaceholder_t *placeholder = &marking->fle2; mc_FLE2FindRangePayloadV2_t payload; bool res = false; @@ -1656,18 +1740,19 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertextForRange(_mongocrypt_ // Parse the query bounds and index bounds from FLE2EncryptionPlaceholder for // range find. mc_FLE2RangeFindSpec_t findSpec; - if (!mc_FLE2RangeFindSpec_parse(&findSpec, &placeholder->v_iter, status)) { + if (!mc_FLE2RangeFindSpec_parse(&findSpec, &placeholder->v_iter, use_range_v2, status)) { goto fail; } if (findSpec.edgesInfo.set) { - // cm := Queryable Encryption max counter - payload.payload.value.maxContentionCounter = placeholder->maxContentionCounter; + // cm := Queryable Encryption max contentionFactor + payload.payload.value.maxContentionFactor = placeholder->maxContentionFactor; // g:= array { BSON_ASSERT(placeholder->sparsity >= 0 && (uint64_t)placeholder->sparsity <= (uint64_t)SIZE_MAX); - mincover = mc_get_mincover_from_FLE2RangeFindSpec(&findSpec, (size_t)placeholder->sparsity, status); + mincover = + mc_get_mincover_from_FLE2RangeFindSpec(&findSpec, (size_t)placeholder->sparsity, status, use_range_v2); if (!mincover) { goto fail; } @@ -1689,8 +1774,8 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertextForRange(_mongocrypt_ &edge_tokens, &placeholder->index_key_id, &edge_buf, - false, /* derive tokens using counter */ - placeholder->maxContentionCounter, + false, /* derive tokens without using contentionFactor */ + placeholder->maxContentionFactor, /* ignored */ status)) { goto fail_loop; } @@ -1715,6 +1800,15 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertextForRange(_mongocrypt_ } } payload.payload.set = true; + + if (use_range_v2) { + // Include "range" payload fields introduced in SERVER-91889. + payload.sparsity = OPT_I64(placeholder->sparsity); + payload.precision = findSpec.edgesInfo.value.precision; + payload.trimFactor = OPT_I32(mc_mincover_get_used_trimFactor(mincover)); + bson_value_copy(bson_iter_value(&findSpec.edgesInfo.value.indexMin), &payload.indexMin); + bson_value_copy(bson_iter_value(&findSpec.edgesInfo.value.indexMax), &payload.indexMax); + } } payload.payloadId = findSpec.payloadId; @@ -1724,7 +1818,7 @@ static bool _mongocrypt_fle2_placeholder_to_find_ciphertextForRange(_mongocrypt_ // Serialize. { bson_t out = BSON_INITIALIZER; - mc_FLE2FindRangePayloadV2_serialize(&payload, &out); + mc_FLE2FindRangePayloadV2_serialize(&payload, &out, use_range_v2); _mongocrypt_buffer_steal_from_bson(&ciphertext->data, &out); } _mongocrypt_buffer_steal(&ciphertext->key_id, &placeholder->index_key_id); diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-opts-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-opts-private.h index 92061bd22c9..4c7b77d0e87 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-opts-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-opts-private.h @@ -26,6 +26,7 @@ #include "mongocrypt-kek-private.h" #include "mongocrypt-log-private.h" #include "mongocrypt.h" +#include typedef struct { char *tenant_id; @@ -56,16 +57,44 @@ typedef struct { _mongocrypt_endpoint_t *endpoint; } _mongocrypt_opts_kms_provider_kmip_t; +typedef struct { + // `type` identifies the set field in `value`. + _mongocrypt_kms_provider_t type; + + union { + _mongocrypt_opts_kms_provider_local_t local; + _mongocrypt_opts_kms_provider_aws_t aws; + _mongocrypt_opts_kms_provider_azure_t azure; + _mongocrypt_opts_kms_provider_gcp_t gcp; + _mongocrypt_opts_kms_provider_kmip_t kmip; + } value; +} mc_kms_creds_t; + typedef struct { int configured_providers; /* A bit set of _mongocrypt_kms_provider_t */ int need_credentials; /* A bit set of _mongocrypt_kms_provider_t */ - _mongocrypt_opts_kms_provider_local_t local; - _mongocrypt_opts_kms_provider_aws_t aws; - _mongocrypt_opts_kms_provider_azure_t azure; - _mongocrypt_opts_kms_provider_gcp_t gcp; - _mongocrypt_opts_kms_provider_kmip_t kmip; + // Fields suffixed with `_mut` are mutated when constructing the `_mongocrypt_opts_kms_providers_t`. + // Prefer using `_mongocrypt_opts_kms_providers_lookup` to read the values. + _mongocrypt_opts_kms_provider_local_t local_mut; + _mongocrypt_opts_kms_provider_aws_t aws_mut; + _mongocrypt_opts_kms_provider_azure_t azure_mut; + _mongocrypt_opts_kms_provider_gcp_t gcp_mut; + _mongocrypt_opts_kms_provider_kmip_t kmip_mut; + // `named_mut` stores a list of named KMS providers. + mc_array_t named_mut; } _mongocrypt_opts_kms_providers_t; +void _mongocrypt_opts_kms_providers_init(_mongocrypt_opts_kms_providers_t *kms_providers); + +bool _mongocrypt_parse_kms_providers(mongocrypt_binary_t *kms_providers_definition, + _mongocrypt_opts_kms_providers_t *kms_providers, + mongocrypt_status_t *status, + _mongocrypt_log_t *log); + +bool _mongocrypt_opts_kms_providers_lookup(const _mongocrypt_opts_kms_providers_t *kms_providers, + const char *kmsid, + mc_kms_creds_t *out); + typedef struct { mongocrypt_log_fn_t log_fn; void *log_ctx; @@ -86,11 +115,15 @@ typedef struct { mstr crypt_shared_lib_override_path; bool use_need_kms_credentials_state; + bool use_need_mongo_collinfo_with_db_state; bool bypass_query_analysis; // When creating new encrypted payloads, // use V2 variants of the FLE2 datatypes. bool use_fle2_v2; + + // Use the Queryable Encryption Range V2 protocol. + bool use_range_v2; } _mongocrypt_opts_t; void _mongocrypt_opts_kms_providers_cleanup(_mongocrypt_opts_kms_providers_t *kms_providers); @@ -120,6 +153,14 @@ bool _mongocrypt_opts_kms_providers_validate(_mongocrypt_opts_t *opts, */ bool _mongocrypt_parse_optional_utf8(const bson_t *bson, const char *dotkey, char **out, mongocrypt_status_t *status); +/* + * Parse an optional boolean value from BSON. + * @dotkey may be a dot separated key like: "a.b.c". + * @*out is set to a copy of the value if found, false otherwise. + * Returns true if no error occured. + */ +bool _mongocrypt_parse_optional_bool(const bson_t *bson, const char *dotkey, bool *out, mongocrypt_status_t *status); + /* * Parse a required UTF-8 value from BSON. * @dotkey may be a dot separated key like: "a.b.c". @@ -200,4 +241,8 @@ bool _mongocrypt_check_allowed_fields_va(const bson_t *bson, const char *dotkey, #define _mongocrypt_check_allowed_fields(bson, path, status, ...) \ _mongocrypt_check_allowed_fields_va(bson, path, status, __VA_ARGS__, NULL) +bool mc_kmsid_parse(const char *kmsid, + _mongocrypt_kms_provider_t *type_out, + const char **name_out, + mongocrypt_status_t *status); #endif /* MONGOCRYPT_OPTS_PRIVATE_H */ diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-opts.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-opts.c index a0288110780..7f823622476 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-opts.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-opts.c @@ -19,12 +19,24 @@ #include "mongocrypt-log-private.h" #include "mongocrypt-opts-private.h" #include "mongocrypt-private.h" +#include // mc_iter_document_as_bson #include +typedef struct { + mc_kms_creds_t creds; + char *kmsid; +} mc_kms_creds_with_id_t; + +void _mongocrypt_opts_kms_providers_init(_mongocrypt_opts_kms_providers_t *kms_providers) { + _mc_array_init(&kms_providers->named_mut, sizeof(mc_kms_creds_with_id_t)); +} + void _mongocrypt_opts_init(_mongocrypt_opts_t *opts) { BSON_ASSERT_PARAM(opts); memset(opts, 0, sizeof(*opts)); + opts->use_range_v2 = true; + _mongocrypt_opts_kms_providers_init(&opts->kms_providers); } static void _mongocrypt_opts_kms_provider_azure_cleanup(_mongocrypt_opts_kms_provider_azure_t *kms_provider_azure) { @@ -48,17 +60,58 @@ static void _mongocrypt_opts_kms_provider_gcp_cleanup(_mongocrypt_opts_kms_provi bson_free(kms_provider_gcp->access_token); } +static void _mongocrypt_opts_kms_provider_local_cleanup(_mongocrypt_opts_kms_provider_local_t *kms_provider_local) { + _mongocrypt_buffer_cleanup(&kms_provider_local->key); +} + +static void _mongocrypt_opts_kms_provider_aws_cleanup(_mongocrypt_opts_kms_provider_aws_t *kms_provider_aws) { + bson_free(kms_provider_aws->secret_access_key); + bson_free(kms_provider_aws->access_key_id); + bson_free(kms_provider_aws->session_token); +} + +static void _mongocrypt_opts_kms_provider_kmip_cleanup(_mongocrypt_opts_kms_provider_kmip_t *kms_provider_kmip) { + _mongocrypt_endpoint_destroy(kms_provider_kmip->endpoint); +} + void _mongocrypt_opts_kms_providers_cleanup(_mongocrypt_opts_kms_providers_t *kms_providers) { if (!kms_providers) { return; } - bson_free(kms_providers->aws.secret_access_key); - bson_free(kms_providers->aws.access_key_id); - bson_free(kms_providers->aws.session_token); - _mongocrypt_buffer_cleanup(&kms_providers->local.key); - _mongocrypt_opts_kms_provider_azure_cleanup(&kms_providers->azure); - _mongocrypt_opts_kms_provider_gcp_cleanup(&kms_providers->gcp); - _mongocrypt_endpoint_destroy(kms_providers->kmip.endpoint); + _mongocrypt_opts_kms_provider_aws_cleanup(&kms_providers->aws_mut); + _mongocrypt_opts_kms_provider_local_cleanup(&kms_providers->local_mut); + _mongocrypt_opts_kms_provider_azure_cleanup(&kms_providers->azure_mut); + _mongocrypt_opts_kms_provider_gcp_cleanup(&kms_providers->gcp_mut); + _mongocrypt_opts_kms_provider_kmip_cleanup(&kms_providers->kmip_mut); + for (size_t i = 0; i < kms_providers->named_mut.len; i++) { + mc_kms_creds_with_id_t kcwid = _mc_array_index(&kms_providers->named_mut, mc_kms_creds_with_id_t, i); + switch (kcwid.creds.type) { + default: + case MONGOCRYPT_KMS_PROVIDER_NONE: break; + case MONGOCRYPT_KMS_PROVIDER_AWS: { + _mongocrypt_opts_kms_provider_aws_cleanup(&kcwid.creds.value.aws); + break; + } + case MONGOCRYPT_KMS_PROVIDER_LOCAL: { + _mongocrypt_opts_kms_provider_local_cleanup(&kcwid.creds.value.local); + break; + } + case MONGOCRYPT_KMS_PROVIDER_AZURE: { + _mongocrypt_opts_kms_provider_azure_cleanup(&kcwid.creds.value.azure); + break; + } + case MONGOCRYPT_KMS_PROVIDER_GCP: { + _mongocrypt_opts_kms_provider_gcp_cleanup(&kcwid.creds.value.gcp); + break; + } + case MONGOCRYPT_KMS_PROVIDER_KMIP: { + _mongocrypt_endpoint_destroy(kcwid.creds.value.kmip.endpoint); + break; + } + } + bson_free(kcwid.kmsid); + } + _mc_array_destroy(&kms_providers->named_mut); } void _mongocrypt_opts_merge_kms_providers(_mongocrypt_opts_kms_providers_t *dest, @@ -67,23 +120,23 @@ void _mongocrypt_opts_merge_kms_providers(_mongocrypt_opts_kms_providers_t *dest BSON_ASSERT_PARAM(source); if (source->configured_providers & MONGOCRYPT_KMS_PROVIDER_AWS) { - memcpy(&dest->aws, &source->aws, sizeof(source->aws)); + memcpy(&dest->aws_mut, &source->aws_mut, sizeof(source->aws_mut)); dest->configured_providers |= MONGOCRYPT_KMS_PROVIDER_AWS; } if (source->configured_providers & MONGOCRYPT_KMS_PROVIDER_LOCAL) { - memcpy(&dest->local, &source->local, sizeof(source->local)); + memcpy(&dest->local_mut, &source->local_mut, sizeof(source->local_mut)); dest->configured_providers |= MONGOCRYPT_KMS_PROVIDER_LOCAL; } if (source->configured_providers & MONGOCRYPT_KMS_PROVIDER_AZURE) { - memcpy(&dest->azure, &source->azure, sizeof(source->azure)); + memcpy(&dest->azure_mut, &source->azure_mut, sizeof(source->azure_mut)); dest->configured_providers |= MONGOCRYPT_KMS_PROVIDER_AZURE; } if (source->configured_providers & MONGOCRYPT_KMS_PROVIDER_GCP) { - memcpy(&dest->gcp, &source->gcp, sizeof(source->gcp)); + memcpy(&dest->gcp_mut, &source->gcp_mut, sizeof(source->gcp_mut)); dest->configured_providers |= MONGOCRYPT_KMS_PROVIDER_GCP; } if (source->configured_providers & MONGOCRYPT_KMS_PROVIDER_KMIP) { - memcpy(&dest->kmip, &source->kmip, sizeof(source->kmip)); + memcpy(&dest->kmip_mut, &source->kmip_mut, sizeof(source->kmip_mut)); dest->configured_providers |= MONGOCRYPT_KMS_PROVIDER_KMIP; } /* ensure all providers were copied */ @@ -111,20 +164,20 @@ bool _mongocrypt_opts_kms_providers_validate(_mongocrypt_opts_t *opts, BSON_ASSERT_PARAM(opts); BSON_ASSERT_PARAM(kms_providers); - if (!kms_providers->configured_providers && !kms_providers->need_credentials) { + if (!kms_providers->configured_providers && !kms_providers->need_credentials && kms_providers->named_mut.len == 0) { CLIENT_ERR("no kms provider set"); return false; } if (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_AWS) { - if (!kms_providers->aws.access_key_id || !kms_providers->aws.secret_access_key) { + if (!kms_providers->aws_mut.access_key_id || !kms_providers->aws_mut.secret_access_key) { CLIENT_ERR("aws credentials unset"); return false; } } if (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_LOCAL) { - if (_mongocrypt_buffer_empty(&kms_providers->local.key)) { + if (_mongocrypt_buffer_empty(&kms_providers->local_mut.key)) { CLIENT_ERR("local data key unset"); return false; } @@ -223,6 +276,51 @@ bool _mongocrypt_opts_validate(_mongocrypt_opts_t *opts, mongocrypt_status_t *st return _mongocrypt_opts_kms_providers_validate(opts, &opts->kms_providers, status); } +bool _mongocrypt_opts_kms_providers_lookup(const _mongocrypt_opts_kms_providers_t *kms_providers, + const char *kmsid, + mc_kms_creds_t *out) { + *out = (mc_kms_creds_t){0}; + if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_AWS) && 0 == strcmp(kmsid, "aws")) { + out->type = MONGOCRYPT_KMS_PROVIDER_AWS; + out->value.aws = kms_providers->aws_mut; + return true; + } + if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_AZURE) && 0 == strcmp(kmsid, "azure")) { + out->type = MONGOCRYPT_KMS_PROVIDER_AZURE; + out->value.azure = kms_providers->azure_mut; + return true; + } + + if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_GCP) && 0 == strcmp(kmsid, "gcp")) { + out->type = MONGOCRYPT_KMS_PROVIDER_GCP; + out->value.gcp = kms_providers->gcp_mut; + return true; + } + + if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_LOCAL) && 0 == strcmp(kmsid, "local")) { + out->type = MONGOCRYPT_KMS_PROVIDER_LOCAL; + out->value.local = kms_providers->local_mut; + return true; + } + + if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_KMIP) && 0 == strcmp(kmsid, "kmip")) { + out->type = MONGOCRYPT_KMS_PROVIDER_KMIP; + out->value.kmip = kms_providers->kmip_mut; + return true; + } + + // Check for KMS providers with a name. + for (size_t i = 0; i < kms_providers->named_mut.len; i++) { + mc_kms_creds_with_id_t kcwi = _mc_array_index(&kms_providers->named_mut, mc_kms_creds_with_id_t, i); + if (0 == strcmp(kmsid, kcwi.kmsid)) { + *out = kcwi.creds; + return true; + } + } + + return false; +} + bool _mongocrypt_parse_optional_utf8(const bson_t *bson, const char *dotkey, char **out, mongocrypt_status_t *status) { bson_iter_t iter; bson_iter_t child; @@ -250,6 +348,33 @@ bool _mongocrypt_parse_optional_utf8(const bson_t *bson, const char *dotkey, cha return true; } +bool _mongocrypt_parse_optional_bool(const bson_t *bson, const char *dotkey, bool *out, mongocrypt_status_t *status) { + bson_iter_t iter; + bson_iter_t child; + + BSON_ASSERT_PARAM(bson); + BSON_ASSERT_PARAM(dotkey); + BSON_ASSERT_PARAM(out); + + *out = false; + + if (!bson_iter_init(&iter, bson)) { + CLIENT_ERR("invalid BSON"); + return false; + } + if (!bson_iter_find_descendant(&iter, dotkey, &child)) { + /* Not found. Not an error. */ + return true; + } + if (!BSON_ITER_HOLDS_BOOL(&child)) { + CLIENT_ERR("expected bool %s", dotkey); + return false; + } + + *out = bson_iter_bool(&child); + return true; +} + bool _mongocrypt_parse_required_utf8(const bson_t *bson, const char *dotkey, char **out, mongocrypt_status_t *status) { BSON_ASSERT_PARAM(bson); BSON_ASSERT_PARAM(dotkey); @@ -421,3 +546,454 @@ bool _mongocrypt_check_allowed_fields_va(const bson_t *bson, const char *dotkey, } return true; } + +#define KEY_HELP "Expected `` or `:`. Example: `local` or `local:name`." + +bool mc_kmsid_parse(const char *kmsid, + _mongocrypt_kms_provider_t *type_out, + const char **name_out, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(kmsid); + BSON_ASSERT_PARAM(type_out); + BSON_ASSERT_PARAM(name_out); + BSON_ASSERT(status || true); // Optional. + + *type_out = MONGOCRYPT_KMS_PROVIDER_NONE; + *name_out = NULL; + + const char *type_end = strstr(kmsid, ":"); + size_t type_nchars; + + if (type_end == NULL) { + // Parse `kmsid` as ``. + type_nchars = strlen(kmsid); + } else { + // Parse `kmsid` as `:`. + ptrdiff_t diff = type_end - kmsid; + BSON_ASSERT(diff >= 0 && (uint64_t)diff < SIZE_MAX); + type_nchars = (size_t)diff; + } + + if (0 == strncmp("aws", kmsid, type_nchars)) { + *type_out = MONGOCRYPT_KMS_PROVIDER_AWS; + } else if (0 == strncmp("azure", kmsid, type_nchars)) { + *type_out = MONGOCRYPT_KMS_PROVIDER_AZURE; + } else if (0 == strncmp("gcp", kmsid, type_nchars)) { + *type_out = MONGOCRYPT_KMS_PROVIDER_GCP; + } else if (0 == strncmp("kmip", kmsid, type_nchars)) { + *type_out = MONGOCRYPT_KMS_PROVIDER_KMIP; + } else if (0 == strncmp("local", kmsid, type_nchars)) { + *type_out = MONGOCRYPT_KMS_PROVIDER_LOCAL; + } else { + CLIENT_ERR("unrecognized KMS provider `%s`: unrecognized type. " KEY_HELP, kmsid); + return false; + } + + if (type_end != NULL) { + // Parse name. + *name_out = type_end + 1; + if (0 == strlen(*name_out)) { + CLIENT_ERR("unrecognized KMS provider `%s`: empty name. " KEY_HELP, kmsid); + return false; + } + + // Validate name only contains: [a-zA-Z0-9_] + for (const char *cp = *name_out; *cp != '\0'; cp++) { + char c = *cp; + if (c >= 'a' && c <= 'z') { + continue; + } + if (c >= 'A' && c <= 'Z') { + continue; + } + if (c >= '0' && c <= '9') { + continue; + } + if (c == '_') { + continue; + } + CLIENT_ERR("unrecognized KMS provider `%s`: unsupported character `%c`. Must be of the form `:` where `` only contain characters [a-zA-Z0-9_]", + kmsid, + c); + return false; + } + } + return true; +} + +static bool _mongocrypt_opts_kms_provider_local_parse(_mongocrypt_opts_kms_provider_local_t *local, + const char *kmsid, + const bson_t *def, + mongocrypt_status_t *status) { + bool ok = false; + if (!_mongocrypt_parse_required_binary(def, "key", &local->key, status)) { + goto fail; + } + + if (local->key.len != MONGOCRYPT_KEY_LEN) { + CLIENT_ERR("local key must be %d bytes", MONGOCRYPT_KEY_LEN); + goto fail; + } + + if (!_mongocrypt_check_allowed_fields(def, NULL /* root */, status, "key")) { + goto fail; + } + ok = true; +fail: + if (!ok) { + // Wrap error to identify the failing `kmsid`. + CLIENT_ERR("Failed to parse KMS provider `%s`: %s", kmsid, mongocrypt_status_message(status, NULL /* len */)); + } + return ok; +} + +static bool _mongocrypt_opts_kms_provider_azure_parse(_mongocrypt_opts_kms_provider_azure_t *azure, + const char *kmsid, + const bson_t *def, + mongocrypt_status_t *status) { + bool ok = false; + if (!_mongocrypt_parse_optional_utf8(def, "accessToken", &azure->access_token, status)) { + goto done; + } + + if (azure->access_token) { + // Caller provides an accessToken directly + if (!_mongocrypt_check_allowed_fields(def, NULL /* root */, status, "accessToken")) { + goto done; + } + ok = true; + goto done; + } + + // No accessToken given, so we'll need to look one up on our own later + // using the Azure API + + if (!_mongocrypt_parse_required_utf8(def, "tenantId", &azure->tenant_id, status)) { + goto done; + } + + if (!_mongocrypt_parse_required_utf8(def, "clientId", &azure->client_id, status)) { + goto done; + } + + if (!_mongocrypt_parse_required_utf8(def, "clientSecret", &azure->client_secret, status)) { + goto done; + } + + if (!_mongocrypt_parse_optional_endpoint(def, + "identityPlatformEndpoint", + &azure->identity_platform_endpoint, + NULL /* opts */, + status)) { + goto done; + } + + if (!_mongocrypt_check_allowed_fields(def, + NULL /* root */, + status, + "tenantId", + "clientId", + "clientSecret", + "identityPlatformEndpoint")) { + goto done; + } + + ok = true; +done: + if (!ok) { + // Wrap error to identify the failing `kmsid`. + CLIENT_ERR("Failed to parse KMS provider `%s`: %s", kmsid, mongocrypt_status_message(status, NULL /* len */)); + } + return ok; +} + +static bool _mongocrypt_opts_kms_provider_gcp_parse(_mongocrypt_opts_kms_provider_gcp_t *gcp, + const char *kmsid, + const bson_t *def, + mongocrypt_status_t *status) { + bool ok = false; + if (!_mongocrypt_parse_optional_utf8(def, "accessToken", &gcp->access_token, status)) { + goto done; + } + + if (gcp->access_token) { + // Caller provides an accessToken directly + if (!_mongocrypt_check_allowed_fields(def, NULL /* root */, status, "accessToken")) { + goto done; + } + ok = true; + goto done; + } + + // No accessToken given, so we'll need to look one up on our own later + // using the GCP API + + if (!_mongocrypt_parse_required_utf8(def, "email", &gcp->email, status)) { + goto done; + } + + if (!_mongocrypt_parse_required_binary(def, "privateKey", &gcp->private_key, status)) { + goto done; + } + + if (!_mongocrypt_parse_optional_endpoint(def, "endpoint", &gcp->endpoint, NULL /* opts */, status)) { + goto done; + } + + if (!_mongocrypt_check_allowed_fields(def, NULL /* root */, status, "email", "privateKey", "endpoint")) { + goto done; + } + + ok = true; +done: + if (!ok) { + // Wrap error to identify the failing `kmsid`. + CLIENT_ERR("Failed to parse KMS provider `%s`: %s", kmsid, mongocrypt_status_message(status, NULL /* len */)); + } + return ok; +} + +static bool _mongocrypt_opts_kms_provider_aws_parse(_mongocrypt_opts_kms_provider_aws_t *aws, + const char *kmsid, + const bson_t *def, + mongocrypt_status_t *status) { + bool ok = false; + + if (!_mongocrypt_parse_required_utf8(def, "accessKeyId", &aws->access_key_id, status)) { + goto done; + } + if (!_mongocrypt_parse_required_utf8(def, "secretAccessKey", &aws->secret_access_key, status)) { + goto done; + } + + if (!_mongocrypt_parse_optional_utf8(def, "sessionToken", &aws->session_token, status)) { + goto done; + } + + if (!_mongocrypt_check_allowed_fields(def, + NULL /* root */, + status, + "accessKeyId", + "secretAccessKey", + "sessionToken")) { + goto done; + } + + ok = true; +done: + if (!ok) { + // Wrap error to identify the failing `kmsid`. + CLIENT_ERR("Failed to parse KMS provider `%s`: %s", kmsid, mongocrypt_status_message(status, NULL /* len */)); + } + return ok; +} + +static bool _mongocrypt_opts_kms_provider_kmip_parse(_mongocrypt_opts_kms_provider_kmip_t *kmip, + const char *kmsid, + const bson_t *def, + mongocrypt_status_t *status) { + bool ok = false; + + _mongocrypt_endpoint_parse_opts_t opts = {0}; + + opts.allow_empty_subdomain = true; + if (!_mongocrypt_parse_required_endpoint(def, "endpoint", &kmip->endpoint, &opts, status)) { + goto done; + } + + if (!_mongocrypt_check_allowed_fields(def, NULL /* root */, status, "endpoint")) { + goto done; + } + + ok = true; +done: + if (!ok) { + // Wrap error to identify the failing `kmsid`. + CLIENT_ERR("Failed to parse KMS provider `%s`: %s", kmsid, mongocrypt_status_message(status, NULL /* len */)); + } + return ok; +} + +bool _mongocrypt_parse_kms_providers(mongocrypt_binary_t *kms_providers_definition, + _mongocrypt_opts_kms_providers_t *kms_providers, + mongocrypt_status_t *status, + _mongocrypt_log_t *log) { + bson_t as_bson; + bson_iter_t iter; + + BSON_ASSERT_PARAM(kms_providers_definition); + BSON_ASSERT_PARAM(kms_providers); + if (!_mongocrypt_binary_to_bson(kms_providers_definition, &as_bson) || !bson_iter_init(&iter, &as_bson)) { + CLIENT_ERR("invalid BSON"); + return false; + } + + while (bson_iter_next(&iter)) { + const char *field_name; + bson_t field_bson; + + field_name = bson_iter_key(&iter); + if (!mc_iter_document_as_bson(&iter, &field_bson, status)) { + return false; + } + + const char *name; + _mongocrypt_kms_provider_t type; + if (!mc_kmsid_parse(field_name, &type, &name, status)) { + return false; + } + + if (name != NULL) { + // Check if named provider already is configured. + for (size_t i = 0; i < kms_providers->named_mut.len; i++) { + mc_kms_creds_with_id_t kcwi = _mc_array_index(&kms_providers->named_mut, mc_kms_creds_with_id_t, i); + if (0 == strcmp(kcwi.kmsid, field_name)) { + CLIENT_ERR("Got unexpected duplicate entry for KMS provider: `%s`", field_name); + return false; + } + } + // Prohibit configuring with an empty document. Named KMS providers do not support on-demand credentials. + if (bson_empty(&field_bson)) { + CLIENT_ERR("Unexpected empty document for named KMS provider: '%s'. On-demand credentials are not " + "supported for named KMS providers.", + field_name); + return false; + } + switch (type) { + default: + case MONGOCRYPT_KMS_PROVIDER_NONE: { + CLIENT_ERR("Unexpected parsing KMS type: none"); + return false; + } + case MONGOCRYPT_KMS_PROVIDER_AWS: { + _mongocrypt_opts_kms_provider_aws_t aws = {0}; + if (!_mongocrypt_opts_kms_provider_aws_parse(&aws, field_name, &field_bson, status)) { + _mongocrypt_opts_kms_provider_aws_cleanup(&aws); + return false; + } + mc_kms_creds_with_id_t kcwi = {.kmsid = bson_strdup(field_name), + .creds = {.type = type, .value = {.aws = aws}}}; + _mc_array_append_val(&kms_providers->named_mut, kcwi); + break; + } + case MONGOCRYPT_KMS_PROVIDER_LOCAL: { + _mongocrypt_opts_kms_provider_local_t local = { + // specify .key to avoid erroneous missing-braces warning in GCC. Refer: + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119 + .key = {0}}; + if (!_mongocrypt_opts_kms_provider_local_parse(&local, field_name, &field_bson, status)) { + _mongocrypt_opts_kms_provider_local_cleanup(&local); + return false; + } + mc_kms_creds_with_id_t kcwi = {.kmsid = bson_strdup(field_name), + .creds = {.type = type, .value = {.local = local}}}; + _mc_array_append_val(&kms_providers->named_mut, kcwi); + break; + } + case MONGOCRYPT_KMS_PROVIDER_AZURE: { + _mongocrypt_opts_kms_provider_azure_t azure = {0}; + if (!_mongocrypt_opts_kms_provider_azure_parse(&azure, field_name, &field_bson, status)) { + _mongocrypt_opts_kms_provider_azure_cleanup(&azure); + return false; + } + mc_kms_creds_with_id_t kcwi = {.kmsid = bson_strdup(field_name), + .creds = {.type = type, .value = {.azure = azure}}}; + _mc_array_append_val(&kms_providers->named_mut, kcwi); + break; + } + case MONGOCRYPT_KMS_PROVIDER_GCP: { + _mongocrypt_opts_kms_provider_gcp_t gcp = {0}; + if (!_mongocrypt_opts_kms_provider_gcp_parse(&gcp, field_name, &field_bson, status)) { + _mongocrypt_opts_kms_provider_gcp_cleanup(&gcp); + return false; + } + mc_kms_creds_with_id_t kcwi = {.kmsid = bson_strdup(field_name), + .creds = {.type = type, .value = {.gcp = gcp}}}; + _mc_array_append_val(&kms_providers->named_mut, kcwi); + break; + } + case MONGOCRYPT_KMS_PROVIDER_KMIP: { + _mongocrypt_opts_kms_provider_kmip_t kmip = {0}; + if (!_mongocrypt_opts_kms_provider_kmip_parse(&kmip, field_name, &field_bson, status)) { + _mongocrypt_opts_kms_provider_kmip_cleanup(&kmip); + return false; + } + mc_kms_creds_with_id_t kcwi = {.kmsid = bson_strdup(field_name), + .creds = {.type = type, .value = {.kmip = kmip}}}; + _mc_array_append_val(&kms_providers->named_mut, kcwi); + break; + } + } + } else if (0 == strcmp(field_name, "azure") && bson_empty(&field_bson)) { + kms_providers->need_credentials |= MONGOCRYPT_KMS_PROVIDER_AZURE; + } else if (0 == strcmp(field_name, "azure")) { + if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_AZURE)) { + CLIENT_ERR("azure KMS provider already set"); + return false; + } + + if (!_mongocrypt_opts_kms_provider_azure_parse(&kms_providers->azure_mut, + field_name, + &field_bson, + status)) { + return false; + } + kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_AZURE; + } else if (0 == strcmp(field_name, "gcp") && bson_empty(&field_bson)) { + kms_providers->need_credentials |= MONGOCRYPT_KMS_PROVIDER_GCP; + } else if (0 == strcmp(field_name, "gcp")) { + if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_GCP)) { + CLIENT_ERR("gcp KMS provider already set"); + return false; + } + if (!_mongocrypt_opts_kms_provider_gcp_parse(&kms_providers->gcp_mut, field_name, &field_bson, status)) { + return false; + } + kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_GCP; + } else if (0 == strcmp(field_name, "local") && bson_empty(&field_bson)) { + kms_providers->need_credentials |= MONGOCRYPT_KMS_PROVIDER_LOCAL; + } else if (0 == strcmp(field_name, "local")) { + if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_LOCAL)) { + CLIENT_ERR("local KMS provider already set"); + return false; + } + if (!_mongocrypt_opts_kms_provider_local_parse(&kms_providers->local_mut, + field_name, + &field_bson, + status)) { + return false; + } + kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_LOCAL; + } else if (0 == strcmp(field_name, "aws") && bson_empty(&field_bson)) { + kms_providers->need_credentials |= MONGOCRYPT_KMS_PROVIDER_AWS; + } else if (0 == strcmp(field_name, "aws")) { + if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_AWS)) { + CLIENT_ERR("aws KMS provider already set"); + return false; + } + if (!_mongocrypt_opts_kms_provider_aws_parse(&kms_providers->aws_mut, field_name, &field_bson, status)) { + return false; + } + kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_AWS; + } else if (0 == strcmp(field_name, "kmip") && bson_empty(&field_bson)) { + kms_providers->need_credentials |= MONGOCRYPT_KMS_PROVIDER_KMIP; + } else if (0 == strcmp(field_name, "kmip")) { + if (!_mongocrypt_opts_kms_provider_kmip_parse(&kms_providers->kmip_mut, field_name, &field_bson, status)) { + return false; + } + kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_KMIP; + } else { + CLIENT_ERR("unsupported KMS provider: %s", field_name); + return false; + } + } + + if (log && log->trace_enabled) { + char *as_str = bson_as_relaxed_extended_json(&as_bson, NULL); + _mongocrypt_log(log, MONGOCRYPT_LOG_LEVEL_TRACE, "%s (%s=\"%s\")", BSON_FUNC, "kms_providers", as_str); + bson_free(as_str); + } + + return true; +} diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-private.h b/src/third_party/libmongocrypt/dist/src/mongocrypt-private.h index 15ec3630748..147bd1e7c2c 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-private.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-private.h @@ -39,7 +39,7 @@ #define CLIENT_ERR_W_CODE(code, ...) _mongocrypt_set_error(status, MONGOCRYPT_STATUS_ERROR_CLIENT, code, __VA_ARGS__) -#define CLIENT_ERR(...) CLIENT_ERR_W_CODE(MONGOCRYPT_GENERIC_ERROR_CODE, __VA_ARGS__) +#define CLIENT_ERR(fmt, ...) CLIENT_ERR_W_CODE(MONGOCRYPT_GENERIC_ERROR_CODE, fmt, ##__VA_ARGS__) #define KMS_ERR_W_CODE(code, ...) _mongocrypt_set_error(status, MONGOCRYPT_STATUS_ERROR_KMS, code, __VA_ARGS__) @@ -49,9 +49,6 @@ #define MONGOCRYPT_DATA_AND_LEN(x) ((uint8_t *)x), (sizeof(x) / sizeof((x)[0]) - 1) -/* TODO: remove after integrating into libmongoc */ -#define BSON_SUBTYPE_ENCRYPTED 6 - /* TODO: Move these to mongocrypt-log-private.h? */ const char *tmp_json(const bson_t *bson); @@ -123,12 +120,12 @@ struct _mongocrypt_t { _mongocrypt_crypto_t *crypto; /* A counter, protected by mutex, for generating unique context ids */ uint32_t ctx_counter; - _mongocrypt_cache_oauth_t *cache_oauth_azure; - _mongocrypt_cache_oauth_t *cache_oauth_gcp; + mc_mapof_kmsid_to_token_t *cache_oauth; /// A CSFLE DLL vtable, initialized by mongocrypt_init _mongo_crypt_v1_vtable csfle; /// Pointer to the global csfle_lib object. Should not be freed directly. mongo_crypt_v1_lib *csfle_lib; + bool retry_enabled; }; typedef enum { @@ -154,18 +151,15 @@ char *_mongocrypt_new_string_from_bytes(const void *in, int len); char *_mongocrypt_new_json_string_from_binary(mongocrypt_binary_t *binary); -bool _mongocrypt_parse_kms_providers(mongocrypt_binary_t *kms_providers_definition, - _mongocrypt_opts_kms_providers_t *kms_providers, - mongocrypt_status_t *status, - _mongocrypt_log_t *log); - /* _mongocrypt_needs_credentials returns true if @crypt was configured to * request credentials for any KMS provider. */ bool _mongocrypt_needs_credentials(mongocrypt_t *crypt); /* _mongocrypt_needs_credentials returns true if @crypt was configured to - * request credentials for @provider. */ -bool _mongocrypt_needs_credentials_for_provider(mongocrypt_t *crypt, _mongocrypt_kms_provider_t provider); + * request credentials for @provider and optional @name. @name may be NULL. */ +bool _mongocrypt_needs_credentials_for_provider(mongocrypt_t *crypt, + _mongocrypt_kms_provider_t provider, + const char *name); /** * Enable/disable the use of FLE2v2 payload types for write. diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt-util.c b/src/third_party/libmongocrypt/dist/src/mongocrypt-util.c index 205ce264d27..202c49586aa 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt-util.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt-util.c @@ -81,7 +81,7 @@ current_module_result current_module_path(void) { #elif defined(_GNU_SOURCE) || defined(_DARWIN_C_SOURCE) || defined(__FreeBSD__) // Darwin/BSD/glibc define extensions for finding dynamic library info from // the address of a symbol. - Dl_info info; + Dl_info info = {0}; int rc = dladdr((const void *)current_module_path, &info); if (rc == 0) { // Failed to resolve the symbol @@ -90,7 +90,8 @@ current_module_result current_module_path(void) { ret_str = mstr_copy_cstr(info.dli_fname); } #else -#error "Don't know how to get the module path on this platform" + // Not supported on this system. + ret_error = ENOSYS; #endif return (current_module_result){.path = ret_str, .error = ret_error}; } diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt.c b/src/third_party/libmongocrypt/dist/src/mongocrypt.c index c5adefbdfc3..14f427b8bdb 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt.c +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt.c @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "mongocrypt.h" #include "mlib/error.h" #include "mlib/path.h" #include "mlib/thread.h" @@ -24,6 +25,7 @@ #include "mongocrypt-binary-private.h" #include "mongocrypt-cache-collinfo-private.h" #include "mongocrypt-cache-key-private.h" +#include "mongocrypt-cache-private.h" #include "mongocrypt-config.h" #include "mongocrypt-crypto-private.h" #include "mongocrypt-log-private.h" @@ -119,8 +121,7 @@ mongocrypt_t *mongocrypt_new(void) { // Default to using FLEv2 (aka QEv2) crypt->opts.use_fle2_v2 = true; crypt->ctx_counter = 1; - crypt->cache_oauth_azure = _mongocrypt_cache_oauth_new(); - crypt->cache_oauth_gcp = _mongocrypt_cache_oauth_new(); + crypt->cache_oauth = mc_mapof_kmsid_to_token_new(); crypt->csfle = (_mongo_crypt_v1_vtable){.okay = false}; static mlib_once_flag init_flag = MLIB_ONCE_INITIALIZER; @@ -154,6 +155,13 @@ bool mongocrypt_setopt_fle2v2(mongocrypt_t *crypt, bool enable) { return true; } +bool mongocrypt_setopt_use_range_v2(mongocrypt_t *crypt) { + ASSERT_MONGOCRYPT_PARAM_UNINIT(crypt); + + // Nothing to do. As of MONGOCRYPT-661, rangeV2 is the default. + return true; +} + bool mongocrypt_setopt_log_handler(mongocrypt_t *crypt, mongocrypt_log_fn_t log_fn, void *log_ctx) { ASSERT_MONGOCRYPT_PARAM_UNINIT(crypt); crypt->opts.log_fn = log_fn; @@ -161,6 +169,12 @@ bool mongocrypt_setopt_log_handler(mongocrypt_t *crypt, mongocrypt_log_fn_t log_ return true; } +bool mongocrypt_setopt_retry_kms(mongocrypt_t *crypt, bool enable) { + ASSERT_MONGOCRYPT_PARAM_UNINIT(crypt); + crypt->retry_enabled = enable; + return true; +} + bool mongocrypt_setopt_kms_provider_aws(mongocrypt_t *crypt, const char *aws_access_key_id, int32_t aws_access_key_id_len, @@ -178,14 +192,14 @@ bool mongocrypt_setopt_kms_provider_aws(mongocrypt_t *crypt, if (!_mongocrypt_validate_and_copy_string(aws_access_key_id, aws_access_key_id_len, - &kms_providers->aws.access_key_id)) { + &kms_providers->aws_mut.access_key_id)) { CLIENT_ERR("invalid aws access key id"); return false; } if (!_mongocrypt_validate_and_copy_string(aws_secret_access_key, aws_secret_access_key_len, - &kms_providers->aws.secret_access_key)) { + &kms_providers->aws_mut.secret_access_key)) { CLIENT_ERR("invalid aws secret access key"); return false; } @@ -196,11 +210,11 @@ bool mongocrypt_setopt_kms_provider_aws(mongocrypt_t *crypt, "%s (%s=\"%s\", %s=%d, %s=\"%s\", %s=%d)", BSON_FUNC, "aws_access_key_id", - kms_providers->aws.access_key_id, + kms_providers->aws_mut.access_key_id, "aws_access_key_id_len", aws_access_key_id_len, "aws_secret_access_key", - kms_providers->aws.secret_access_key, + kms_providers->aws_mut.secret_access_key, "aws_secret_access_key_len", aws_secret_access_key_len); } @@ -208,6 +222,17 @@ bool mongocrypt_setopt_kms_provider_aws(mongocrypt_t *crypt, return true; } +bool mongocrypt_setopt_key_expiration(mongocrypt_t *crypt, uint64_t cache_expiration_ms) { + ASSERT_MONGOCRYPT_PARAM_UNINIT(crypt); + if (cache_expiration_ms > INT64_MAX) { + mongocrypt_status_t *status = crypt->status; + CLIENT_ERR("expiration time must be less than %" PRId64 ", but got %" PRIu64, INT64_MAX, cache_expiration_ms); + return false; + } + crypt->cache_key.expiration = cache_expiration_ms; + return true; +} + char *_mongocrypt_new_string_from_bytes(const void *in, int len) { const int max_bytes = 100; const int chars_per_byte = 2; @@ -345,7 +370,7 @@ bool mongocrypt_setopt_kms_provider_local(mongocrypt_t *crypt, mongocrypt_binary bson_free(key_val); } - _mongocrypt_buffer_copy_from_binary(&kms_providers->local.key, key); + _mongocrypt_buffer_copy_from_binary(&kms_providers->local_mut.key, key); kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_LOCAL; return true; } @@ -365,7 +390,7 @@ typedef struct { * * @param status is an optional status to set an error message if `mcr_dll_open` fails. */ -static _loaded_csfle _try_load_csfle(const char *filepath, _mongocrypt_log_t *log, mongocrypt_status_t *status) { +static _loaded_csfle _try_load_csfle(const char *filepath, mongocrypt_status_t *status, _mongocrypt_log_t *log) { // Try to open the dynamic lib mcr_dll lib = mcr_dll_open(filepath); // Check for errors, which are represented by strings @@ -479,7 +504,7 @@ static _loaded_csfle _try_find_csfle(mongocrypt_t *crypt) { // Do not allow a plain filename to go through, as that will cause the // DLL load to search the system. mstr_assign(&csfle_cand_filepath, mpath_absolute(csfle_cand_filepath.view, MPATH_NATIVE)); - candidate_csfle = _try_load_csfle(csfle_cand_filepath.data, &crypt->log, crypt->status); + candidate_csfle = _try_load_csfle(csfle_cand_filepath.data, crypt->status, &crypt->log); } } else { // No override path was specified, so try to find it on the provided @@ -501,7 +526,7 @@ static _loaded_csfle _try_find_csfle(mongocrypt_t *crypt) { } } // Try to load the file: - candidate_csfle = _try_load_csfle(csfle_cand_filepath.data, &crypt->log, NULL /* status */); + candidate_csfle = _try_load_csfle(csfle_cand_filepath.data, NULL /* status */, &crypt->log); if (candidate_csfle.okay) { // Stop searching: break; @@ -552,6 +577,14 @@ static bool _validate_csfle_singleton(mongocrypt_t *crypt, _loaded_csfle found) BSON_ASSERT_PARAM(crypt); + if (!mcr_dll_path_supported()) { + _mongocrypt_log(&crypt->log, + MONGOCRYPT_LOG_LEVEL_WARNING, + "Cannot get path of loaded library on this platform. Skipping validation to ensure " + "exactly one csfle library is loaded."); + return true; + } + status = crypt->status; // Path to the existing loaded csfle: @@ -884,6 +917,14 @@ bool mongocrypt_init(mongocrypt_t *crypt) { return _try_enable_csfle(crypt); } +bool mongocrypt_is_crypto_available(void) { +#ifdef MONGOCRYPT_ENABLE_CRYPTO + return true; +#else + return false; +#endif +} + bool mongocrypt_status(mongocrypt_t *crypt, mongocrypt_status_t *out) { BSON_ASSERT_PARAM(crypt); @@ -912,8 +953,7 @@ void mongocrypt_destroy(mongocrypt_t *crypt) { _mongocrypt_log_cleanup(&crypt->log); mongocrypt_status_destroy(crypt->status); bson_free(crypt->crypto); - _mongocrypt_cache_oauth_destroy(crypt->cache_oauth_azure); - _mongocrypt_cache_oauth_destroy(crypt->cache_oauth_gcp); + mc_mapof_kmsid_to_token_destroy(crypt->cache_oauth); if (crypt->csfle.okay) { _csfle_drop_global_ref(); @@ -1097,236 +1137,6 @@ bool mongocrypt_setopt_kms_providers(mongocrypt_t *crypt, mongocrypt_binary_t *k &crypt->log); } -bool _mongocrypt_parse_kms_providers(mongocrypt_binary_t *kms_providers_definition, - _mongocrypt_opts_kms_providers_t *kms_providers, - mongocrypt_status_t *status, - _mongocrypt_log_t *log) { - bson_t as_bson; - bson_iter_t iter; - - BSON_ASSERT_PARAM(kms_providers_definition); - BSON_ASSERT_PARAM(kms_providers); - if (!_mongocrypt_binary_to_bson(kms_providers_definition, &as_bson) || !bson_iter_init(&iter, &as_bson)) { - CLIENT_ERR("invalid BSON"); - return false; - } - - while (bson_iter_next(&iter)) { - const char *field_name; - bson_t field_bson; - - field_name = bson_iter_key(&iter); - if (!mc_iter_document_as_bson(&iter, &field_bson, status)) { - return false; - } - - if (0 == strcmp(field_name, "azure") && bson_empty(&field_bson)) { - kms_providers->need_credentials |= MONGOCRYPT_KMS_PROVIDER_AZURE; - } else if (0 == strcmp(field_name, "azure")) { - if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_AZURE)) { - CLIENT_ERR("azure KMS provider already set"); - return false; - } - - if (!_mongocrypt_parse_optional_utf8(&as_bson, - "azure.accessToken", - &kms_providers->azure.access_token, - status)) { - return false; - } - - if (kms_providers->azure.access_token) { - // Caller provides an accessToken directly - if (!_mongocrypt_check_allowed_fields(&as_bson, "azure", status, "accessToken")) { - return false; - } - kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_AZURE; - continue; - } - - // No accessToken given, so we'll need to look one up on our own later - // using the Azure API - - if (!_mongocrypt_parse_required_utf8(&as_bson, "azure.tenantId", &kms_providers->azure.tenant_id, status)) { - return false; - } - - if (!_mongocrypt_parse_required_utf8(&as_bson, "azure.clientId", &kms_providers->azure.client_id, status)) { - return false; - } - - if (!_mongocrypt_parse_required_utf8(&as_bson, - "azure.clientSecret", - &kms_providers->azure.client_secret, - status)) { - return false; - } - - if (!_mongocrypt_parse_optional_endpoint(&as_bson, - "azure.identityPlatformEndpoint", - &kms_providers->azure.identity_platform_endpoint, - NULL /* opts */, - status)) { - return false; - } - - if (!_mongocrypt_check_allowed_fields(&as_bson, - "azure", - status, - "tenantId", - "clientId", - "clientSecret", - "identityPlatformEndpoint")) { - return false; - } - kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_AZURE; - } else if (0 == strcmp(field_name, "gcp") && bson_empty(&field_bson)) { - kms_providers->need_credentials |= MONGOCRYPT_KMS_PROVIDER_GCP; - } else if (0 == strcmp(field_name, "gcp")) { - if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_GCP)) { - CLIENT_ERR("gcp KMS provider already set"); - return false; - } - - if (!_mongocrypt_parse_optional_utf8(&as_bson, - "gcp.accessToken", - &kms_providers->gcp.access_token, - status)) { - return false; - } - - if (NULL != kms_providers->gcp.access_token) { - /* "gcp" document has form: - * { - * "accessToken": - * } - */ - if (!_mongocrypt_check_allowed_fields(&as_bson, "gcp", status, "accessToken")) { - return false; - } - kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_GCP; - continue; - } - - /* "gcp" document has form: - * { - * "email": - * "privateKey": - * } - */ - if (!_mongocrypt_parse_required_utf8(&as_bson, "gcp.email", &kms_providers->gcp.email, status)) { - return false; - } - - if (!_mongocrypt_parse_required_binary(&as_bson, - "gcp.privateKey", - &kms_providers->gcp.private_key, - status)) { - return false; - } - - if (!_mongocrypt_parse_optional_endpoint(&as_bson, - "gcp.endpoint", - &kms_providers->gcp.endpoint, - NULL /* opts */, - status)) { - return false; - } - - if (!_mongocrypt_check_allowed_fields(&as_bson, "gcp", status, "email", "privateKey", "endpoint")) { - return false; - } - kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_GCP; - } else if (0 == strcmp(field_name, "local") && bson_empty(&field_bson)) { - kms_providers->need_credentials |= MONGOCRYPT_KMS_PROVIDER_LOCAL; - } else if (0 == strcmp(field_name, "local")) { - if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_LOCAL)) { - CLIENT_ERR("local KMS provider already set"); - return false; - } - if (!_mongocrypt_parse_required_binary(&as_bson, "local.key", &kms_providers->local.key, status)) { - return false; - } - - if (kms_providers->local.key.len != MONGOCRYPT_KEY_LEN) { - CLIENT_ERR("local key must be %d bytes", MONGOCRYPT_KEY_LEN); - return false; - } - - if (!_mongocrypt_check_allowed_fields(&as_bson, "local", status, "key")) { - return false; - } - kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_LOCAL; - } else if (0 == strcmp(field_name, "aws") && bson_empty(&field_bson)) { - kms_providers->need_credentials |= MONGOCRYPT_KMS_PROVIDER_AWS; - } else if (0 == strcmp(field_name, "aws")) { - if (0 != (kms_providers->configured_providers & MONGOCRYPT_KMS_PROVIDER_AWS)) { - CLIENT_ERR("aws KMS provider already set"); - return false; - } - - if (!_mongocrypt_parse_required_utf8(&as_bson, - "aws.accessKeyId", - &kms_providers->aws.access_key_id, - status)) { - return false; - } - if (!_mongocrypt_parse_required_utf8(&as_bson, - "aws.secretAccessKey", - &kms_providers->aws.secret_access_key, - status)) { - return false; - } - - if (!_mongocrypt_parse_optional_utf8(&as_bson, - "aws.sessionToken", - &kms_providers->aws.session_token, - status)) { - return false; - } - - if (!_mongocrypt_check_allowed_fields(&as_bson, - "aws", - status, - "accessKeyId", - "secretAccessKey", - "sessionToken")) { - return false; - } - kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_AWS; - } else if (0 == strcmp(field_name, "kmip") && bson_empty(&field_bson)) { - kms_providers->need_credentials |= MONGOCRYPT_KMS_PROVIDER_KMIP; - } else if (0 == strcmp(field_name, "kmip")) { - _mongocrypt_endpoint_parse_opts_t opts = {0}; - - opts.allow_empty_subdomain = true; - if (!_mongocrypt_parse_required_endpoint(&as_bson, - "kmip.endpoint", - &kms_providers->kmip.endpoint, - &opts, - status)) { - return false; - } - - if (!_mongocrypt_check_allowed_fields(&as_bson, "kmip", status, "endpoint")) { - return false; - } - kms_providers->configured_providers |= MONGOCRYPT_KMS_PROVIDER_KMIP; - } else { - CLIENT_ERR("unsupported KMS provider: %s", field_name); - return false; - } - } - - if (log && log->trace_enabled) { - char *as_str = bson_as_json(&as_bson, NULL); - _mongocrypt_log(log, MONGOCRYPT_LOG_LEVEL_TRACE, "%s (%s=\"%s\")", BSON_FUNC, "kms_providers", as_str); - bson_free(as_str); - } - - return true; -} - void mongocrypt_setopt_append_crypt_shared_lib_search_path(mongocrypt_t *crypt, const char *path) { BSON_ASSERT_PARAM(crypt); BSON_ASSERT_PARAM(path); @@ -1352,6 +1162,12 @@ void mongocrypt_setopt_use_need_kms_credentials_state(mongocrypt_t *crypt) { crypt->opts.use_need_kms_credentials_state = true; } +void mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(mongocrypt_t *crypt) { + BSON_ASSERT_PARAM(crypt); + + crypt->opts.use_need_mongo_collinfo_with_db_state = true; +} + void mongocrypt_setopt_set_crypt_shared_lib_path_override(mongocrypt_t *crypt, const char *path) { BSON_ASSERT_PARAM(crypt); BSON_ASSERT_PARAM(path); @@ -1369,9 +1185,16 @@ bool _mongocrypt_needs_credentials(mongocrypt_t *crypt) { return crypt->opts.kms_providers.need_credentials != 0; } -bool _mongocrypt_needs_credentials_for_provider(mongocrypt_t *crypt, _mongocrypt_kms_provider_t provider) { +bool _mongocrypt_needs_credentials_for_provider(mongocrypt_t *crypt, + _mongocrypt_kms_provider_t provider, + const char *name) { BSON_ASSERT_PARAM(crypt); + if (name != NULL) { + // Named KMS providers do not support on-demand credentials. + return false; + } + if (!crypt->opts.use_need_kms_credentials_state) { return false; } diff --git a/src/third_party/libmongocrypt/dist/src/mongocrypt.h b/src/third_party/libmongocrypt/dist/src/mongocrypt.h index e4ca0b133cd..7227e0ef7ec 100644 --- a/src/third_party/libmongocrypt/dist/src/mongocrypt.h +++ b/src/third_party/libmongocrypt/dist/src/mongocrypt.h @@ -48,6 +48,17 @@ MONGOCRYPT_EXPORT const char *mongocrypt_version(uint32_t *len); +/** + * Returns true if libmongocrypt was built with native crypto support. + * + * If libmongocrypt was not built with native crypto support, setting crypto + * hooks is required. + * + * @returns True if libmongocrypt was built with native crypto support. + */ +MONGOCRYPT_EXPORT +bool mongocrypt_is_crypto_available(void); + /** * A non-owning view of a byte buffer. * @@ -69,8 +80,14 @@ const char *mongocrypt_version(uint32_t *len); * mongocrypt_ctx_mongo_op guarantees that the viewed data of * mongocrypt_binary_t is valid until the parent ctx is destroyed with @ref * mongocrypt_ctx_destroy. + * + * The `mongocrypt_binary_t` struct definition is public. + * Consumers may rely on the struct layout. */ -typedef struct _mongocrypt_binary_t mongocrypt_binary_t; +typedef struct _mongocrypt_binary_t { + void *data; + uint32_t len; +} mongocrypt_binary_t; /** * Create a new non-owning view of a buffer (data + length). @@ -293,6 +310,18 @@ mongocrypt_t *mongocrypt_new(void); MONGOCRYPT_EXPORT bool mongocrypt_setopt_log_handler(mongocrypt_t *crypt, mongocrypt_log_fn_t log_fn, void *log_ctx); +/** + * Enable or disable KMS retry behavior. + * + * @param[in] crypt The @ref mongocrypt_t object. + * @param[in] enable A boolean indicating whether to retry operations. + * @pre @ref mongocrypt_init has not been called on @p crypt. + * @returns A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_ctx_status + */ +MONGOCRYPT_EXPORT +bool mongocrypt_setopt_retry_kms(mongocrypt_t *crypt, bool enable); + /** * Configure an AWS KMS provider on the @ref mongocrypt_t object. * @@ -456,6 +485,18 @@ void mongocrypt_setopt_set_crypt_shared_lib_path_override(mongocrypt_t *crypt, c MONGOCRYPT_EXPORT void mongocrypt_setopt_use_need_kms_credentials_state(mongocrypt_t *crypt); +/** + * @brief Opt-into handling the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state. + * + * A context enters the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state when + * processing a `bulkWrite` command. The target database of the `bulkWrite` may differ from the command database + * ("admin"). + * + * @param[in] crypt The @ref mongocrypt_t object to update + */ +MONGOCRYPT_EXPORT +void mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(mongocrypt_t *crypt); + /** * Initialize new @ref mongocrypt_t object. * @@ -517,7 +558,7 @@ const char *mongocrypt_crypt_shared_lib_version_string(const mongocrypt_t *crypt * @brief Obtain a 64-bit constant encoding the version of the loaded * crypt_shared library, if available. * - * @param[in] crypt The mongocrypt_t object after a successul call to + * @param[in] crypt The mongocrypt_t object after a successful call to * mongocrypt_init. * * @return A 64-bit encoded version number, with the version encoded as four @@ -657,10 +698,9 @@ bool mongocrypt_ctx_setopt_algorithm(mongocrypt_ctx_t *ctx, const char *algorith #define MONGOCRYPT_ALGORITHM_INDEXED_STR "Indexed" /// String constant for setopt_algorithm "Unindexed" explicit encryption #define MONGOCRYPT_ALGORITHM_UNINDEXED_STR "Unindexed" -/// String constant for setopt_algorithm "rangePreview" explicit encryption -/// NOTE: The RangePreview algorithm is experimental only. It is not intended -/// for public use. -#define MONGOCRYPT_ALGORITHM_RANGEPREVIEW_STR "RangePreview" +// DEPRECATED: support "RangePreview" has been removed in favor of "range". +#define MONGOCRYPT_ALGORITHM_RANGEPREVIEW_DEPRECATED_STR "RangePreview" +#define MONGOCRYPT_ALGORITHM_RANGE_STR "Range" /** * Identify the AWS KMS master key to use for creating a data key. @@ -849,9 +889,7 @@ bool mongocrypt_ctx_explicit_encrypt_init(mongocrypt_ctx_t *ctx, mongocrypt_bina /** * Explicit helper method to encrypt a Match Expression or Aggregate Expression. * Contexts created for explicit encryption will not go through mongocryptd. - * Requires query_type to be "rangePreview". - * NOTE: The RangePreview algorithm is experimental only. It is not intended for - * public use. + * Requires query_type to be "range". * * This method expects the passed-in BSON to be of the form: * { "v" : FLE2RangeFindDriverSpec } @@ -948,9 +986,10 @@ bool mongocrypt_ctx_rewrap_many_datakey_init(mongocrypt_ctx_t *ctx, mongocrypt_b */ typedef enum { MONGOCRYPT_CTX_ERROR = 0, - MONGOCRYPT_CTX_NEED_MONGO_COLLINFO = 1, /* run on main MongoClient */ - MONGOCRYPT_CTX_NEED_MONGO_MARKINGS = 2, /* run on mongocryptd. */ - MONGOCRYPT_CTX_NEED_MONGO_KEYS = 3, /* run on key vault */ + MONGOCRYPT_CTX_NEED_MONGO_COLLINFO = 1, /* run on main MongoClient */ + MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB = 8, /* run on main MongoClient */ + MONGOCRYPT_CTX_NEED_MONGO_MARKINGS = 2, /* run on mongocryptd. */ + MONGOCRYPT_CTX_NEED_MONGO_KEYS = 3, /* run on key vault */ MONGOCRYPT_CTX_NEED_KMS = 4, MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS = 7, /* fetch/renew KMS credentials */ MONGOCRYPT_CTX_READY = 5, /* ready for encryption/decryption */ @@ -971,7 +1010,7 @@ mongocrypt_ctx_state_t mongocrypt_ctx_state(mongocrypt_ctx_t *ctx); * is in MONGOCRYPT_CTX_NEED_MONGO_* states. * * @p op_bson is a BSON document to be used for the operation. - * - For MONGOCRYPT_CTX_NEED_MONGO_COLLINFO it is a listCollections filter. + * - For MONGOCRYPT_CTX_NEED_MONGO_COLLINFO(_WITH_DB) it is a listCollections filter. * - For MONGOCRYPT_CTX_NEED_MONGO_KEYS it is a find filter. * - For MONGOCRYPT_CTX_NEED_MONGO_MARKINGS it is a command to send to * mongocryptd. @@ -989,13 +1028,29 @@ mongocrypt_ctx_state_t mongocrypt_ctx_state(mongocrypt_ctx_t *ctx); MONGOCRYPT_EXPORT bool mongocrypt_ctx_mongo_op(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *op_bson); +/** + * Get the database to run the mongo operation. + * + * Only applies when mongocrypt_ctx_t is in the state: + * MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB. + * + * The lifetime of the returned string is tied to the lifetime of @p ctx. It is + * valid until @ref mongocrypt_ctx_destroy is called. + * + * @param[in] ctx The @ref mongocrypt_ctx_t object. + * @returns A string or NULL. If NULL, an error status is set. Retrieve it with + * @ref mongocrypt_ctx_status + */ +MONGOCRYPT_EXPORT +const char *mongocrypt_ctx_mongo_db(mongocrypt_ctx_t *ctx); + /** * Feed a BSON reply or result when mongocrypt_ctx_t is in * MONGOCRYPT_CTX_NEED_MONGO_* states. This may be called multiple times * depending on the operation. * * reply is a BSON document result being fed back for this operation. - * - For MONGOCRYPT_CTX_NEED_MONGO_COLLINFO it is a doc from a listCollections + * - For MONGOCRYPT_CTX_NEED_MONGO_COLLINFO(_WITH_DB) it is a doc from a listCollections * cursor. (Note, if listCollections returned no result, do not call this * function.) * - For MONGOCRYPT_CTX_NEED_MONGO_KEYS it is a doc from a find cursor. @@ -1039,6 +1094,8 @@ typedef struct _mongocrypt_kms_ctx_t mongocrypt_kms_ctx_t; * If KMS handles are being handled synchronously, the driver can reuse the same * TLS socket to send HTTP requests and receive responses. * + * The returned KMS handle does not outlive `ctx`. + * * @param[in] ctx A @ref mongocrypt_ctx_t. * @returns a new @ref mongocrypt_kms_ctx_t or NULL. */ @@ -1087,6 +1144,15 @@ bool mongocrypt_kms_ctx_endpoint(mongocrypt_kms_ctx_t *kms, const char **endpoin MONGOCRYPT_EXPORT uint32_t mongocrypt_kms_ctx_bytes_needed(mongocrypt_kms_ctx_t *kms); +/** + * Indicates how long to sleep before sending this request. + * + * @param[in] kms The @ref mongocrypt_kms_ctx_t. + * @returns How long to sleep in microseconds. + */ +MONGOCRYPT_EXPORT +int64_t mongocrypt_kms_ctx_usleep(mongocrypt_kms_ctx_t *kms); + /** * Feed bytes from the HTTP response. * @@ -1102,6 +1168,15 @@ uint32_t mongocrypt_kms_ctx_bytes_needed(mongocrypt_kms_ctx_t *kms); MONGOCRYPT_EXPORT bool mongocrypt_kms_ctx_feed(mongocrypt_kms_ctx_t *kms, mongocrypt_binary_t *bytes); +/** + * Indicate a network-level failure. + * + * @param[in] kms The @ref mongocrypt_kms_ctx_t. + * @return A boolean indicating whether the failed request may be retried. + */ +MONGOCRYPT_EXPORT +bool mongocrypt_kms_ctx_fail(mongocrypt_kms_ctx_t *kms); + /** * Get the status associated with a @ref mongocrypt_kms_ctx_t object. * @@ -1221,7 +1296,7 @@ void mongocrypt_ctx_destroy(mongocrypt_ctx_t *ctx); * @param[out] status An optional status to pass error messages. See @ref * mongocrypt_status_set. * @returns A boolean indicating success. If returning false, set @p status - * with a message indiciating the error using @ref mongocrypt_status_set. + * with a message indicating the error using @ref mongocrypt_status_set. */ typedef bool (*mongocrypt_crypto_fn)(void *ctx, mongocrypt_binary_t *key, @@ -1246,7 +1321,7 @@ typedef bool (*mongocrypt_crypto_fn)(void *ctx, * @param[out] status An optional status to pass error messages. See @ref * mongocrypt_status_set. * @returns A boolean indicating success. If returning false, set @p status - * with a message indiciating the error using @ref mongocrypt_status_set. + * with a message indicating the error using @ref mongocrypt_status_set. */ typedef bool (*mongocrypt_hmac_fn)(void *ctx, mongocrypt_binary_t *key, @@ -1265,7 +1340,7 @@ typedef bool (*mongocrypt_hmac_fn)(void *ctx, * @param[out] status An optional status to pass error messages. See @ref * mongocrypt_status_set. * @returns A boolean indicating success. If returning false, set @p status - * with a message indiciating the error using @ref mongocrypt_status_set. + * with a message indicating the error using @ref mongocrypt_status_set. */ typedef bool (*mongocrypt_hash_fn)(void *ctx, mongocrypt_binary_t *in, @@ -1283,7 +1358,7 @@ typedef bool (*mongocrypt_hash_fn)(void *ctx, * @param[out] status An optional status to pass error messages. See @ref * mongocrypt_status_set. * @returns A boolean indicating success. If returning false, set @p status - * with a message indiciating the error using @ref mongocrypt_status_set. + * with a message indicating the error using @ref mongocrypt_status_set. */ typedef bool (*mongocrypt_random_fn)(void *ctx, mongocrypt_binary_t *out, uint32_t count, mongocrypt_status_t *status); @@ -1305,8 +1380,7 @@ bool mongocrypt_setopt_crypto_hooks(mongocrypt_t *crypt, * operation. * @param[in] aes_256_ctr_decrypt The crypto callback function for decrypt * operation. - * @param[in] ctx A context passed as an argument to the crypto callback - * every invocation. + * @param[in] ctx Unused. * @pre @ref mongocrypt_init has not been called on @p crypt. * @returns A boolean indicating success. If false, an error status is set. * Retrieve it with @ref mongocrypt_status @@ -1326,8 +1400,7 @@ bool mongocrypt_setopt_aes_256_ctr(mongocrypt_t *crypt, * @param[in] crypt The @ref mongocrypt_t object. * @param[in] aes_256_ecb_encrypt The crypto callback function for encrypt * operation. - * @param[in] ctx A context passed as an argument to the crypto callback - * every invocation. + * @param[in] ctx Unused. * @pre @ref mongocrypt_init has not been called on @p crypt. * @returns A boolean indicating success. If false, an error status is set. * Retrieve it with @ref mongocrypt_status @@ -1370,6 +1443,17 @@ bool mongocrypt_setopt_crypto_hook_sign_rsaes_pkcs1_v1_5(mongocrypt_t *crypt, MONGOCRYPT_EXPORT void mongocrypt_setopt_bypass_query_analysis(mongocrypt_t *crypt); +/** + * DEPRECATED: Use of `mongocrypt_setopt_use_range_v2` is deprecated. Range V2 is always enabled. + * + * @param[in] crypt The @ref mongocrypt_t object. + * + * @returns A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_status + */ +MONGOCRYPT_EXPORT +bool mongocrypt_setopt_use_range_v2(mongocrypt_t *crypt); + /** * Set the contention factor used for explicit encryption. * The contention factor is only used for indexed Queryable Encryption. @@ -1415,16 +1499,15 @@ MONGOCRYPT_EXPORT bool mongocrypt_ctx_setopt_query_type(mongocrypt_ctx_t *ctx, const char *query_type, int len); /** - * Set options for explicit encryption with the "rangePreview" algorithm. - * NOTE: The RangePreview algorithm is experimental only. It is not intended for - * public use. + * Set options for explicit encryption with the "range" algorithm. * * @p opts is a BSON document of the form: * { * "min": Optional, * "max": Optional, - * "sparsity": Int64, - * "precision": Optional + * "sparsity": Optional, + * "precision": Optional, + * "trimFactor": Optional * } * * @param[in] ctx The @ref mongocrypt_ctx_t object. @@ -1436,10 +1519,20 @@ bool mongocrypt_ctx_setopt_query_type(mongocrypt_ctx_t *ctx, const char *query_t MONGOCRYPT_EXPORT bool mongocrypt_ctx_setopt_algorithm_range(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *opts); +/** + * Set the expiration time for the data encryption key cache. Defaults to 60 seconds if not set. + * + * @param[in] ctx The @ref mongocrypt_ctx_t object. + * @param[in] cache_expiration_ms The cache expiration time in milliseconds. If zero, the cache + * never expires. + */ +MONGOCRYPT_EXPORT +bool mongocrypt_setopt_key_expiration(mongocrypt_t *crypt, uint64_t cache_expiration_ms); + /// String constants for setopt_query_type #define MONGOCRYPT_QUERY_TYPE_EQUALITY_STR "equality" -// NOTE: The RangePreview algorithm is experimental only. It is not intended for -// public use. -#define MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_STR "rangePreview" +// DEPRECATED: Support "rangePreview" has been removed in favor of "range". +#define MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_DEPRECATED_STR "rangePreview" +#define MONGOCRYPT_QUERY_TYPE_RANGE_STR "range" #endif /* MONGOCRYPT_H */ diff --git a/src/third_party/libmongocrypt/dist/src/os_posix/os_dll.c b/src/third_party/libmongocrypt/dist/src/os_posix/os_dll.c index 425f39fb241..21149e6ebde 100644 --- a/src/third_party/libmongocrypt/dist/src/os_posix/os_dll.c +++ b/src/third_party/libmongocrypt/dist/src/os_posix/os_dll.c @@ -95,26 +95,42 @@ mcr_dll_path_result mcr_dll_path(mcr_dll dll) { return (mcr_dll_path_result){.error_string = mstr_copy_cstr("Handle not found in loaded modules")}; } +bool mcr_dll_path_supported(void) { + return true; +} + #elif defined(__linux__) || defined(__FreeBSD__) #include mcr_dll_path_result mcr_dll_path(mcr_dll dll) { - struct link_map *map; + struct link_map *map = NULL; int rc = dlinfo(dll._native_handle, RTLD_DI_LINKMAP, &map); if (rc == 0) { + assert(NULL != map); return (mcr_dll_path_result){.path = mstr_copy_cstr(map->l_name)}; } else { return (mcr_dll_path_result){.error_string = mstr_copy_cstr(dlerror())}; } } +bool mcr_dll_path_supported(void) { + return true; +} + #elif defined(_WIN32) // Handled in os_win/os_dll.c #else -#error "Don't know how to do mcr_dll_path() on this platform" +mcr_dll_path_result mcr_dll_path(mcr_dll dll) { + return (mcr_dll_path_result){.error_string = + mstr_copy_cstr("Don't know how to do mcr_dll_path() on this platform")}; +} + +bool mcr_dll_path_supported(void) { + return false; +} #endif diff --git a/src/third_party/libmongocrypt/dist/src/os_win/os_dll.c b/src/third_party/libmongocrypt/dist/src/os_win/os_dll.c index b01179438c7..e6e4f1b5a0b 100644 --- a/src/third_party/libmongocrypt/dist/src/os_win/os_dll.c +++ b/src/third_party/libmongocrypt/dist/src/os_win/os_dll.c @@ -76,4 +76,8 @@ mcr_dll_path_result mcr_dll_path(mcr_dll dll) { }; } +bool mcr_dll_path_supported(void) { + return true; +} + #endif diff --git a/src/third_party/libmongocrypt/import.sh b/src/third_party/libmongocrypt/import.sh index 9988824ce0e..20357db1819 100755 --- a/src/third_party/libmongocrypt/import.sh +++ b/src/third_party/libmongocrypt/import.sh @@ -18,7 +18,7 @@ if grep -q Microsoft /proc/version; then fi NAME=libmongocrypt -VERSION=1.8.4 +VERSION=1.12.0 if grep -q Microsoft /proc/version; then SRC_ROOT=$(wslpath -u $(powershell.exe -Command "Get-ChildItem Env:TEMP | Get-Content | Write-Host"))