SERVER-96236 Update libmongocrypt to 1.12.0 (#28899)

GitOrigin-RevId: e2576facb200fbbe4ca03e22e147505c02d03f6a
This commit is contained in:
Joshua Siegel 2024-11-06 10:29:08 -06:00 committed by MongoDB Bot
parent da8da2d4cf
commit 7d32e888ed
93 changed files with 5470 additions and 1608 deletions

View File

@ -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 | | ✗ |

View File

@ -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",

View File

@ -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",

View File

@ -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

View File

@ -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,

View File

@ -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)
{

View File

@ -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);

View File

@ -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:
<RequestMessage tag="0x420078" type="Structure">
<RequestHeader tag="0x420077" type="Structure">
<ProtocolVersion tag="0x420069" type="Structure">
<ProtocolVersionMajor tag="0x42006a" type="Integer" value="1"/>
<ProtocolVersionMinor tag="0x42006b" type="Integer" value="2"/>
</ProtocolVersion>
<BatchCount tag="0x42000d" type="Integer" value="1"/>
</RequestHeader>
<BatchItem tag="0x42000f" type="Structure">
<Operation tag="0x42005c" type="Enumeration" value="1"/>
<RequestPayload tag="0x420079" type="Structure">
<ObjectType tag="0x420057" type="Enumeration" value="2"/>
<TemplateAttribute tag="0x420091" type="Structure">
<Attribute tag="0x420008" type="Structure">
<AttributeName tag="0x42000a" type="TextString" value="Cryptographic Algorithm"/>
<AttributeValue tag="0x42000b" type="Enumeration" value="3"/>
</Attribute>
<Attribute tag="0x420008" type="Structure">
<AttributeName tag="0x42000a" type="TextString" value="Cryptographic Length"/>
<AttributeValue tag="0x42000b" type="Integer" value="256"/>
</Attribute>
<Attribute tag="0x420008" type="Structure">
<AttributeName tag="0x42000a" type="TextString" value="Cryptographic
Usage Mask"/> <AttributeValue tag="0x42000b" type="Integer" value="12"/>
</Attribute>
</TemplateAttribute>
</RequestPayload>
</BatchItem>
</RequestMessage>
*/
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:
<RequestMessage tag="0x420078" type="Structure">
<RequestHeader tag="0x420077" type="Structure">
<ProtocolVersion tag="0x420069" type="Structure">
<ProtocolVersionMajor tag="0x42006a" type="Integer" value="1"/>
<ProtocolVersionMinor tag="0x42006b" type="Integer" value="2"/>
</ProtocolVersion>
<BatchCount tag="0x42000d" type="Integer" value="1"/>
</RequestHeader>
<BatchItem tag="0x42000f" type="Structure">
<Operation tag="0x42005c" type="Enumeration" value="31"/>
<RequestPayload tag="0x420079" type="Structure">
<UniqueIdentifier tag="0x420094" type="TextString" value="..."/>
<CryptographicParameters tag="0x42002b" type="Structure">
<BlockCipherMode tag="0x420011" type="Enumeration" value="1"/>
<PaddingMethod tag="0x42005f" type="Enumeration" value="3"/>
<CryptographicAlgorithm tag="0x420028" type="Enumeration" value="3"/>
<RandomIV tag="0x4200c5" type="Boolean" value="True"/>
</CryptographicParameters>
<Data tag="0x4200c2" type="ByteString" value="..."/>
</RequestPayload>
</BatchItem>
</RequestMessage>
*/
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:
<RequestMessage tag="0x420078" type="Structure">
<RequestHeader tag="0x420077" type="Structure">
<ProtocolVersion tag="0x420069" type="Structure">
<ProtocolVersionMajor tag="0x42006a" type="Integer" value="1"/>
<ProtocolVersionMinor tag="0x42006b" type="Integer" value="2"/>
</ProtocolVersion>
<BatchCount tag="0x42000d" type="Integer" value="1"/>
</RequestHeader>
<BatchItem tag="0x42000f" type="Structure">
<Operation tag="0x42005c" type="Enumeration" value="32"/>
<RequestPayload tag="0x420079" type="Structure">
<UniqueIdentifier tag="0x420094" type="TextString" value="..."/>
<CryptographicParameters tag="0x42002b" type="Structure">
<BlockCipherMode tag="0x420011" type="Enumeration" value="1"/>
<PaddingMethod tag="0x42005f" type="Enumeration" value="3"/>
<CryptographicAlgorithm tag="0x420028" type="Enumeration" value="3"/>
</CryptographicParameters>
<Data tag="0x4200c2" type="ByteString" value="..."/>
<IVCounterNonce tag="0x42003d" type="ByteString" value="..."/>
</RequestPayload>
</BatchItem>
</RequestMessage>
*/
return kmip_encrypt_decrypt(unique_identifer, ciphertext, len, iv_data, iv_len, false);
}

