Page MenuHomePhabricator (Chris)

mongocrypt-crypto.c
No OneTemporary

Authored By
Unknown
Size
56 KB
Referenced Files
None
Subscribers
None

mongocrypt-crypto.c

/*
* Copyright 2019-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.
*/
/*
* Comments in this implementation refer to:
* [MCGREW] https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05
*/
#include <bson/bson.h>
#include "mongocrypt-binary-private.h"
#include "mongocrypt-buffer-private.h"
#include "mongocrypt-crypto-private.h"
#include "mongocrypt-log-private.h"
#include "mongocrypt-private.h"
#include "mongocrypt-status-private.h"
#include <inttypes.h>
/* This function uses ECB callback to simulate CTR encrypt and decrypt
*
* Note: the same function performs both encrypt and decrypt using same ECB
* encryption function
*/
static bool _crypto_aes_256_ctr_encrypt_decrypt_via_ecb(void *ctx,
mongocrypt_crypto_fn aes_256_ecb_encrypt,
aes_256_args_t args,
mongocrypt_status_t *status) {
BSON_ASSERT(args.iv && args.iv->len);
BSON_ASSERT(args.in);
BSON_ASSERT(args.out);
if (args.out->len < args.in->len) {
CLIENT_ERR("output buffer too small");
return false;
}
_mongocrypt_buffer_t ctr, tmp;
mongocrypt_binary_t key_bin, out_bin, in_bin, ctr_bin, tmp_bin;
bool ret;
_mongocrypt_buffer_to_binary(args.key, &key_bin);
_mongocrypt_buffer_init(&ctr);
_mongocrypt_buffer_copy_to(args.iv, &ctr);
_mongocrypt_buffer_to_binary(&ctr, &ctr_bin);
_mongocrypt_buffer_to_binary(args.out, &out_bin);
_mongocrypt_buffer_to_binary(args.in, &in_bin);
_mongocrypt_buffer_init_size(&tmp, args.iv->len);
_mongocrypt_buffer_to_binary(&tmp, &tmp_bin);
for (uint32_t ptr = 0; ptr < args.in->len;) {
/* Encrypt value in CTR buffer */
uint32_t bytes_written = 0;
if (!aes_256_ecb_encrypt(ctx, &key_bin, NULL, &ctr_bin, &tmp_bin, &bytes_written, status)) {
ret = false;
goto cleanup;
}
if (bytes_written != tmp_bin.len) {
CLIENT_ERR("encryption hook returned unexpected length");
ret = false;
goto cleanup;
}
/* XOR resulting stream with original data */
for (uint32_t i = 0; i < bytes_written && ptr < args.in->len; i++, ptr++) {
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 */
uint32_t carry = 1;
/* 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) {
uint8_t *ctr_bin_u8 = ctr_bin.data;
uint32_t bpp = carry + ctr_bin_u8[i];
carry = bpp >> 8;
ctr_bin_u8[i] = bpp & 0xFF;
}
}
if (args.bytes_written) {
*args.bytes_written = args.in->len;
}
ret = true;
cleanup:
_mongocrypt_buffer_cleanup(&ctr);
_mongocrypt_buffer_cleanup(&tmp);
return ret;
}
/* Crypto primitives. These either call the native built in crypto primitives or
* user supplied hooks. */
static bool _crypto_aes_256_cbc_encrypt(_mongocrypt_crypto_t *crypto, aes_256_args_t args) {
mongocrypt_status_t *status = args.status;
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT(args.key);
if (args.key->len != MONGOCRYPT_ENC_KEY_LEN) {
CLIENT_ERR("invalid encryption key length");
return false;
}
BSON_ASSERT(args.iv);
if (args.iv->len != MONGOCRYPT_IV_LEN) {
CLIENT_ERR("invalid iv length");
return false;
}
if (crypto->hooks_enabled) {
mongocrypt_binary_t enc_key_bin, iv_bin, out_bin, in_bin;
bool ret;
_mongocrypt_buffer_to_binary(args.key, &enc_key_bin);
_mongocrypt_buffer_to_binary(args.iv, &iv_bin);
_mongocrypt_buffer_to_binary(args.out, &out_bin);
_mongocrypt_buffer_to_binary(args.in, &in_bin);
ret = crypto->aes_256_cbc_encrypt(crypto->ctx,
&enc_key_bin,
&iv_bin,
&in_bin,
&out_bin,
args.bytes_written,
status);
return ret;
}
return _native_crypto_aes_256_cbc_encrypt(args);
}
static bool _crypto_aes_256_ctr_encrypt(_mongocrypt_crypto_t *crypto, aes_256_args_t args) {
mongocrypt_status_t *status = args.status;
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT(args.key);
if (args.key->len != MONGOCRYPT_ENC_KEY_LEN) {
CLIENT_ERR("invalid encryption key length");
return false;
}
BSON_ASSERT(args.iv);
if (args.iv->len != MONGOCRYPT_IV_LEN) {
CLIENT_ERR("invalid iv length");
return false;
}
if (crypto->aes_256_ctr_encrypt) {
mongocrypt_binary_t enc_key_bin, iv_bin, out_bin, in_bin;
bool ret;
_mongocrypt_buffer_to_binary(args.key, &enc_key_bin);
_mongocrypt_buffer_to_binary(args.iv, &iv_bin);
_mongocrypt_buffer_to_binary(args.out, &out_bin);
_mongocrypt_buffer_to_binary(args.in, &in_bin);
ret = crypto->aes_256_ctr_encrypt(crypto->ctx,
&enc_key_bin,
&iv_bin,
&in_bin,
&out_bin,
args.bytes_written,
status);
return ret;
}
if (crypto->aes_256_ecb_encrypt) {
return _crypto_aes_256_ctr_encrypt_decrypt_via_ecb(crypto->ctx, crypto->aes_256_ecb_encrypt, args, status);
}
return _native_crypto_aes_256_ctr_encrypt(args);
}
static bool _crypto_aes_256_cbc_decrypt(_mongocrypt_crypto_t *crypto, aes_256_args_t args) {
mongocrypt_status_t *status = args.status;
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT(args.key);
if (args.key->len != MONGOCRYPT_ENC_KEY_LEN) {
CLIENT_ERR("invalid encryption key length");
return false;
}
if (crypto->hooks_enabled) {
mongocrypt_binary_t enc_key_bin, iv_bin, out_bin, in_bin;
bool ret;
_mongocrypt_buffer_to_binary(args.key, &enc_key_bin);
_mongocrypt_buffer_to_binary(args.iv, &iv_bin);
_mongocrypt_buffer_to_binary(args.out, &out_bin);
_mongocrypt_buffer_to_binary(args.in, &in_bin);
ret = crypto->aes_256_cbc_decrypt(crypto->ctx,
&enc_key_bin,
&iv_bin,
&in_bin,
&out_bin,
args.bytes_written,
status);
return ret;
}
return _native_crypto_aes_256_cbc_decrypt(args);
}
static bool _crypto_aes_256_ctr_decrypt(_mongocrypt_crypto_t *crypto, aes_256_args_t args) {
mongocrypt_status_t *status = args.status;
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT(args.key);
if (args.key->len != MONGOCRYPT_ENC_KEY_LEN) {
CLIENT_ERR("invalid encryption key length");
return false;
}
if (crypto->aes_256_ctr_decrypt) {
mongocrypt_binary_t enc_key_bin, iv_bin, out_bin, in_bin;
bool ret;
_mongocrypt_buffer_to_binary(args.key, &enc_key_bin);
_mongocrypt_buffer_to_binary(args.iv, &iv_bin);
_mongocrypt_buffer_to_binary(args.out, &out_bin);
_mongocrypt_buffer_to_binary(args.in, &in_bin);
ret = crypto->aes_256_ctr_decrypt(crypto->ctx,
&enc_key_bin,
&iv_bin,
&in_bin,
&out_bin,
args.bytes_written,
status);
return ret;
}
if (crypto->aes_256_ecb_encrypt) {
return _crypto_aes_256_ctr_encrypt_decrypt_via_ecb(crypto->ctx, crypto->aes_256_ecb_encrypt, args, status);
}
return _native_crypto_aes_256_ctr_decrypt(args);
}
static bool _crypto_hmac_sha_512(_mongocrypt_crypto_t *crypto,
const _mongocrypt_buffer_t *hmac_key,
const _mongocrypt_buffer_t *in,
_mongocrypt_buffer_t *out,
mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(hmac_key);
BSON_ASSERT_PARAM(in);
BSON_ASSERT_PARAM(out);
if (hmac_key->len != MONGOCRYPT_MAC_KEY_LEN) {
CLIENT_ERR("invalid hmac key length");
return false;
}
if (out->len != MONGOCRYPT_HMAC_SHA512_LEN) {
CLIENT_ERR("out does not contain %d bytes", MONGOCRYPT_HMAC_SHA512_LEN);
return false;
}
if (crypto->hooks_enabled) {
mongocrypt_binary_t hmac_key_bin, out_bin, in_bin;
bool ret;
_mongocrypt_buffer_to_binary(hmac_key, &hmac_key_bin);
_mongocrypt_buffer_to_binary(out, &out_bin);
_mongocrypt_buffer_to_binary(in, &in_bin);
ret = crypto->hmac_sha_512(crypto->ctx, &hmac_key_bin, &in_bin, &out_bin, status);
return ret;
}
return _native_crypto_hmac_sha_512(hmac_key, in, out, status);
}
bool _mongocrypt_hmac_sha_256(_mongocrypt_crypto_t *crypto,
const _mongocrypt_buffer_t *key,
const _mongocrypt_buffer_t *in,
_mongocrypt_buffer_t *out,
mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(key);
BSON_ASSERT_PARAM(in);
BSON_ASSERT_PARAM(out);
if (key->len != MONGOCRYPT_MAC_KEY_LEN) {
CLIENT_ERR("invalid hmac_sha_256 key length. Got %" PRIu32 ", expected: %d", key->len, MONGOCRYPT_MAC_KEY_LEN);
return false;
}
if (crypto->hooks_enabled) {
mongocrypt_binary_t key_bin, out_bin, in_bin;
_mongocrypt_buffer_to_binary(key, &key_bin);
_mongocrypt_buffer_to_binary(out, &out_bin);
_mongocrypt_buffer_to_binary(in, &in_bin);
return crypto->hmac_sha_256(crypto->ctx, &key_bin, &in_bin, &out_bin, status);
}
return _native_crypto_hmac_sha_256(key, in, out, status);
}
static bool
_crypto_random(_mongocrypt_crypto_t *crypto, _mongocrypt_buffer_t *out, uint32_t count, mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(out);
if (out->len != count) {
CLIENT_ERR("out does not contain %u bytes", count);
return false;
}
if (crypto->hooks_enabled) {
mongocrypt_binary_t out_bin;
_mongocrypt_buffer_to_binary(out, &out_bin);
return crypto->random(crypto->ctx, &out_bin, count, status);
}
return _native_crypto_random(out, count, status);
}
/*
* Secure memcmp copied from the C driver.
*/
int _mongocrypt_memequal(const void *const b1, const void *const b2, size_t len) {
const unsigned char *p1 = b1, *p2 = b2;
int ret = 0;
BSON_ASSERT_PARAM(b1);
BSON_ASSERT_PARAM(b2);
for (; len > 0; len--) {
ret |= *p1++ ^ *p2++;
}
return ret;
}
typedef enum {
MODE_CBC,
MODE_CTR,
} _mongocrypt_encryption_mode_t;
typedef enum {
HMAC_NONE,
HMAC_SHA_512_256, // sha512 truncated to 256 bits
HMAC_SHA_256,
} _mongocrypt_hmac_type_t;
typedef enum {
KEY_FORMAT_FLE1, // 32 octets MAC key, 32 DATA key, 32 IV key (ignored)
KEY_FORMAT_FLE2, // 32 octets DATA key
KEY_FORMAT_FLE2AEAD, // 32 octets DATA key, 32 MAC key, 32 IV key (ignored)
KEY_FORMAT_FLE2v2AEAD, // 32 octets DATA key, 32 MAC key, 32 IV key (ignored)
} _mongocrypt_key_format_t;
typedef enum {
MAC_FORMAT_FLE1, // HMAC(AAD || IV || S || LEN(AAD) as uint64be)
MAC_FORMAT_FLE2, // NONE
MAC_FORMAT_FLE2AEAD, // HMAC(AAD || IV || S)
MAC_FORMAT_FLE2v2AEAD, // HMAC(AAD || IV || S)
} _mongocrypt_mac_format_t;
/* ----------------------------------------------------------------------------
*
* _mongocrypt_calculate_ciphertext_len
*
* Calculate the space needed for a ciphertext payload of a given size
* and using fixed iv/hmac lengths.
*
* MODE_CBC: Assumes the ciphertext will be padded according to PKCS#7
* which rounds up to the next block size, adding up to a complete block
* for block aligned input payloads.
*
* MODE_CTR: Assumes no additional padding since CTR is a streaming cipher.
*
* Assumes all algorithms use identical IV length and blocksizes.
*
* ----------------------------------------------------------------------------
*/
static uint32_t _mongocrypt_calculate_ciphertext_len(uint32_t inlen,
_mongocrypt_encryption_mode_t mode,
_mongocrypt_hmac_type_t hmac,
mongocrypt_status_t *status) {
const uint32_t hmaclen = (hmac == HMAC_NONE) ? 0 : MONGOCRYPT_HMAC_LEN;
const uint32_t maxinlen = UINT32_MAX - (MONGOCRYPT_IV_LEN + MONGOCRYPT_BLOCK_SIZE + hmaclen);
uint32_t fill;
if (inlen > maxinlen) {
CLIENT_ERR("plaintext too long");
return 0;
}
if (mode == MODE_CBC) {
fill = MONGOCRYPT_BLOCK_SIZE - (inlen % MONGOCRYPT_BLOCK_SIZE);
} else {
BSON_ASSERT(mode == MODE_CTR);
fill = 0;
}
return MONGOCRYPT_IV_LEN + inlen + fill + hmaclen;
}
/* ----------------------------------------------------------------------------
*
* _mongocrypt_calculate_plaintext_len
*
* Calculate the space needed for a plaintext payload of a given size
* and using fixed iv/hmac lengths.
*
* MODE_CBC: In practice, plaintext will be between 1 and {blocksize} bytes
* shorter
* than the input ciphertext, but it's easier and safer to assume the
* full ciphertext length and waste a few bytes.
*
* MODE_CTR: Assumes no additional padding since CTR is a streaming cipher.
*
* Assumes all algorithms use identical IV length and blocksizes.
*
* ----------------------------------------------------------------------------
*/
static uint32_t _mongocrypt_calculate_plaintext_len(uint32_t inlen,
_mongocrypt_encryption_mode_t mode,
_mongocrypt_hmac_type_t hmac,
mongocrypt_status_t *status) {
const uint32_t hmaclen = (hmac == HMAC_NONE) ? 0 : MONGOCRYPT_HMAC_LEN;
const uint32_t mincipher = (mode == MODE_CTR) ? 1 : MONGOCRYPT_BLOCK_SIZE;
if (inlen < (MONGOCRYPT_IV_LEN + mincipher + hmaclen)) {
CLIENT_ERR("input ciphertext too small. Must be more than %" PRIu32 " bytes",
MONGOCRYPT_IV_LEN + mincipher + hmaclen);
return 0;
}
return inlen - (MONGOCRYPT_IV_LEN + hmaclen);
}
/* ----------------------------------------------------------------------------
*
* _aes256_cbc_encrypt --
*
* Encrypts using AES256 CBC using a secret key and a known IV.
*
* Parameters:
* @iv a 16 byte IV.
* @enc_key a 32 byte key.
* @plaintext the plaintext to encrypt.
* @ciphertext the resulting ciphertext.
* @bytes_written a location for the resulting number of bytes written into
* ciphertext->data.
* @status set on error.
*
* Returns:
* True on success. On error, sets @status and returns false.
*
* Preconditions:
* 1. ciphertext->data has been pre-allocated with enough space for the
* resulting ciphertext.
*
* Postconditions:
* 1. bytes_written is set to the length of the written ciphertext. This
* is the same as
* _mongocrypt_calculate_ciphertext_len (plaintext->len, mode, hmac, status).
*
* ----------------------------------------------------------------------------
*/
static bool _encrypt_step(_mongocrypt_crypto_t *crypto,
_mongocrypt_encryption_mode_t mode,
const _mongocrypt_buffer_t *iv,
const _mongocrypt_buffer_t *enc_key,
const _mongocrypt_buffer_t *plaintext,
_mongocrypt_buffer_t *ciphertext,
uint32_t *bytes_written,
mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(iv);
BSON_ASSERT_PARAM(enc_key);
BSON_ASSERT_PARAM(plaintext);
BSON_ASSERT_PARAM(ciphertext);
BSON_ASSERT_PARAM(bytes_written);
*bytes_written = 0;
if (MONGOCRYPT_IV_LEN != iv->len) {
CLIENT_ERR("IV should have length %d, but has length %" PRIu32, MONGOCRYPT_IV_LEN, iv->len);
return false;
}
if (MONGOCRYPT_ENC_KEY_LEN != enc_key->len) {
CLIENT_ERR("Encryption key should have length %d, but has length %" PRIu32,
MONGOCRYPT_ENC_KEY_LEN,
enc_key->len);
return false;
}
if (mode == MODE_CTR) {
// Streaming cipher, no padding required.
return _crypto_aes_256_ctr_encrypt(crypto,
(aes_256_args_t){.key = enc_key,
.iv = iv,
.in = plaintext,
.out = ciphertext,
.bytes_written = bytes_written,
.status = status});
}
BSON_ASSERT(mode == MODE_CBC);
/* calculate how many extra bytes there are after a block boundary */
const uint32_t unaligned = plaintext->len % MONGOCRYPT_BLOCK_SIZE;
uint32_t padding_byte = MONGOCRYPT_BLOCK_SIZE - unaligned;
_mongocrypt_buffer_t intermediates[2], to_encrypt;
uint8_t final_block_storage[MONGOCRYPT_BLOCK_SIZE];
bool ret;
BSON_ASSERT(MONGOCRYPT_BLOCK_SIZE >= unaligned);
/* Some crypto providers disallow variable length inputs, and require
* the input to be a multiple of the block size. So add everything up
* to but excluding the last block if not block aligned, then add
* the last block with padding. */
_mongocrypt_buffer_init(&intermediates[0]);
_mongocrypt_buffer_init(&intermediates[1]);
intermediates[0].data = (uint8_t *)plaintext->data;
/* don't check plaintext->len, as the above modulo operation guarantees
* that unaligned will be smaller */
intermediates[0].len = plaintext->len - unaligned;
intermediates[1].data = final_block_storage;
intermediates[1].len = sizeof(final_block_storage);
/* [MCGREW]: "Prior to CBC encryption, the plaintext P is padded by appending
* a padding string PS to that data, to ensure that len(P || PS) is a
* multiple of 128". This is also known as PKCS #7 padding. */
if (unaligned) {
/* Copy the unaligned bytes. */
memcpy(intermediates[1].data, plaintext->data + (plaintext->len - unaligned), unaligned);
}
/* Fill out block remained or whole block with padding_byte */
memset(intermediates[1].data + unaligned, (int)padding_byte, padding_byte);
_mongocrypt_buffer_init(&to_encrypt);
if (!_mongocrypt_buffer_concat(&to_encrypt, intermediates, 2)) {
CLIENT_ERR("failed to allocate buffer");
_mongocrypt_buffer_cleanup(&to_encrypt);
return false;
}
ret = _crypto_aes_256_cbc_encrypt(crypto,
(aes_256_args_t){.key = enc_key,
.iv = iv,
.in = &to_encrypt,
.out = ciphertext,
.bytes_written = bytes_written,
.status = status});
_mongocrypt_buffer_cleanup(&to_encrypt);
if (!ret) {
return false;
}
if (*bytes_written % MONGOCRYPT_BLOCK_SIZE != 0) {
CLIENT_ERR("encryption failure, wrote %" PRIu32 " bytes, not a multiple of %d",
*bytes_written,
MONGOCRYPT_BLOCK_SIZE);
return false;
}
return true;
}
/* ----------------------------------------------------------------------------
*
* _hmac_step --
*
* Compute the selected HMAC with a secret key.
*
* Parameters:
* @Km a 32 byte key.
* @AAD associated data to add into the HMAC. This may be
* an empty buffer.
* @iv_and_ciphertext the IV and S components to add into the HMAC.
* @out a location for the resulting HMAC tag.
* @status set on error.
*
* Returns:
* True on success. On error, sets @status and returns false.
*
* Preconditions:
* 1. out->data has been pre-allocated with at least 64 bytes.
*
* Postconditions:
* 1. out->data will have a 64 byte tag appended.
*
* ----------------------------------------------------------------------------
*/
static bool _hmac_step(_mongocrypt_crypto_t *crypto,
_mongocrypt_mac_format_t mac_format,
_mongocrypt_hmac_type_t hmac,
const _mongocrypt_buffer_t *Km,
const _mongocrypt_buffer_t *AAD,
const _mongocrypt_buffer_t *iv_and_ciphertext,
_mongocrypt_buffer_t *out,
mongocrypt_status_t *status) {
_mongocrypt_buffer_t to_hmac = {0};
bool ret = false;
BSON_ASSERT(hmac != HMAC_NONE);
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(Km);
// AAD may be NULL
BSON_ASSERT_PARAM(iv_and_ciphertext);
BSON_ASSERT_PARAM(out);
_mongocrypt_buffer_init(&to_hmac);
if (MONGOCRYPT_MAC_KEY_LEN != Km->len) {
CLIENT_ERR("HMAC key wrong length: %" PRIu32, Km->len);
goto done;
}
if (out->len != MONGOCRYPT_HMAC_LEN) {
CLIENT_ERR("out wrong length: %" PRIu32, out->len);
goto done;
}
/* Construct the input to the HMAC */
uint32_t num_intermediates = 0;
_mongocrypt_buffer_t intermediates[3];
if (AAD && !_mongocrypt_buffer_from_subrange(&intermediates[num_intermediates++], AAD, 0, AAD->len)) {
CLIENT_ERR("Failed creating MAC subrange on AD");
goto done;
}
if (!_mongocrypt_buffer_from_subrange(&intermediates[num_intermediates++],
iv_and_ciphertext,
0,
iv_and_ciphertext->len)) {
CLIENT_ERR("Failed creating MAC subrange on IV and S");
goto done;
}
// {AL} must be stored in the function's lexical scope so that
// {intermediates}'s reference to it survives until the
// _mongocrypt_buffer_concat operation later.
uint64_t AL;
if (mac_format == MAC_FORMAT_FLE1) {
/* T := HMAC(AAD || IV || S || AL)
* AL is equal to the number of bits in AAD expressed
* as a 64bit unsigned big-endian integer.
* Multiplying a uint32_t by 8 won't bring it anywhere close to
* UINT64_MAX.
*/
AL = AAD ? BSON_UINT64_TO_BE(8 * (uint64_t)AAD->len) : 0;
_mongocrypt_buffer_init(&intermediates[num_intermediates]);
intermediates[num_intermediates].data = (uint8_t *)&AL;
intermediates[num_intermediates++].len = sizeof(uint64_t);
} else {
/* T := HMAC(AAD || IV || S) */
BSON_ASSERT((mac_format == MAC_FORMAT_FLE2AEAD) || (mac_format == MAC_FORMAT_FLE2v2AEAD));
}
if (!_mongocrypt_buffer_concat(&to_hmac, intermediates, num_intermediates)) {
CLIENT_ERR("failed to allocate buffer");
goto done;
}
if (hmac == HMAC_SHA_512_256) {
uint8_t storage[64];
_mongocrypt_buffer_t tag = {.data = storage, .len = sizeof(storage)};
if (!_crypto_hmac_sha_512(crypto, Km, &to_hmac, &tag, status)) {
goto done;
}
// Truncate sha512 to first 256 bits.
memcpy(out->data, tag.data, MONGOCRYPT_HMAC_LEN);
} else {
BSON_ASSERT(hmac == HMAC_SHA_256);
if (!_mongocrypt_hmac_sha_256(crypto, Km, &to_hmac, out, status)) {
goto done;
}
}
ret = true;
done:
_mongocrypt_buffer_cleanup(&to_hmac);
return ret;
}
/* ----------------------------------------------------------------------------
*
* _mongocrypt_do_encryption --
*
* Defer encryption to whichever crypto library libmongocrypt is using.
*
* Parameters:
* @iv a 16 byte IV.
* @associated_data associated data for the HMAC. May be NULL.
* @key is the encryption key. The size depends on @key_format.
* @plaintext the plaintext to encrypt.
* @ciphertext a location for the resulting ciphertext and HMAC tag.
* @bytes_written a location for the resulting bytes written.
* @status set on error.
*
* Returns:
* True on success. On error, sets @status and returns false.
*
* Preconditions:
* 1. ciphertext->data has been pre-allocated with enough space for the
* resulting ciphertext. Use _mongocrypt_calculate_ciphertext_len.
*
* Postconditions:
* 1. bytes_written is set to the length of the written ciphertext. This
* is the same as
* _mongocrypt_calculate_ciphertext_len (plaintext->len, mode, hmac, status).
*
* ----------------------------------------------------------------------------
*/
static bool _mongocrypt_do_encryption(_mongocrypt_crypto_t *crypto,
_mongocrypt_key_format_t key_format,
_mongocrypt_mac_format_t mac_format,
_mongocrypt_encryption_mode_t mode,
_mongocrypt_hmac_type_t hmac,
const _mongocrypt_buffer_t *iv,
const _mongocrypt_buffer_t *associated_data,
const _mongocrypt_buffer_t *key,
const _mongocrypt_buffer_t *plaintext,
_mongocrypt_buffer_t *ciphertext,
uint32_t *bytes_written,
mongocrypt_status_t *status) {
_mongocrypt_buffer_t Ke = {0}; // Ke == Key for Encryption
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(iv);
/* associated_data is checked at the point it is used, so it can be NULL */
BSON_ASSERT_PARAM(key);
BSON_ASSERT_PARAM(plaintext);
BSON_ASSERT_PARAM(ciphertext);
if (plaintext->len <= 0) {
CLIENT_ERR("input plaintext too small. Must be more than zero bytes.");
return false;
}
const uint32_t expect_ciphertext_len = _mongocrypt_calculate_ciphertext_len(plaintext->len, mode, hmac, status);
if (mongocrypt_status_type(status) != MONGOCRYPT_STATUS_OK) {
return false;
}
if (expect_ciphertext_len != ciphertext->len) {
CLIENT_ERR("output ciphertext should have been allocated with %" PRIu32 " bytes", expect_ciphertext_len);
return false;
}
if (MONGOCRYPT_IV_LEN != iv->len) {
CLIENT_ERR("IV should have length %d, but has length %" PRIu32, MONGOCRYPT_IV_LEN, iv->len);
return false;
}
const uint32_t expected_key_len = (key_format == KEY_FORMAT_FLE2) ? MONGOCRYPT_ENC_KEY_LEN : MONGOCRYPT_KEY_LEN;
if (key->len != expected_key_len) {
CLIENT_ERR("key should have length %" PRIu32 ", but has length %" PRIu32, expected_key_len, key->len);
return false;
}
// Copy IV into the output, and clear remainder.
memmove(ciphertext->data, iv->data, MONGOCRYPT_IV_LEN);
memset(ciphertext->data + MONGOCRYPT_IV_LEN, 0, ciphertext->len - MONGOCRYPT_IV_LEN);
// S is the encryption payload without IV or HMAC
_mongocrypt_buffer_t S;
if (!_mongocrypt_buffer_from_subrange(&S, ciphertext, MONGOCRYPT_IV_LEN, ciphertext->len - MONGOCRYPT_IV_LEN)) {
CLIENT_ERR("unable to create S subrange from C");
return false;
}
if (hmac != HMAC_NONE) {
S.len -= MONGOCRYPT_HMAC_LEN;
}
// Ke is the key used for payload encryption
const uint32_t Ke_offset = (key_format == KEY_FORMAT_FLE1) ? MONGOCRYPT_MAC_KEY_LEN : 0;
if (!_mongocrypt_buffer_from_subrange(&Ke, key, Ke_offset, MONGOCRYPT_ENC_KEY_LEN)) {
CLIENT_ERR("unable to create Ke subrange from key");
return false;
}
uint32_t S_bytes_written = 0;
if (!_encrypt_step(crypto, mode, iv, &Ke, plaintext, &S, &S_bytes_written, status)) {
return false;
}
BSON_ASSERT_PARAM(bytes_written);
BSON_ASSERT((UINT32_MAX - S_bytes_written) > MONGOCRYPT_IV_LEN);
*bytes_written = MONGOCRYPT_IV_LEN + S_bytes_written;
if (hmac != HMAC_NONE) {
// Km == Key for MAC
const uint32_t Km_offset = (key_format == KEY_FORMAT_FLE1) ? 0 : MONGOCRYPT_ENC_KEY_LEN;
// Km is the HMAC Key.
_mongocrypt_buffer_t Km;
if (!_mongocrypt_buffer_from_subrange(&Km, key, Km_offset, MONGOCRYPT_MAC_KEY_LEN)) {
CLIENT_ERR("unable to create Km subrange from key");
return false;
}
/* Primary payload to MAC. */
_mongocrypt_buffer_t iv_and_ciphertext;
if (!_mongocrypt_buffer_from_subrange(&iv_and_ciphertext, ciphertext, 0, *bytes_written)) {
CLIENT_ERR("unable to create IV || S subrange from C");
return false;
}
// T == HMAC Tag
_mongocrypt_buffer_t T;
if (!_mongocrypt_buffer_from_subrange(&T, ciphertext, *bytes_written, MONGOCRYPT_HMAC_LEN)) {
CLIENT_ERR("unable to create T subrange from C");
return false;
}
if (!_hmac_step(crypto, mac_format, hmac, &Km, associated_data, &iv_and_ciphertext, &T, status)) {
return false;
}
*bytes_written += MONGOCRYPT_HMAC_LEN;
}
return true;
}
/* ----------------------------------------------------------------------------
*
* _decrypt_step --
*
* Decrypts using AES256 using a secret key and a known IV.
*
* Parameters:
* @enc_key a 32 byte key.
* @ciphertext the ciphertext to decrypt.
* @plaintext the resulting plaintext.
* @bytes_written a location for the resulting number of bytes written into
* plaintext->data.
* @status set on error.
*
* Returns:
* True on success. On error, sets @status and returns false.
*
* Preconditions:
* 1. plaintext->data has been pre-allocated with enough space for the
* resulting plaintext.
*
* Postconditions:
* 1. bytes_written is set to the length of the written plaintext, excluding
* padding. This may be less than
* _mongocrypt_calculate_plaintext_len (ciphertext->len, home, hmac, status).
*
* ----------------------------------------------------------------------------
*/
static bool _decrypt_step(_mongocrypt_crypto_t *crypto,
_mongocrypt_encryption_mode_t mode,
const _mongocrypt_buffer_t *iv,
const _mongocrypt_buffer_t *enc_key,
const _mongocrypt_buffer_t *ciphertext,
_mongocrypt_buffer_t *plaintext,
uint32_t *bytes_written,
mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(iv);
BSON_ASSERT_PARAM(enc_key);
BSON_ASSERT_PARAM(ciphertext);
BSON_ASSERT_PARAM(plaintext);
BSON_ASSERT_PARAM(bytes_written);
*bytes_written = 0;
if (MONGOCRYPT_IV_LEN != iv->len) {
CLIENT_ERR("IV should have length %d, but has length %" PRIu32, MONGOCRYPT_IV_LEN, iv->len);
return false;
}
if (MONGOCRYPT_ENC_KEY_LEN != enc_key->len) {
CLIENT_ERR("encryption key should have length %d, but has length %" PRIu32,
MONGOCRYPT_ENC_KEY_LEN,
enc_key->len);
return false;
}
if (mode == MODE_CBC) {
if (ciphertext->len % MONGOCRYPT_BLOCK_SIZE > 0) {
CLIENT_ERR("error, ciphertext length is not a multiple of block size");
return false;
}
if (!_crypto_aes_256_cbc_decrypt(crypto,
(aes_256_args_t){.iv = iv,
.key = enc_key,
.in = ciphertext,
.out = plaintext,
.bytes_written = bytes_written,
.status = status})) {
return false;
}
BSON_ASSERT(*bytes_written > 0);
uint8_t padding_byte = plaintext->data[*bytes_written - 1];
if (padding_byte > 16) {
CLIENT_ERR("error, ciphertext malformed padding");
return false;
}
*bytes_written -= padding_byte;
} else {
BSON_ASSERT(mode == MODE_CTR);
if (!_crypto_aes_256_ctr_decrypt(crypto,
(aes_256_args_t){.iv = iv,
.key = enc_key,
.in = ciphertext,
.out = plaintext,
.bytes_written = bytes_written,
.status = status})) {
return false;
}
BSON_ASSERT(*bytes_written == plaintext->len);
}
return true;
}
/* ----------------------------------------------------------------------------
*
* _mongocrypt_do_decryption --
*
* Defer decryption to whichever crypto library libmongocrypt is using.
*
* Parameters:
* @associated_data associated data for the HMAC. May be NULL.
* @key a 96 byte key.
* @ciphertext the ciphertext to decrypt. This contains the IV prepended.
* @plaintext a location for the resulting plaintext.
* @bytes_written a location for the resulting bytes written.
* @status set on error.
*
* Returns:
* True on success. On error, sets @status and returns false.
*
* Preconditions:
* 1. plaintext->data has been pre-allocated with enough space for the
* resulting plaintext and padding. See _mongocrypt_calculate_plaintext_len.
*
* Postconditions:
* 1. bytes_written is set to the length of the written plaintext, excluding
* padding. This may be less than
* _mongocrypt_calculate_plaintext_len (ciphertext->len, mode, hmac, status).
*
* ----------------------------------------------------------------------------
*/
static bool _mongocrypt_do_decryption(_mongocrypt_crypto_t *crypto,
_mongocrypt_key_format_t key_format,
_mongocrypt_mac_format_t mac_format,
_mongocrypt_encryption_mode_t mode,
_mongocrypt_hmac_type_t hmac,
const _mongocrypt_buffer_t *associated_data,
const _mongocrypt_buffer_t *key,
const _mongocrypt_buffer_t *ciphertext,
_mongocrypt_buffer_t *plaintext,
uint32_t *bytes_written,
mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(crypto);
/* associated_data is checked at the point it is used, so it can be NULL */
BSON_ASSERT_PARAM(key);
BSON_ASSERT_PARAM(ciphertext);
BSON_ASSERT_PARAM(plaintext);
BSON_ASSERT_PARAM(bytes_written);
const uint32_t expect_plaintext_len = _mongocrypt_calculate_plaintext_len(ciphertext->len, mode, hmac, status);
if (mongocrypt_status_type(status) != MONGOCRYPT_STATUS_OK) {
return false;
}
if (plaintext->len != expect_plaintext_len) {
CLIENT_ERR("output plaintext should have been allocated with %" PRIu32 " bytes, "
"but has: %" PRIu32,
expect_plaintext_len,
plaintext->len);
return false;
}
if (expect_plaintext_len == 0) {
// While a ciphertext string describing a zero length plaintext is
// technically valid,
// it's not actually particularly useful in the context of FLE where such
// values aren't encoded.
CLIENT_ERR("input ciphertext too small. Must be more than %" PRIu32 " bytes",
_mongocrypt_calculate_ciphertext_len(0, mode, hmac, NULL));
return false;
}
const uint32_t expected_key_len = (key_format == KEY_FORMAT_FLE2) ? MONGOCRYPT_ENC_KEY_LEN : MONGOCRYPT_KEY_LEN;
if (expected_key_len != key->len) {
CLIENT_ERR("key should have length %" PRIu32 ", but has length %" PRIu32, expected_key_len, key->len);
return false;
}
const uint32_t min_cipherlen = _mongocrypt_calculate_ciphertext_len(0, mode, hmac, NULL);
if (ciphertext->len < min_cipherlen) {
CLIENT_ERR("corrupt ciphertext - must be >= %" PRIu32 " bytes", min_cipherlen);
return false;
}
_mongocrypt_buffer_t Ke;
const uint32_t Ke_offset = (key_format == KEY_FORMAT_FLE1) ? MONGOCRYPT_MAC_KEY_LEN : 0;
if (!_mongocrypt_buffer_from_subrange(&Ke, key, Ke_offset, MONGOCRYPT_ENC_KEY_LEN)) {
CLIENT_ERR("unable to create Ke subrange from key");
return false;
}
_mongocrypt_buffer_t IV;
if (!_mongocrypt_buffer_from_subrange(&IV, ciphertext, 0, MONGOCRYPT_IV_LEN)) {
CLIENT_ERR("unable to create IV subrange from ciphertext");
return false;
}
if (hmac == HMAC_NONE) {
BSON_ASSERT(key_format == KEY_FORMAT_FLE2);
} else {
BSON_ASSERT(key_format != KEY_FORMAT_FLE2);
uint8_t hmac_tag_storage[MONGOCRYPT_HMAC_LEN];
const uint32_t mac_key_offset = (key_format == KEY_FORMAT_FLE1) ? 0 : MONGOCRYPT_ENC_KEY_LEN;
_mongocrypt_buffer_t Km;
if (!_mongocrypt_buffer_from_subrange(&Km, key, mac_key_offset, MONGOCRYPT_MAC_KEY_LEN)) {
CLIENT_ERR("unable to create Km subrange from key");
return false;
}
_mongocrypt_buffer_t iv_and_ciphertext;
if (!_mongocrypt_buffer_from_subrange(&iv_and_ciphertext,
ciphertext,
0,
ciphertext->len - MONGOCRYPT_HMAC_LEN)) {
CLIENT_ERR("unable to create IV || S subrange from C");
return false;
}
_mongocrypt_buffer_t hmac_tag = {.data = hmac_tag_storage, .len = MONGOCRYPT_HMAC_LEN};
if (!_hmac_step(crypto, mac_format, hmac, &Km, associated_data, &iv_and_ciphertext, &hmac_tag, status)) {
return false;
}
/* Constant time compare. */
_mongocrypt_buffer_t T;
if (!_mongocrypt_buffer_from_subrange(&T,
ciphertext,
ciphertext->len - MONGOCRYPT_HMAC_LEN,
MONGOCRYPT_HMAC_LEN)) {
CLIENT_ERR("unable to create T subrange from C");
return false;
}
if (0 != _mongocrypt_memequal(hmac_tag.data, T.data, MONGOCRYPT_HMAC_LEN)) {
CLIENT_ERR("HMAC validation failure");
return false;
}
}
/* Decrypt data excluding IV + HMAC. */
const uint32_t hmac_len = (hmac == HMAC_NONE) ? 0 : MONGOCRYPT_HMAC_LEN;
_mongocrypt_buffer_t S;
if (!_mongocrypt_buffer_from_subrange(&S,
ciphertext,
MONGOCRYPT_IV_LEN,
ciphertext->len - MONGOCRYPT_IV_LEN - hmac_len)) {
CLIENT_ERR("unable to create S subrange from C");
return false;
}
return _decrypt_step(crypto, mode, &IV, &Ke, &S, plaintext, bytes_written, status);
}
#define DECLARE_ALGORITHM(name, mode, hmac) \
static uint32_t _mc_##name##_ciphertext_len(uint32_t plaintext_len, mongocrypt_status_t *status) { \
return _mongocrypt_calculate_ciphertext_len(plaintext_len, MODE_##mode, HMAC_##hmac, status); \
} \
static uint32_t _mc_##name##_plaintext_len(uint32_t ciphertext_len, mongocrypt_status_t *status) { \
return _mongocrypt_calculate_plaintext_len(ciphertext_len, MODE_##mode, HMAC_##hmac, status); \
} \
static bool _mc_##name##_do_encryption(_mongocrypt_crypto_t *crypto, \
const _mongocrypt_buffer_t *iv, \
const _mongocrypt_buffer_t *aad, \
const _mongocrypt_buffer_t *key, \
const _mongocrypt_buffer_t *plaintext, \
_mongocrypt_buffer_t *ciphertext, \
uint32_t *written, \
mongocrypt_status_t *status) { \
return _mongocrypt_do_encryption(crypto, \
KEY_FORMAT_##name, \
MAC_FORMAT_##name, \
MODE_##mode, \
HMAC_##hmac, \
iv, \
aad, \
key, \
plaintext, \
ciphertext, \
written, \
status); \
} \
static bool _mc_##name##_do_decryption(_mongocrypt_crypto_t *crypto, \
const _mongocrypt_buffer_t *aad, \
const _mongocrypt_buffer_t *key, \
const _mongocrypt_buffer_t *ciphertext, \
_mongocrypt_buffer_t *plaintext, \
uint32_t *written, \
mongocrypt_status_t *status) { \
return _mongocrypt_do_decryption(crypto, \
KEY_FORMAT_##name, \
MAC_FORMAT_##name, \
MODE_##mode, \
HMAC_##hmac, \
aad, \
key, \
ciphertext, \
plaintext, \
written, \
status); \
} \
static const _mongocrypt_value_encryption_algorithm_t _mc##name##Algorithm_definition = { \
_mc_##name##_ciphertext_len, \
_mc_##name##_plaintext_len, \
_mc_##name##_do_encryption, \
_mc_##name##_do_decryption, \
}; \
const _mongocrypt_value_encryption_algorithm_t *_mc##name##Algorithm(void) { \
return &_mc##name##Algorithm_definition; \
}
// FLE1 algorithm: AES-256-CBC HMAC/SHA-512-256 (SHA-512 truncated to 256 bits)
DECLARE_ALGORITHM(FLE1, CBC, SHA_512_256)
// FLE2 AEAD used value algorithm: AES-256-CTR HMAC/SHA-256
DECLARE_ALGORITHM(FLE2AEAD, CTR, SHA_256)
// FLE2 used with ESC/ECOC tokens: AES-256-CTR no HMAC
DECLARE_ALGORITHM(FLE2, CTR, NONE)
// FLE2v2 AEAD general algorithm: AES-256-CBC HMAC/SHA-256
DECLARE_ALGORITHM(FLE2v2AEAD, CBC, SHA_256)
#undef DECLARE_ALGORITHM
/* ----------------------------------------------------------------------------
*
* _mongocrypt_random --
*
* Generates a string of random bytes.
*
* Parameters:
* @out an output buffer that has been pre-allocated.
* @status set on error.
* @count the size of the random string in bytes.
*
* Returns:
* True on success. On error, sets @status and returns false.
*
* Preconditions:
* 1. out has been pre-allocated with at least 'count' bytes of space.
*
* ----------------------------------------------------------------------------
*/
bool _mongocrypt_random(_mongocrypt_crypto_t *crypto,
_mongocrypt_buffer_t *out,
uint32_t count,
mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(out);
if (count != out->len) {
CLIENT_ERR("out should have length %" PRIu32 ", but has length %" PRIu32, count, out->len);
return false;
}
return _crypto_random(crypto, out, count, status);
}
/* ----------------------------------------------------------------------------
*
* _mongocrypt_calculate_deterministic_iv --
*
* Compute the IV for deterministic encryption from the plaintext and IV
* key by using HMAC function.
*
* Parameters:
* @key the 96 byte key. The last 32 represent the IV key.
* @plaintext the plaintext to be encrypted.
* @associated_data associated data to include in the HMAC.
* @out an output buffer that has been pre-allocated.
* @status set on error.
*
* Returns:
* True on success. On error, sets @status and returns false.
*
* Preconditions:
* 1. out has been pre-allocated with at least MONGOCRYPT_IV_LEN bytes.
*
* ----------------------------------------------------------------------------
*/
bool _mongocrypt_calculate_deterministic_iv(_mongocrypt_crypto_t *crypto,
const _mongocrypt_buffer_t *key,
const _mongocrypt_buffer_t *plaintext,
const _mongocrypt_buffer_t *associated_data,
_mongocrypt_buffer_t *out,
mongocrypt_status_t *status) {
_mongocrypt_buffer_t intermediates[3];
_mongocrypt_buffer_t to_hmac;
_mongocrypt_buffer_t iv_key;
uint64_t associated_data_len_be;
uint8_t tag_storage[64];
_mongocrypt_buffer_t tag;
bool ret = false;
_mongocrypt_buffer_init(&to_hmac);
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(key);
BSON_ASSERT_PARAM(plaintext);
BSON_ASSERT_PARAM(associated_data);
BSON_ASSERT_PARAM(out);
if (MONGOCRYPT_KEY_LEN != key->len) {
CLIENT_ERR("key should have length %d, but has length %" PRIu32 "\n", MONGOCRYPT_KEY_LEN, key->len);
goto done;
}
if (MONGOCRYPT_IV_LEN != out->len) {
CLIENT_ERR("out should have length %d, but has length %" PRIu32 "\n", MONGOCRYPT_IV_LEN, out->len);
goto done;
}
_mongocrypt_buffer_init(&iv_key);
iv_key.data = key->data + MONGOCRYPT_ENC_KEY_LEN + MONGOCRYPT_MAC_KEY_LEN;
iv_key.len = MONGOCRYPT_IV_KEY_LEN;
_mongocrypt_buffer_init(&intermediates[0]);
_mongocrypt_buffer_init(&intermediates[1]);
_mongocrypt_buffer_init(&intermediates[2]);
/* Add associated data. */
intermediates[0].data = associated_data->data;
intermediates[0].len = associated_data->len;
/* Add associated data length in bits. */
/* multiplying a uint32_t by 8 won't bring it anywhere close to UINT64_MAX */
associated_data_len_be = 8 * (uint64_t)associated_data->len;
associated_data_len_be = BSON_UINT64_TO_BE(associated_data_len_be);
intermediates[1].data = (uint8_t *)&associated_data_len_be;
intermediates[1].len = sizeof(uint64_t);
/* Add plaintext. */
intermediates[2].data = (uint8_t *)plaintext->data;
intermediates[2].len = plaintext->len;
tag.data = tag_storage;
tag.len = sizeof(tag_storage);
if (!_mongocrypt_buffer_concat(&to_hmac, intermediates, 3)) {
CLIENT_ERR("failed to allocate buffer");
goto done;
}
if (!_crypto_hmac_sha_512(crypto, &iv_key, &to_hmac, &tag, status)) {
goto done;
}
/* Truncate to IV length */
memcpy(out->data, tag.data, MONGOCRYPT_IV_LEN);
ret = true;
done:
_mongocrypt_buffer_cleanup(&to_hmac);
return ret;
}
bool _mongocrypt_wrap_key(_mongocrypt_crypto_t *crypto,
_mongocrypt_buffer_t *kek,
_mongocrypt_buffer_t *dek,
_mongocrypt_buffer_t *encrypted_dek,
mongocrypt_status_t *status) {
const _mongocrypt_value_encryption_algorithm_t *fle1alg = _mcFLE1Algorithm();
uint32_t bytes_written;
_mongocrypt_buffer_t iv = {0};
bool ret = false;
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(kek);
BSON_ASSERT_PARAM(dek);
BSON_ASSERT_PARAM(encrypted_dek);
_mongocrypt_buffer_init(encrypted_dek);
if (dek->len != MONGOCRYPT_KEY_LEN) {
CLIENT_ERR("data encryption key is incorrect length, expected: %d, got: %" PRIu32,
MONGOCRYPT_KEY_LEN,
dek->len);
goto done;
}
// _mongocrypt_wrap_key() uses FLE1 algorithm parameters.
_mongocrypt_buffer_resize(encrypted_dek, fle1alg->get_ciphertext_len(dek->len, status));
_mongocrypt_buffer_resize(&iv, MONGOCRYPT_IV_LEN);
if (!_mongocrypt_random(crypto, &iv, MONGOCRYPT_IV_LEN, status)) {
goto done;
}
if (!fle1alg
->do_encrypt(crypto, &iv, NULL /* associated data. */, kek, dek, encrypted_dek, &bytes_written, status)) {
goto done;
}
ret = true;
done:
_mongocrypt_buffer_cleanup(&iv);
return ret;
}
bool _mongocrypt_unwrap_key(_mongocrypt_crypto_t *crypto,
_mongocrypt_buffer_t *kek,
_mongocrypt_buffer_t *encrypted_dek,
_mongocrypt_buffer_t *dek,
mongocrypt_status_t *status) {
const _mongocrypt_value_encryption_algorithm_t *fle1alg = _mcFLE1Algorithm();
uint32_t bytes_written;
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(kek);
BSON_ASSERT_PARAM(dek);
BSON_ASSERT_PARAM(encrypted_dek);
// _mongocrypt_wrap_key() uses FLE1 algorithm parameters.
_mongocrypt_buffer_init(dek);
const uint32_t plaintext_len = fle1alg->get_plaintext_len(encrypted_dek->len, status);
if (!mongocrypt_status_ok(status)) {
return false;
}
_mongocrypt_buffer_resize(dek, plaintext_len);
if (!fle1alg->do_decrypt(crypto, NULL /* associated data. */, kek, encrypted_dek, dek, &bytes_written, status)) {
return false;
}
dek->len = bytes_written;
if (dek->len != MONGOCRYPT_KEY_LEN) {
CLIENT_ERR("decrypted key is incorrect length, expected: %d, got: %" PRIu32, MONGOCRYPT_KEY_LEN, dek->len);
return false;
}
return true;
}
/* This implementation avoids modulo bias. It is based on arc4random_uniform:
https://github.com/openbsd/src/blob/2207c4325726fdc5c4bcd0011af0fdf7d3dab137/lib/libc/crypt/arc4random_uniform.c#L33
*/
bool _mongocrypt_random_uint64(_mongocrypt_crypto_t *crypto,
uint64_t exclusive_upper_bound,
uint64_t *out,
mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(out);
*out = 0;
if (exclusive_upper_bound < 2) {
*out = 0;
return true;
}
/* 2**64 % x == (2**64 - x) % x */
uint64_t min = (0 - exclusive_upper_bound) % exclusive_upper_bound;
_mongocrypt_buffer_t rand_u64_buf;
_mongocrypt_buffer_init(&rand_u64_buf);
_mongocrypt_buffer_resize(&rand_u64_buf, (uint32_t)sizeof(uint64_t));
uint64_t rand_u64;
for (;;) {
if (!_mongocrypt_random(crypto, &rand_u64_buf, rand_u64_buf.len, status)) {
_mongocrypt_buffer_cleanup(&rand_u64_buf);
return false;
}
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;
}
}
*out = rand_u64 % exclusive_upper_bound;
_mongocrypt_buffer_cleanup(&rand_u64_buf);
return true;
}
bool _mongocrypt_random_int64(_mongocrypt_crypto_t *crypto,
int64_t exclusive_upper_bound,
int64_t *out,
mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(crypto);
BSON_ASSERT_PARAM(out);
if (exclusive_upper_bound <= 0) {
CLIENT_ERR("Expected exclusive_upper_bound > 0");
return false;
}
uint64_t u64_exclusive_upper_bound = (uint64_t)exclusive_upper_bound;
uint64_t u64_out;
if (!_mongocrypt_random_uint64(crypto, u64_exclusive_upper_bound, &u64_out, status)) {
return false;
}
/* Zero the leading bit to ensure rand_i64 is non-negative. */
u64_out &= (~(1ull << 63));
*out = (int64_t)u64_out;
return true;
}

File Metadata

Mime Type
text/x-c
Expires
Sat, Nov 14, 9:27 PM (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
135443
Default Alt Text
mongocrypt-crypto.c (56 KB)

Event Timeline