/* * 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. */ #include "../mongocrypt-crypto-private.h" #include "../mongocrypt-private.h" #include #ifdef MONGOCRYPT_ENABLE_CRYPTO_CNG #include static BCRYPT_ALG_HANDLE _algo_sha512_hmac = 0; static BCRYPT_ALG_HANDLE _algo_sha256_hmac = 0; static BCRYPT_ALG_HANDLE _algo_aes256_cbc = 0; static BCRYPT_ALG_HANDLE _algo_aes256_ecb = 0; static DWORD _aes256_key_blob_length; static DWORD _aes256_block_length; static BCRYPT_ALG_HANDLE _random; #define STATUS_SUCCESS 0 bool _native_crypto_initialized = false; void _native_crypto_init (void) { DWORD cbOutput; NTSTATUS nt_status; /* Note, there is no mechanism for libmongocrypt to close these providers, * If we ever add such a mechanism, call BCryptCloseAlgorithmProvider. */ nt_status = BCryptOpenAlgorithmProvider (&_algo_sha512_hmac, BCRYPT_SHA512_ALGORITHM, MS_PRIMITIVE_PROVIDER, BCRYPT_ALG_HANDLE_HMAC_FLAG); if (nt_status != STATUS_SUCCESS) { return; } nt_status = BCryptOpenAlgorithmProvider (&_algo_sha256_hmac, BCRYPT_SHA256_ALGORITHM, MS_PRIMITIVE_PROVIDER, BCRYPT_ALG_HANDLE_HMAC_FLAG); if (nt_status != STATUS_SUCCESS) { return; } nt_status = BCryptOpenAlgorithmProvider ( &_algo_aes256_cbc, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0); if (nt_status != STATUS_SUCCESS) { return; } nt_status = BCryptSetProperty ( _algo_aes256_cbc, BCRYPT_CHAINING_MODE, (PUCHAR) (BCRYPT_CHAIN_MODE_CBC), (ULONG) (sizeof (wchar_t) * wcslen (BCRYPT_CHAIN_MODE_CBC)), 0); if (nt_status != STATUS_SUCCESS) { return; } nt_status = BCryptOpenAlgorithmProvider ( &_algo_aes256_ecb, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0); if (nt_status != STATUS_SUCCESS) { return; } nt_status = BCryptSetProperty ( _algo_aes256_ecb, BCRYPT_CHAINING_MODE, (PUCHAR) (BCRYPT_CHAIN_MODE_ECB), (ULONG) (sizeof (wchar_t) * wcslen (BCRYPT_CHAIN_MODE_ECB)), 0); if (nt_status != STATUS_SUCCESS) { return; } cbOutput = sizeof (_aes256_key_blob_length); nt_status = BCryptGetProperty (_algo_aes256_cbc, BCRYPT_OBJECT_LENGTH, (PUCHAR) (&_aes256_key_blob_length), cbOutput, &cbOutput, 0); if (nt_status != STATUS_SUCCESS) { return; } cbOutput = sizeof (_aes256_block_length); nt_status = BCryptGetProperty (_algo_aes256_cbc, BCRYPT_BLOCK_LENGTH, (PUCHAR) (&_aes256_block_length), cbOutput, &cbOutput, 0); if (nt_status != STATUS_SUCCESS) { return; } nt_status = BCryptOpenAlgorithmProvider ( &_random, BCRYPT_RNG_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0); if (nt_status != STATUS_SUCCESS) { return; } _native_crypto_initialized = true; } typedef struct { unsigned char *key_object; uint32_t key_object_length; BCRYPT_KEY_HANDLE key_handle; unsigned char *iv; uint32_t iv_len; } cng_encrypt_state; static void _crypto_state_destroy (cng_encrypt_state *state); static cng_encrypt_state * _crypto_state_init (const _mongocrypt_buffer_t *key, const _mongocrypt_buffer_t *iv, mongocrypt_status_t *status) { cng_encrypt_state *state; uint32_t keyBlobLength; unsigned char *keyBlob; BCRYPT_KEY_DATA_BLOB_HEADER blobHeader; NTSTATUS nt_status; BSON_ASSERT_PARAM (key); BSON_ASSERT_PARAM (iv); keyBlob = NULL; state = bson_malloc0 (sizeof (*state)); BSON_ASSERT (state); state->key_handle = INVALID_HANDLE_VALUE; /* Initialize key storage buffer */ state->key_object = bson_malloc0 (_aes256_key_blob_length); BSON_ASSERT (state->key_object); state->key_object_length = _aes256_key_blob_length; /* Allocate temporary buffer for key import */ BSON_ASSERT (sizeof (BCRYPT_KEY_DATA_BLOB_HEADER) + key->len <= UINT32_MAX); keyBlobLength = sizeof (BCRYPT_KEY_DATA_BLOB_HEADER) + key->len; keyBlob = bson_malloc0 (keyBlobLength); BSON_ASSERT (keyBlob); blobHeader.dwMagic = BCRYPT_KEY_DATA_BLOB_MAGIC; blobHeader.dwVersion = BCRYPT_KEY_DATA_BLOB_VERSION1; blobHeader.cbKeyData = key->len; memcpy (keyBlob, &blobHeader, sizeof (BCRYPT_KEY_DATA_BLOB_HEADER)); memcpy (keyBlob + sizeof (BCRYPT_KEY_DATA_BLOB_HEADER), key->data, key->len); nt_status = BCryptImportKey (_algo_aes256_cbc, NULL, BCRYPT_KEY_DATA_BLOB, &(state->key_handle), state->key_object, state->key_object_length, keyBlob, keyBlobLength, 0); if (nt_status != STATUS_SUCCESS) { CLIENT_ERR ("Import Key Failed: 0x%x", (int) nt_status); goto fail; } bson_free (keyBlob); state->iv = bson_malloc0 (iv->len); BSON_ASSERT (state->iv); state->iv_len = iv->len; memcpy (state->iv, iv->data, iv->len); return state; fail: _crypto_state_destroy (state); bson_free (keyBlob); return NULL; } static void _crypto_state_destroy (cng_encrypt_state *state) { if (state) { /* Free the key handle before the key_object that contains it */ if (state->key_handle != INVALID_HANDLE_VALUE) { BCryptDestroyKey (state->key_handle); } bson_free (state->key_object); bson_free (state->iv); bson_free (state); } } bool _native_crypto_aes_256_cbc_encrypt (aes_256_args_t args) { BSON_ASSERT (args.in); BSON_ASSERT (args.out); bool ret = false; mongocrypt_status_t *status = args.status; cng_encrypt_state *state = _crypto_state_init (args.key, args.iv, status); BSON_ASSERT (state); NTSTATUS nt_status; nt_status = BCryptEncrypt (state->key_handle, (PUCHAR) (args.in->data), args.in->len, NULL, state->iv, state->iv_len, args.out->data, args.out->len, args.bytes_written, 0); if (nt_status != STATUS_SUCCESS) { CLIENT_ERR ("error initializing cipher: 0x%x", (int) nt_status); goto done; } ret = true; done: _crypto_state_destroy (state); return ret; } bool _native_crypto_aes_256_cbc_decrypt (aes_256_args_t args) { BSON_ASSERT (args.in); BSON_ASSERT (args.out); bool ret = false; mongocrypt_status_t *status = args.status; cng_encrypt_state *state = _crypto_state_init (args.key, args.iv, status); BSON_ASSERT (state); NTSTATUS nt_status; nt_status = BCryptDecrypt (state->key_handle, (PUCHAR) (args.in->data), args.in->len, NULL, state->iv, state->iv_len, args.out->data, args.out->len, args.bytes_written, 0); if (nt_status != STATUS_SUCCESS) { CLIENT_ERR ("error initializing cipher: 0x%x", (int) nt_status); goto done; } ret = true; done: _crypto_state_destroy (state); return ret; } /* _hmac_with_algorithm computes an HMAC of @in with the algorithm specified by * @hAlgorithm. * @key is the input key. * @out is the output. @out must be allocated by the caller with * the expected length @expect_out_len for the output. * Returns false and sets @status on error. @status is required. */ static bool _hmac_with_algorithm (BCRYPT_ALG_HANDLE hAlgorithm, const _mongocrypt_buffer_t *key, const _mongocrypt_buffer_t *in, _mongocrypt_buffer_t *out, uint32_t expect_out_len, mongocrypt_status_t *status) { bool ret = false; BCRYPT_HASH_HANDLE hHash; NTSTATUS nt_status; BSON_ASSERT_PARAM (key); BSON_ASSERT_PARAM (in); BSON_ASSERT_PARAM (out); if (out->len != expect_out_len) { CLIENT_ERR ("out does not contain " PRIu32 " bytes", expect_out_len); return false; } nt_status = BCryptCreateHash ( hAlgorithm, &hHash, NULL, 0, (PUCHAR) key->data, (ULONG) key->len, 0); if (nt_status != STATUS_SUCCESS) { CLIENT_ERR ("error initializing hmac: 0x%x", (int) nt_status); /* Only call BCryptDestroyHash if BCryptCreateHash succeeded. */ return false; } nt_status = BCryptHashData (hHash, (PUCHAR) in->data, (ULONG) in->len, 0); if (nt_status != STATUS_SUCCESS) { CLIENT_ERR ("error hashing data: 0x%x", (int) nt_status); goto done; } nt_status = BCryptFinishHash (hHash, out->data, out->len, 0); if (nt_status != STATUS_SUCCESS) { CLIENT_ERR ("error finishing hmac: 0x%x", (int) nt_status); goto done; } ret = true; done: (void) BCryptDestroyHash (hHash); return ret; } bool _native_crypto_hmac_sha_512 (const _mongocrypt_buffer_t *key, const _mongocrypt_buffer_t *in, _mongocrypt_buffer_t *out, mongocrypt_status_t *status) { return _hmac_with_algorithm ( _algo_sha512_hmac, key, in, out, MONGOCRYPT_HMAC_SHA512_LEN, status); } bool _native_crypto_random (_mongocrypt_buffer_t *out, uint32_t count, mongocrypt_status_t *status) { BSON_ASSERT_PARAM (out); NTSTATUS nt_status = BCryptGenRandom (_random, out->data, count, 0); if (nt_status != STATUS_SUCCESS) { CLIENT_ERR ("BCryptGenRandom Failed: 0x%x", (int) nt_status); return false; } return true; } typedef struct { BCRYPT_KEY_HANDLE key_handle; unsigned char *input_block; uint32_t input_block_len; unsigned char *output_block; uint32_t output_block_len; uint32_t output_block_ptr; } cng_ctr_encrypt_state; static bool _cng_ctr_crypto_generate (cng_ctr_encrypt_state *state, mongocrypt_status_t *status) { BSON_ASSERT (state); uint32_t bytesEncrypted = 0; NTSTATUS nt_status = BCryptEncrypt (state->key_handle, state->input_block, state->input_block_len, NULL, NULL, 0, state->output_block, state->output_block_len, &bytesEncrypted, 0); if (nt_status != STATUS_SUCCESS) { CLIENT_ERR ("error encrypting: 0x%x", (int) nt_status); return false; } BSON_ASSERT (bytesEncrypted); state->output_block_ptr = 0; return true; } static void _cng_ctr_crypto_advance (cng_ctr_encrypt_state *state) { BSON_ASSERT_PARAM (state); /* Assert rather than return false/NULL since this function's type is void */ BSON_ASSERT (sizeof (BCRYPT_KEY_DATA_BLOB_HEADER) <= UINT32_MAX - state->input_block_len); uint32_t carry = 1; for (int i = (int) state->input_block_len - 1; i >= 0 && carry != 0; --i) { uint32_t bpp = (uint32_t) (state->input_block[i]) + carry; carry = bpp >> 8; state->input_block[i] = bpp & 0xFF; } } static bool _cng_ctr_crypto_next (cng_ctr_encrypt_state *state, mongocrypt_status_t *status, unsigned char *mask) { BSON_ASSERT_PARAM (state); BSON_ASSERT_PARAM (mask); if (state->output_block_ptr >= state->output_block_len) { _cng_ctr_crypto_advance (state); if (!_cng_ctr_crypto_generate (state, status)) { return false; }; } *mask = state->output_block[state->output_block_ptr]; ++state->output_block_ptr; return true; } static void _cng_ctr_crypto_state_destroy (cng_ctr_encrypt_state *state); static cng_ctr_encrypt_state * _cng_ctr_crypto_state_init (const _mongocrypt_buffer_t *key, const _mongocrypt_buffer_t *iv, mongocrypt_status_t *status) { cng_ctr_encrypt_state *state; uint32_t keyBlobLength; unsigned char *keyBlob; BCRYPT_KEY_DATA_BLOB_HEADER blobHeader; NTSTATUS nt_status; BSON_ASSERT_PARAM (key); BSON_ASSERT_PARAM (iv); keyBlob = NULL; state = bson_malloc0 (sizeof (*state)); BSON_ASSERT (state); if (UINT32_MAX - key->len < sizeof (BCRYPT_KEY_DATA_BLOB_HEADER)) { CLIENT_ERR ("key is too long"); goto fail; } state->key_handle = INVALID_HANDLE_VALUE; /* Initialize input storage buffer */ state->input_block = bson_malloc0 (_aes256_block_length); BSON_ASSERT (state->input_block); state->input_block_len = _aes256_block_length; BSON_ASSERT (iv->len == _aes256_block_length); memcpy (state->input_block, iv->data, iv->len); /* Initialize output storage buffer */ state->output_block = bson_malloc0 (_aes256_block_length); BSON_ASSERT (state->output_block); state->output_block_len = _aes256_block_length; state->output_block_ptr = 0; /* Allocate temporary buffer for key import */ keyBlobLength = sizeof (BCRYPT_KEY_DATA_BLOB_HEADER) + key->len; keyBlob = bson_malloc0 (keyBlobLength); BSON_ASSERT (keyBlob); blobHeader.dwMagic = BCRYPT_KEY_DATA_BLOB_MAGIC; blobHeader.dwVersion = BCRYPT_KEY_DATA_BLOB_VERSION1; blobHeader.cbKeyData = key->len; memcpy (keyBlob, &blobHeader, sizeof (BCRYPT_KEY_DATA_BLOB_HEADER)); memcpy (keyBlob + sizeof (BCRYPT_KEY_DATA_BLOB_HEADER), key->data, key->len); nt_status = BCryptImportKey (_algo_aes256_ecb, NULL, BCRYPT_KEY_DATA_BLOB, &(state->key_handle), NULL, 0, keyBlob, keyBlobLength, 0); if (nt_status != STATUS_SUCCESS) { CLIENT_ERR ("Import Key Failed: 0x%x", (int) nt_status); goto fail; } bson_free (keyBlob); if (!_cng_ctr_crypto_generate (state, status)) { goto fail; } return state; fail: _cng_ctr_crypto_state_destroy (state); bson_free (keyBlob); return NULL; } static void _cng_ctr_crypto_state_destroy (cng_ctr_encrypt_state *state) { if (state) { if (state->key_handle != INVALID_HANDLE_VALUE) { BCryptDestroyKey (state->key_handle); } bson_free (state->input_block); bson_free (state->output_block); bson_free (state); } } bool _native_crypto_aes_256_ctr_encrypt (aes_256_args_t args) { bool ret = false; cng_ctr_encrypt_state *state = NULL; mongocrypt_status_t *status = args.status; BSON_ASSERT (args.in && args.in->data); BSON_ASSERT (args.out && args.out->data); if (args.out->len < args.in->len) { CLIENT_ERR ("Output buffer is too small"); goto fail; } state = _cng_ctr_crypto_state_init (args.key, args.iv, status); if (!state) { goto fail; } for (uint32_t i = 0; i < args.in->len; ++i) { unsigned char mask; if (!_cng_ctr_crypto_next (state, status, &mask)) { goto fail; } args.out->data[i] = args.in->data[i] ^ mask; } if (args.bytes_written) { *args.bytes_written = args.in->len; } ret = true; fail: _cng_ctr_crypto_state_destroy (state); return ret; } bool _native_crypto_aes_256_ctr_decrypt (aes_256_args_t args) { bool ret = false; cng_ctr_encrypt_state *state = NULL; mongocrypt_status_t *status = args.status; BSON_ASSERT (args.in && args.in->data); BSON_ASSERT (args.out && args.out->data); if (args.out->len < args.in->len) { CLIENT_ERR ("Output buffer is too small"); goto fail; } state = _cng_ctr_crypto_state_init (args.key, args.iv, status); if (!state) { goto fail; } for (uint32_t i = 0; i < args.in->len; ++i) { unsigned char mask; if (!_cng_ctr_crypto_next (state, status, &mask)) { goto fail; } args.out->data[i] = args.in->data[i] ^ mask; } if (args.bytes_written) { *args.bytes_written = args.in->len; } ret = true; fail: _cng_ctr_crypto_state_destroy (state); return ret; } bool _native_crypto_hmac_sha_256 (const _mongocrypt_buffer_t *key, const _mongocrypt_buffer_t *in, _mongocrypt_buffer_t *out, mongocrypt_status_t *status) { return _hmac_with_algorithm ( _algo_sha256_hmac, key, in, out, MONGOCRYPT_HMAC_SHA256_LEN, status); } #endif /* MONGOCRYPT_ENABLE_CRYPTO_CNG */