View File

@ -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:
<ResponseMessage tag="0x42007b" type="Structure">
<ResponseHeader tag="0x42007a" type="Structure">
<ProtocolVersion tag="0x420069" type="Structure">
<ProtocolVersionMajor tag="0x42006a" type="Integer" value="1"/>
<ProtocolVersionMinor tag="0x42006b" type="Integer" value="2"/>
</ProtocolVersion>
<TimeStamp tag="0x420092" type="DateTime" value="2021-10-12T14:09:25-0500"/>
<BatchCount tag="0x42000d" type="Integer" value="1"/>
</ResponseHeader>
<BatchItem tag="0x42000f" type="Structure">
<Operation tag="0x42005c" type="Enumeration" value="31"/>
<ResultStatus tag="0x42007f" type="Enumeration" value="0"/>
<ResponsePayload tag="0x42007c" type="Structure">
<UniqueIdentifier tag="0x420094" type="TextString" value="39"/>
<Data tag="0x4200c2" type="ByteString" value="..."/>
<IVCounterNonce tag="0x42003d" type="ByteString" value="..."/>
</ResponsePayload>
</BatchItem>
</ResponseMessage>
*/
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:
<ResponseMessage tag="0x42007b" type="Structure">
<ResponseHeader tag="0x42007a" type="Structure">
<ProtocolVersion tag="0x420069" type="Structure">
<ProtocolVersionMajor tag="0x42006a" type="Integer" value="1"/>
<ProtocolVersionMinor tag="0x42006b" type="Integer" value="2"/>
</ProtocolVersion>
<TimeStamp tag="0x420092" type="DateTime" value="2021-10-12T14:09:25-0500"/>
<BatchCount tag="0x42000d" type="Integer" value="1"/>
</ResponseHeader>
<BatchItem tag="0x42000f" type="Structure">
<Operation tag="0x42005c" type="Enumeration" value="32"/>
<ResultStatus tag="0x42007f" type="Enumeration" value="0"/>
<ResponsePayload tag="0x42007c" type="Structure">
<UniqueIdentifier tag="0x420094" type="TextString" value="39"/>
<Data tag="0x4200c2" type="ByteString" value="..."/>
</ResponsePayload>
</BatchItem>
</ResponseMessage>
*/
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:
<ResponseMessage tag="0x42007b" type="Structure">

View File

@ -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. */

View File

@ -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

View File

@ -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 */

View File

@ -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

View File

@ -18,7 +18,8 @@
#if defined(_WIN32)
#include <stdlib.h>
#include <string.h>
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
#endif

View File

@ -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;
}

View File

@ -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)) {

View File

@ -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)
{

View File

@ -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 */)) {

View File

@ -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 <bson/bson.h> /* ssize_t + BSON_CONCAT */
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
/* 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 */

View File

@ -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;

View File

@ -19,8 +19,19 @@
#include "mongocrypt-buffer-private.h"
#include <bson/bson.h>
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);

View File

@ -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;
}
}

View File

@ -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.
*/

View File

@ -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,

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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<EdgeFindTokenSetV2> // Array of Edges
* cm: <int64> // Queryable Encryption max counter
* payload: <document>
* g: array<EdgeFindTokenSetV2> // Array of Edges
* cm: <int64> // Queryable Encryption max counter
* payloadId: <int32> // Payload ID.
* firstOperator: <int32>
* secondOperator: <int32>
* sp: optional<int64> // Sparsity.
* pn: optional<int32> // Precision.
* tf: optional<int32> // Trim Factor.
* mn: optional<any> // Index Min.
* mx: optional<any> // 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: <binary> // EDCDerivedFromDataTokenAndCounter
* s: <binary> // ESCDerivedFromDataTokenAndCounter
* d: <binary> // EDCDerivedFromDataTokenAndContentionFactor
* s: <binary> // ESCDerivedFromDataTokenAndContentionFactor
* l: <binary> // 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);

View File

@ -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<EdgeFindTokenSet> // Array of Edges
* e: <binary> // ServerDataEncryptionLevel1Token
* cm: <int64> // Queryable Encryption max counter
* cm: <int64> // Queryable Encryption max contentionFactor
*/
typedef struct {
struct {
@ -69,9 +69,9 @@ typedef struct {
/**
* EdgeFindTokenSet is the following BSON document:
* d: <binary> // EDCDerivedFromDataTokenAndCounter
* s: <binary> // ESCDerivedFromDataTokenAndCounter
* c: <binary> // ECCDerivedFromDataTokenAndCounter
* d: <binary> // EDCDerivedFromDataTokenAndContentionFactor
* s: <binary> // ESCDerivedFromDataTokenAndContentionFactor
* c: <binary> // ECCDerivedFromDataTokenAndContentionFactor
*
* Instances of mc_EdgeFindTokenSet_t are expected to be owned by
* mc_FLE2FindRangePayload_t and are freed in

View File

@ -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;
}

View File

@ -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;
}

View File

@ -20,6 +20,7 @@
#include <bson/bson.h>
#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: <binary> // EDCDerivedFromDataTokenAndCounter
* s: <binary> // ESCDerivedFromDataTokenAndCounter
* d: <binary> // EDCDerivedFromDataTokenAndContentionFactor
* s: <binary> // ESCDerivedFromDataTokenAndContentionFactor
* p: <binary> // Encrypted Tokens
* u: <UUID> // Index KeyId
* t: <int32> // Encrypted type
@ -45,13 +46,18 @@
* e: <binary> // ServerDataEncryptionLevel1Token
* l: <binary> // ServerDerivedFromDataToken
* k: <int64> // Randomly sampled contention factor value
* g: array<EdgeTokenSetV2> // Array of Edges
* g: array<EdgeTokenSetV2> // Array of Edges. Only included for range payloads.
* sp: optional<int64> // Sparsity. Only included for range payloads.
* pn: optional<int32> // Precision. Only included for range payloads.
* tf: optional<int32> // Trim Factor. Only included for range payloads.
* mn: optional<any> // Index Min. Only included for range payloads.
* mx: optional<any> // 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: <binary> // EDCDerivedFromDataTokenAndCounter
* s: <binary> // ESCDerivedFromDataTokenAndCounter
* d: <binary> // EDCDerivedFromDataTokenAndContentionFactor
* s: <binary> // ESCDerivedFromDataTokenAndContentionFactor
* l: <binary> // ServerDerivedFromDataToken
* p: <binary> // 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);

View File

@ -36,9 +36,9 @@
* } FLE2InsertUpdatePayload;
*
* bson is a BSON document of this form:
* d: <binary> // EDCDerivedFromDataTokenAndCounter
* s: <binary> // ESCDerivedFromDataTokenAndCounter
* c: <binary> // ECCDerivedFromDataTokenAndCounter
* d: <binary> // EDCDerivedFromDataTokenAndContentionFactor
* s: <binary> // ESCDerivedFromDataTokenAndContentionFactor
* c: <binary> // ECCDerivedFromDataTokenAndContentionFactor
* p: <binary> // Encrypted Tokens
* u: <UUID> // Index KeyId
* t: <int32> // 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: <binary> // EDCDerivedFromDataTokenAndCounter
* s: <binary> // ESCDerivedFromDataTokenAndCounter
* c: <binary> // ECCDerivedFromDataTokenAndCounter
* d: <binary> // EDCDerivedFromDataTokenAndContentionFactor
* s: <binary> // ESCDerivedFromDataTokenAndContentionFactor
* c: <binary> // ECCDerivedFromDataTokenAndContentionFactor
* p: <binary> // Encrypted Tokens
*
* Instances of mc_EdgeTokenSet_t are expected to be owned by

View File

@ -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;
}

View File

@ -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 */

View File

@ -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;
}

View File

@ -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__);

View File

@ -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.

View File

@ -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.

View File

@ -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 */

View File

@ -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;
}

View File

@ -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;

View File

@ -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));

View File

@ -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
#endif // MONGOCRYPT_HAVE_DECIMAL128_SUPPORT

View File

@ -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 */

View File

@ -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;
}
}

View File

@ -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

View File

@ -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 */

View File

@ -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;
}

View File

@ -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<Int32>
* "sparsity": Optional<Int64>,
* "precision": Optional<Int32>,
* "trimFactor": Optional<Int32>,
* }
*/
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

View File

@ -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 <float.h> // 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;

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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 */

View File

@ -19,6 +19,11 @@
#include "mongocrypt-util-private.h"
#include <bson/bson.h>
// 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);

View File

@ -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;
}

View File

@ -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,

View File

@ -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 */
#endif /* MONGOCRYPT_CACHE_OAUTH_PRIVATE_H */

View File

@ -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;
}

View File

@ -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 */
#endif /* MONGOCRYPT_CACHE_PRIVATE */

View File

@ -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. */

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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));

File diff suppressed because it is too large Load Diff

View File

@ -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 "<target_db>.<target_coll>" 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. <db_name>.<coll_name> 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;

View File

@ -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);
}

View File

@ -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

View File

@ -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 */
#endif /* MONGOCRYPT_KEK_PRIVATE_H */

View File

@ -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;
}

View File

@ -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);

View File

@ -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. */

View File

@ -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,

View File

@ -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);

View File

@ -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 */

View File

@ -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 <bson/bson.h>
#include <kms_message/kms_azure_request.h>
#include <kms_message/kms_b64.h>
#include <kms_message/kms_gcp_request.h>
@ -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);
}

View File

@ -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) {

View File

@ -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 */

View File

@ -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<EdgeTokenSet>
{
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<EdgeTokenSetV2>
{
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<EdgeFindTokenSet>
{
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<EdgeFindTokenSet>
{
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);

View File

@ -26,6 +26,7 @@
#include "mongocrypt-kek-private.h"
#include "mongocrypt-log-private.h"
#include "mongocrypt.h"
#include <mc-array-private.h>
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 */

View File

@ -19,12 +19,24 @@
#include "mongocrypt-log-private.h"
#include "mongocrypt-opts-private.h"
#include "mongocrypt-private.h"
#include <mongocrypt-util-private.h> // mc_iter_document_as_bson
#include <kms_message/kms_b64.h>
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 `<type>` or `<type>:<name>`. 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>`.
type_nchars = strlen(kmsid);
} else {
// Parse `kmsid` as `<type>:<name>`.
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 `<provider "
"type>:<name>` where `<name>` 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;
}

View File

@ -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.

View File

@ -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};
}

View File

@ -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": <required UTF-8>
* }
*/
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": <required UTF-8>
* "privateKey": <required UTF-8 or Binary>
* }
*/
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;
}

View File

@ -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<BSON value>,
* "max": Optional<BSON value>,
* "sparsity": Int64,
* "precision": Optional<Int32>
* "sparsity": Optional<Int64>,
* "precision": Optional<Int32>,
* "trimFactor": Optional<Int32>
* }
*
* @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 */

View File

@ -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 <link.h>
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

View File

@ -76,4 +76,8 @@ mcr_dll_path_result mcr_dll_path(mcr_dll dll) {
};
}
bool mcr_dll_path_supported(void) {
return true;
}
#endif

View File

@ -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"))