summaryrefslogtreecommitdiff
path: root/src/mongo/crypto
diff options
context:
space:
mode:
authorMark Benvenuto <mark.benvenuto@mongodb.com>2019-06-18 00:19:33 -0400
committerMark Benvenuto <mark.benvenuto@mongodb.com>2019-06-18 00:19:33 -0400
commitc436b8090417baf847143e97f5d221285b1898e1 (patch)
treebb5369a793c8ec6646b20e92b1a0f85b7979682f /src/mongo/crypto
parent5eda33f9fa40a1a17f9f63f904a8c147700d648c (diff)
downloadmongo-c436b8090417baf847143e97f5d221285b1898e1.tar.gz
SERVER-41644 Expose explicit encryption helpers in community shell
Diffstat (limited to 'src/mongo/crypto')
-rw-r--r--src/mongo/crypto/SConscript33
-rw-r--r--src/mongo/crypto/aead_encryption.cpp391
-rw-r--r--src/mongo/crypto/aead_encryption.h94
-rw-r--r--src/mongo/crypto/aead_encryption_test.cpp152
-rw-r--r--src/mongo/crypto/symmetric_crypto.cpp107
-rw-r--r--src/mongo/crypto/symmetric_crypto.h196
-rw-r--r--src/mongo/crypto/symmetric_crypto_apple.cpp183
-rw-r--r--src/mongo/crypto/symmetric_crypto_openssl.cpp255
-rw-r--r--src/mongo/crypto/symmetric_crypto_windows.cpp335
-rw-r--r--src/mongo/crypto/symmetric_key.cpp100
-rw-r--r--src/mongo/crypto/symmetric_key.h146
11 files changed, 1992 insertions, 0 deletions
diff --git a/src/mongo/crypto/SConscript b/src/mongo/crypto/SConscript
index 97f73e3356a..5cc05d65e2d 100644
--- a/src/mongo/crypto/SConscript
+++ b/src/mongo/crypto/SConscript
@@ -76,3 +76,36 @@ env.CppUnitTest('mechanism_scram_test',
'$BUILD_DIR/mongo/base/secure_allocator',
'sha_block_${MONGO_CRYPTO}',
])
+
+
+env.Library(target='symmetric_crypto',
+ source=[
+ 'symmetric_crypto.cpp',
+ 'symmetric_crypto_${MONGO_CRYPTO}.cpp',
+ 'symmetric_key.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base/secure_allocator',
+ '$BUILD_DIR/mongo/util/net/ssl_manager',
+ '$BUILD_DIR/mongo/util/secure_zero_memory',
+ ],
+)
+
+env.Library(
+ target="aead_encryption",
+ source=[
+ "aead_encryption.cpp",
+ ],
+ LIBDEPS=[
+ 'symmetric_crypto',
+ '$BUILD_DIR/mongo/db/matcher/expressions',
+ ],
+)
+
+env.CppUnitTest(
+ target='aead_encryption_test',
+ source='aead_encryption_test.cpp',
+ LIBDEPS=[
+ 'aead_encryption',
+ ]
+)
diff --git a/src/mongo/crypto/aead_encryption.cpp b/src/mongo/crypto/aead_encryption.cpp
new file mode 100644
index 00000000000..77ec7ed41c4
--- /dev/null
+++ b/src/mongo/crypto/aead_encryption.cpp
@@ -0,0 +1,391 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/crypto/aead_encryption.h"
+
+#include "mongo/base/data_view.h"
+#include "mongo/crypto/sha512_block.h"
+#include "mongo/crypto/symmetric_crypto.h"
+#include "mongo/db/matcher/schema/encrypt_schema_gen.h"
+#include "mongo/util/secure_compare_memory.h"
+
+namespace mongo {
+namespace crypto {
+
+namespace {
+constexpr size_t kHmacOutSize = 32;
+constexpr size_t kIVSize = 16;
+
+// AssociatedData can be 2^24 bytes but since there needs to be room for the ciphertext in the
+// object, a value of 1<<16 was decided to cap the maximum size of AssociatedData.
+constexpr int kMaxAssociatedDataLength = 1 << 16;
+
+size_t aesCBCCipherOutputLength(size_t plainTextLen) {
+ return aesBlockSize * (1 + plainTextLen / aesBlockSize);
+}
+
+std::pair<size_t, size_t> aesCBCExpectedPlaintextLen(size_t cipherTextLength) {
+ return {cipherTextLength - aesCBCIVSize - aesBlockSize, cipherTextLength - aesCBCIVSize};
+}
+
+void aeadGenerateIV(const SymmetricKey* key, uint8_t* buffer, size_t bufferLen) {
+ if (bufferLen < aesCBCIVSize) {
+ fassert(51235, "IV buffer is too small for selected mode");
+ }
+
+ auto status = engineRandBytes(buffer, aesCBCIVSize);
+ if (!status.isOK()) {
+ fassert(51236, status);
+ }
+}
+
+Status _aesEncrypt(const SymmetricKey& key,
+ const std::uint8_t* in,
+ std::size_t inLen,
+ std::uint8_t* out,
+ std::size_t outLen,
+ std::size_t* resultLen,
+ bool ivProvided) try {
+
+ if (!ivProvided) {
+ aeadGenerateIV(&key, out, aesCBCIVSize);
+ }
+
+ auto encryptor =
+ uassertStatusOK(SymmetricEncryptor::create(key, aesMode::cbc, out, aesCBCIVSize));
+
+ const size_t dataSize = outLen - aesCBCIVSize;
+ uint8_t* data = out + aesCBCIVSize;
+
+ const auto updateLen = uassertStatusOK(encryptor->update(in, inLen, data, dataSize));
+ const auto finalLen =
+ uassertStatusOK(encryptor->finalize(data + updateLen, dataSize - updateLen));
+ const auto len = updateLen + finalLen;
+
+ // Some cipher modes, such as GCM, will know in advance exactly how large their ciphertexts will
+ // be. Others, like CBC, will have an upper bound. When this is true, we must allocate enough
+ // memory to store the worst case. We must then set the actual size of the ciphertext so that
+ // the buffer it has been written to may be serialized.
+ invariant(len <= dataSize);
+ *resultLen = aesCBCIVSize + len;
+
+ // Check the returned length, including block size padding
+ if (len != aesCBCCipherOutputLength(inLen)) {
+ return {ErrorCodes::BadValue,
+ str::stream() << "Encrypt error, expected cipher text of length "
+ << aesCBCCipherOutputLength(inLen)
+ << " but found "
+ << len};
+ }
+
+ return Status::OK();
+} catch (const AssertionException& ex) {
+ return ex.toStatus();
+}
+
+Status _aesDecrypt(const SymmetricKey& key,
+ ConstDataRange in,
+ std::uint8_t* out,
+ std::size_t outLen,
+ std::size_t* resultLen) try {
+ // Check the plaintext buffer can fit the product of decryption
+ auto[lowerBound, upperBound] = aesCBCExpectedPlaintextLen(in.length());
+ if (upperBound > outLen) {
+ return {ErrorCodes::BadValue,
+ str::stream() << "Cleartext buffer of size " << outLen
+ << " too small for output which can be as large as "
+ << upperBound
+ << "]"};
+ }
+
+ const uint8_t* dataPtr = reinterpret_cast<const std::uint8_t*>(in.data());
+
+ auto decryptor =
+ uassertStatusOK(SymmetricDecryptor::create(key, aesMode::cbc, dataPtr, aesCBCIVSize));
+
+ const size_t dataSize = in.length() - aesCBCIVSize;
+ const uint8_t* data = dataPtr + aesCBCIVSize;
+
+ const auto updateLen = uassertStatusOK(decryptor->update(data, dataSize, out, outLen));
+
+ const auto finalLen = uassertStatusOK(decryptor->finalize(out + updateLen, outLen - updateLen));
+
+ *resultLen = updateLen + finalLen;
+ invariant(*resultLen <= outLen);
+
+ // Check the returned length, excluding headers block padding
+ if (*resultLen < lowerBound || *resultLen > upperBound) {
+ return {ErrorCodes::BadValue,
+ str::stream() << "Decrypt error, expected clear text length in interval"
+ << "["
+ << lowerBound
+ << ","
+ << upperBound
+ << "]"
+ << "but found "
+ << *resultLen};
+ }
+
+ return Status::OK();
+} catch (const AssertionException& ex) {
+ return ex.toStatus();
+}
+
+} // namespace
+
+size_t aeadCipherOutputLength(size_t plainTextLen) {
+ // To calculate the size of the byte, we divide by the byte size and add 2 for padding
+ // (1 for the attached IV, and 1 for the extra padding). The algorithm will add padding even
+ // if the len is a multiple of the byte size, so if the len divides cleanly it will be
+ // 32 bytes longer than the original, which is 16 bytes as padding and 16 bytes for the
+ // IV. For things that don't divide cleanly, the cast takes care of floor dividing so it will
+ // be 0 < x < 16 bytes added for padding and 16 bytes added for the IV.
+ size_t aesOutLen = aesBlockSize * (plainTextLen / aesBlockSize + 2);
+ return aesOutLen + kHmacOutSize;
+}
+
+Status aeadEncrypt(const SymmetricKey& key,
+ const uint8_t* in,
+ const size_t inLen,
+ const uint8_t* associatedData,
+ const uint64_t associatedDataLen,
+ uint8_t* out,
+ size_t outLen) {
+
+ if (associatedDataLen >= kMaxAssociatedDataLength) {
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "AssociatedData for encryption is too large. Cannot be larger than "
+ << kMaxAssociatedDataLength
+ << " bytes.");
+ }
+
+ // According to the rfc on AES encryption, the associatedDataLength is defined as the
+ // number of bits in associatedData in BigEndian format. This is what the code segment
+ // below describes.
+ // RFC: (https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-01#section-2.1)
+ std::array<uint8_t, sizeof(uint64_t)> dataLenBitsEncodedStorage;
+ DataRange dataLenBitsEncoded(dataLenBitsEncodedStorage);
+ dataLenBitsEncoded.write<BigEndian<uint64_t>>(associatedDataLen * 8);
+
+ auto keySize = key.getKeySize();
+ if (keySize < kAeadAesHmacKeySize) {
+ return Status(ErrorCodes::BadValue,
+ "AEAD encryption key too short. "
+ "Must be either 64 or 96 bytes.");
+ }
+
+ ConstDataRange aeadKey(key.getKey(), kAeadAesHmacKeySize);
+
+ if (key.getKeySize() == kAeadAesHmacKeySize) {
+ // local key store key encryption
+ return aeadEncryptWithIV(aeadKey,
+ in,
+ inLen,
+ nullptr,
+ 0,
+ associatedData,
+ associatedDataLen,
+ dataLenBitsEncoded,
+ out,
+ outLen);
+ }
+
+ if (key.getKeySize() != kFieldLevelEncryptionKeySize) {
+ return Status(ErrorCodes::BadValue, "Invalid key size.");
+ }
+
+ if (in == nullptr || !in) {
+ return Status(ErrorCodes::BadValue, "Invalid AEAD plaintext input.");
+ }
+
+ if (key.getAlgorithm() != aesAlgorithm) {
+ return Status(ErrorCodes::BadValue, "Invalid algorithm for key.");
+ }
+
+ ConstDataRange hmacCDR(nullptr, 0);
+ SHA512Block hmacOutput;
+ if (static_cast<int>(associatedData[0]) ==
+ FleAlgorithmInt_serializer(FleAlgorithmInt::kDeterministic)) {
+ const uint8_t* ivKey = key.getKey() + kAeadAesHmacKeySize;
+ hmacOutput = SHA512Block::computeHmac(ivKey,
+ sym256KeySize,
+ {ConstDataRange(associatedData, associatedDataLen),
+ dataLenBitsEncoded,
+ ConstDataRange(in, inLen)});
+
+ static_assert(SHA512Block::kHashLength >= kIVSize,
+ "Invalid AEAD parameters. Generated IV too short.");
+
+ hmacCDR = ConstDataRange(hmacOutput.data(), kIVSize);
+ }
+ return aeadEncryptWithIV(aeadKey,
+ in,
+ inLen,
+ reinterpret_cast<const uint8_t*>(hmacCDR.data()),
+ hmacCDR.length(),
+ associatedData,
+ associatedDataLen,
+ dataLenBitsEncoded,
+ out,
+ outLen);
+}
+
+Status aeadEncryptWithIV(ConstDataRange key,
+ const uint8_t* in,
+ const size_t inLen,
+ const uint8_t* iv,
+ const size_t ivLen,
+ const uint8_t* associatedData,
+ const uint64_t associatedDataLen,
+ ConstDataRange dataLenBitsEncoded,
+ uint8_t* out,
+ size_t outLen) {
+ if (key.length() != kAeadAesHmacKeySize) {
+ return Status(ErrorCodes::BadValue, "Invalid key size.");
+ }
+
+ if (!(in && out)) {
+ return Status(ErrorCodes::BadValue, "Invalid AEAD parameters.");
+ }
+
+ if (outLen != aeadCipherOutputLength(inLen)) {
+ return Status(ErrorCodes::BadValue, "Invalid output buffer size.");
+ }
+
+ if (associatedDataLen >= kMaxAssociatedDataLength) {
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "AssociatedData for encryption is too large. Cannot be larger than "
+ << kMaxAssociatedDataLength
+ << " bytes.");
+ }
+
+ const uint8_t* macKey = reinterpret_cast<const uint8_t*>(key.data());
+ const uint8_t* encKey = reinterpret_cast<const uint8_t*>(key.data() + sym256KeySize);
+
+ size_t aesOutLen = outLen - kHmacOutSize;
+
+ size_t cipherTextLen = 0;
+
+ SymmetricKey symEncKey(encKey, sym256KeySize, aesAlgorithm, "aesKey", 1);
+
+ bool ivProvided = false;
+ if (ivLen != 0) {
+ invariant(ivLen == 16);
+ std::copy(iv, iv + ivLen, out);
+ ivProvided = true;
+ }
+
+ auto sEncrypt = _aesEncrypt(symEncKey, in, inLen, out, aesOutLen, &cipherTextLen, ivProvided);
+
+ if (!sEncrypt.isOK()) {
+ return sEncrypt;
+ }
+
+ SHA512Block hmacOutput =
+ SHA512Block::computeHmac(macKey,
+ sym256KeySize,
+ {ConstDataRange(associatedData, associatedDataLen),
+ ConstDataRange(out, cipherTextLen),
+ dataLenBitsEncoded});
+
+ std::copy(hmacOutput.data(), hmacOutput.data() + kHmacOutSize, out + cipherTextLen);
+ return Status::OK();
+}
+
+Status aeadDecrypt(const SymmetricKey& key,
+ const uint8_t* cipherText,
+ const size_t cipherLen,
+ const uint8_t* associatedData,
+ const uint64_t associatedDataLen,
+ uint8_t* out,
+ size_t* outLen) {
+ if (key.getKeySize() < kAeadAesHmacKeySize) {
+ return Status(ErrorCodes::BadValue, "Invalid key size.");
+ }
+
+ if (!(cipherText && out)) {
+ return Status(ErrorCodes::BadValue, "Invalid AEAD parameters.");
+ }
+
+ if ((*outLen) != cipherLen) {
+ return Status(ErrorCodes::BadValue, "Output buffer must be as long as the cipherText.");
+ }
+
+ if (associatedDataLen >= kMaxAssociatedDataLength) {
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "AssociatedData for encryption is too large. Cannot be larger than "
+ << kMaxAssociatedDataLength
+ << " bytes.");
+ }
+
+ const uint8_t* macKey = key.getKey();
+ const uint8_t* encKey = key.getKey() + sym256KeySize;
+
+ if (cipherLen < kHmacOutSize) {
+ return Status(ErrorCodes::BadValue, "Ciphertext is not long enough.");
+ }
+ size_t aesLen = cipherLen - kHmacOutSize;
+
+ // According to the rfc on AES encryption, the associatedDataLength is defined as the
+ // number of bits in associatedData in BigEndian format. This is what the code segment
+ // below describes.
+ std::array<uint8_t, sizeof(uint64_t)> dataLenBitsEncodedStorage;
+ DataRange dataLenBitsEncoded(dataLenBitsEncodedStorage);
+ dataLenBitsEncoded.write<BigEndian<uint64_t>>(associatedDataLen * 8);
+
+ SHA512Block hmacOutput =
+ SHA512Block::computeHmac(macKey,
+ sym256KeySize,
+ {ConstDataRange(associatedData, associatedDataLen),
+ ConstDataRange(cipherText, aesLen),
+ dataLenBitsEncoded});
+
+ if (consttimeMemEqual(reinterpret_cast<const unsigned char*>(hmacOutput.data()),
+ reinterpret_cast<const unsigned char*>(cipherText + aesLen),
+ kHmacOutSize) == false) {
+ return Status(ErrorCodes::BadValue, "HMAC data authentication failed.");
+ }
+
+ SymmetricKey symEncKey(encKey, sym256KeySize, aesAlgorithm, key.getKeyId(), 1);
+
+ auto sDecrypt = _aesDecrypt(symEncKey, ConstDataRange(cipherText, aesLen), out, aesLen, outLen);
+ if (!sDecrypt.isOK()) {
+ return sDecrypt;
+ }
+
+ return Status::OK();
+}
+
+} // namespace crypto
+} // namespace mongo
diff --git a/src/mongo/crypto/aead_encryption.h b/src/mongo/crypto/aead_encryption.h
new file mode 100644
index 00000000000..c5fb79479e6
--- /dev/null
+++ b/src/mongo/crypto/aead_encryption.h
@@ -0,0 +1,94 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+#include "mongo/base/data_view.h"
+#include "mongo/base/status.h"
+#include "mongo/crypto/symmetric_key.h"
+
+namespace mongo {
+namespace crypto {
+
+/**
+ * Constants used in the AEAD function
+ */
+
+constexpr size_t kFieldLevelEncryptionKeySize = 96;
+constexpr size_t kAeadAesHmacKeySize = 64;
+
+/**
+ * Returns the length of the ciphertext output given the plaintext length. Only for AEAD.
+ */
+size_t aeadCipherOutputLength(size_t plainTextLen);
+
+
+/**
+ * Encrypts the plaintext using following the AEAD_AES_256_CBC_HMAC_SHA_512 encryption
+ * algorithm. Writes output to out.
+ */
+Status aeadEncrypt(const SymmetricKey& key,
+ const uint8_t* in,
+ const size_t inLen,
+ const uint8_t* associatedData,
+ const uint64_t associatedDataLen,
+ uint8_t* out,
+ size_t outLen);
+
+/**
+ * Internal calls for the aeadEncryption algorithm. Only used for testing.
+ */
+Status aeadEncryptWithIV(ConstDataRange key,
+ const uint8_t* in,
+ const size_t inLen,
+ const uint8_t* iv,
+ const size_t ivLen,
+ const uint8_t* associatedData,
+ const uint64_t associatedDataLen,
+ ConstDataRange dataLenBitsEncodedStorage,
+ uint8_t* out,
+ size_t outLen);
+
+/**
+ * Decrypts the cipherText using AEAD_AES_256_CBC_HMAC_SHA_512 decryption. Writes output
+ * to out.
+ */
+Status aeadDecrypt(const SymmetricKey& key,
+ const uint8_t* cipherText,
+ const size_t cipherLen,
+ const uint8_t* associatedData,
+ const uint64_t associatedDataLen,
+ uint8_t* out,
+ size_t* outLen);
+
+} // namespace crypto
+} // namespace mongo
diff --git a/src/mongo/crypto/aead_encryption_test.cpp b/src/mongo/crypto/aead_encryption_test.cpp
new file mode 100644
index 00000000000..28177f05b82
--- /dev/null
+++ b/src/mongo/crypto/aead_encryption_test.cpp
@@ -0,0 +1,152 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include <algorithm>
+
+#include "mongo/unittest/death_test.h"
+#include "mongo/unittest/unittest.h"
+
+#include "aead_encryption.h"
+
+namespace mongo {
+namespace {
+
+// The first test is to ensure that the length of the cipher is correct when
+// calling AEAD encrypt.
+TEST(AEAD, aeadCipherOutputLength) {
+ size_t plainTextLen = 16;
+ auto cipherLen = crypto::aeadCipherOutputLength(plainTextLen);
+ ASSERT_EQ(cipherLen, size_t(80));
+
+ plainTextLen = 10;
+ cipherLen = crypto::aeadCipherOutputLength(plainTextLen);
+ ASSERT_EQ(cipherLen, size_t(64));
+}
+
+TEST(AEAD, EncryptAndDecrypt) {
+ // Test case from RFC:
+ // https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-5.4
+
+ const uint8_t aesAlgorithm = 0x1;
+
+ std::array<uint8_t, 64> symKey = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
+ 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
+ 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
+ 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f};
+
+ SecureVector<uint8_t> aesVector = SecureVector<uint8_t>(symKey.begin(), symKey.end());
+ SymmetricKey key = SymmetricKey(aesVector, aesAlgorithm, "aeadEncryptDecryptTest");
+
+ const std::array<uint8_t, 128> plainTextTest = {
+ 0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d,
+ 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65,
+ 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65,
+ 0x63, 0x72, 0x65, 0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x6d, 0x75,
+ 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x66,
+ 0x61, 0x6c, 0x6c, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61,
+ 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65, 0x6d,
+ 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e,
+ 0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65};
+
+ std::array<uint8_t, 192> cryptoBuffer = {};
+
+ std::array<uint8_t, 16> iv = {0x1a,
+ 0xf3,
+ 0x8c,
+ 0x2d,
+ 0xc2,
+ 0xb9,
+ 0x6f,
+ 0xfd,
+ 0xd8,
+ 0x66,
+ 0x94,
+ 0x09,
+ 0x23,
+ 0x41,
+ 0xbc,
+ 0x04};
+
+ std::array<uint8_t, 42> associatedData = {
+ 0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x69,
+ 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x41, 0x75, 0x67, 0x75,
+ 0x73, 0x74, 0x65, 0x20, 0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66, 0x66, 0x73};
+
+ const size_t dataLen = 42;
+
+ std::array<uint8_t, sizeof(uint64_t)> dataLenBitsEncodedStorage;
+ DataRange dataLenBitsEncoded(dataLenBitsEncodedStorage);
+ dataLenBitsEncoded.write<BigEndian<uint64_t>>(dataLen * 8);
+
+ const size_t outLen = crypto::aeadCipherOutputLength(128);
+
+ ASSERT_OK(crypto::aeadEncryptWithIV(symKey,
+ plainTextTest.data(),
+ plainTextTest.size(),
+ iv.data(),
+ iv.size(),
+ associatedData.data(),
+ dataLen,
+ dataLenBitsEncoded,
+ cryptoBuffer.data(),
+ outLen));
+
+ std::array<uint8_t, 192> cryptoBufferTest = {
+ 0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd, 0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc,
+ 0x04, 0x4a, 0xff, 0xaa, 0xad, 0xb7, 0x8c, 0x31, 0xc5, 0xda, 0x4b, 0x1b, 0x59, 0x0d, 0x10,
+ 0xff, 0xbd, 0x3d, 0xd8, 0xd5, 0xd3, 0x02, 0x42, 0x35, 0x26, 0x91, 0x2d, 0xa0, 0x37, 0xec,
+ 0xbc, 0xc7, 0xbd, 0x82, 0x2c, 0x30, 0x1d, 0xd6, 0x7c, 0x37, 0x3b, 0xcc, 0xb5, 0x84, 0xad,
+ 0x3e, 0x92, 0x79, 0xc2, 0xe6, 0xd1, 0x2a, 0x13, 0x74, 0xb7, 0x7f, 0x07, 0x75, 0x53, 0xdf,
+ 0x82, 0x94, 0x10, 0x44, 0x6b, 0x36, 0xeb, 0xd9, 0x70, 0x66, 0x29, 0x6a, 0xe6, 0x42, 0x7e,
+ 0xa7, 0x5c, 0x2e, 0x08, 0x46, 0xa1, 0x1a, 0x09, 0xcc, 0xf5, 0x37, 0x0d, 0xc8, 0x0b, 0xfe,
+ 0xcb, 0xad, 0x28, 0xc7, 0x3f, 0x09, 0xb3, 0xa3, 0xb7, 0x5e, 0x66, 0x2a, 0x25, 0x94, 0x41,
+ 0x0a, 0xe4, 0x96, 0xb2, 0xe2, 0xe6, 0x60, 0x9e, 0x31, 0xe6, 0xe0, 0x2c, 0xc8, 0x37, 0xf0,
+ 0x53, 0xd2, 0x1f, 0x37, 0xff, 0x4f, 0x51, 0x95, 0x0b, 0xbe, 0x26, 0x38, 0xd0, 0x9d, 0xd7,
+ 0xa4, 0x93, 0x09, 0x30, 0x80, 0x6d, 0x07, 0x03, 0xb1, 0xf6, 0x4d, 0xd3, 0xb4, 0xc0, 0x88,
+ 0xa7, 0xf4, 0x5c, 0x21, 0x68, 0x39, 0x64, 0x5b, 0x20, 0x12, 0xbf, 0x2e, 0x62, 0x69, 0xa8,
+ 0xc5, 0x6a, 0x81, 0x6d, 0xbc, 0x1b, 0x26, 0x77, 0x61, 0x95, 0x5b, 0xc5};
+
+ ASSERT_EQ(0, std::memcmp(cryptoBuffer.data(), cryptoBufferTest.data(), 192));
+
+ std::array<uint8_t, 192> plainText = {};
+ size_t plainTextDecryptLen = 192;
+ ASSERT_OK(crypto::aeadDecrypt(key,
+ cryptoBuffer.data(),
+ cryptoBuffer.size(),
+ associatedData.data(),
+ dataLen,
+ plainText.data(),
+ &plainTextDecryptLen));
+
+ ASSERT_EQ(0, std::memcmp(plainText.data(), plainTextTest.data(), 128));
+}
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/crypto/symmetric_crypto.cpp b/src/mongo/crypto/symmetric_crypto.cpp
new file mode 100644
index 00000000000..32d888cfbbb
--- /dev/null
+++ b/src/mongo/crypto/symmetric_crypto.cpp
@@ -0,0 +1,107 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/crypto/symmetric_crypto.h"
+
+#include <memory>
+
+#include "mongo/base/data_cursor.h"
+#include "mongo/base/init.h"
+#include "mongo/base/status.h"
+#include "mongo/crypto/symmetric_key.h"
+#include "mongo/platform/random.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/log.h"
+#include "mongo/util/net/ssl_manager.h"
+#include "mongo/util/str.h"
+
+namespace mongo {
+namespace crypto {
+
+namespace {
+std::unique_ptr<SecureRandom> random;
+} // namespace
+
+MONGO_INITIALIZER(CreateKeyEntropySource)(InitializerContext* context) {
+ random = std::unique_ptr<SecureRandom>(SecureRandom::create());
+ return Status::OK();
+}
+
+size_t aesGetIVSize(crypto::aesMode mode) {
+ switch (mode) {
+ case crypto::aesMode::cbc:
+ return crypto::aesCBCIVSize;
+ case crypto::aesMode::gcm:
+ return crypto::aesGCMIVSize;
+ default:
+ fassertFailed(4053);
+ }
+}
+
+aesMode getCipherModeFromString(const std::string& mode) {
+ if (mode == aes256CBCName) {
+ return aesMode::cbc;
+ } else if (mode == aes256GCMName) {
+ return aesMode::gcm;
+ } else {
+ MONGO_UNREACHABLE;
+ }
+}
+
+std::string getStringFromCipherMode(aesMode mode) {
+ if (mode == aesMode::cbc) {
+ return aes256CBCName;
+ } else if (mode == aesMode::gcm) {
+ return aes256GCMName;
+ } else {
+ MONGO_UNREACHABLE;
+ }
+}
+
+SymmetricKey aesGenerate(size_t keySize, SymmetricKeyId keyId) {
+ invariant(keySize == sym256KeySize);
+
+ SecureVector<uint8_t> key(keySize);
+
+ size_t offset = 0;
+ while (offset < keySize) {
+ std::uint64_t randomValue = random->nextInt64();
+ memcpy(key->data() + offset, &randomValue, sizeof(randomValue));
+ offset += sizeof(randomValue);
+ }
+
+ return SymmetricKey(std::move(key), aesAlgorithm, std::move(keyId));
+}
+
+} // namespace crypto
+} // namespace mongo
diff --git a/src/mongo/crypto/symmetric_crypto.h b/src/mongo/crypto/symmetric_crypto.h
new file mode 100644
index 00000000000..350675a1763
--- /dev/null
+++ b/src/mongo/crypto/symmetric_crypto.h
@@ -0,0 +1,196 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <set>
+#include <string>
+
+#include "mongo/base/status.h"
+#include "mongo/base/status_with.h"
+#include "mongo/crypto/symmetric_key.h"
+
+namespace mongo {
+namespace crypto {
+
+/**
+ * Encryption algorithm identifiers and block sizes
+ */
+constexpr uint8_t aesAlgorithm = 0x1;
+
+/**
+ * Block and key sizes
+ */
+constexpr size_t aesBlockSize = 16;
+constexpr size_t sym256KeySize = 32;
+
+/**
+ * Min and max symmetric key lengths
+ */
+constexpr size_t minKeySize = 16;
+constexpr size_t maxKeySize = 32;
+
+/**
+ * CBC fixed constants
+ */
+constexpr size_t aesCBCIVSize = aesBlockSize;
+
+/**
+ * GCM tunable parameters
+ */
+constexpr size_t aesGCMTagSize = 12;
+constexpr size_t aesGCMIVSize = 12;
+
+/**
+ * Encryption mode identifiers
+ */
+enum class aesMode : uint8_t { cbc, gcm };
+
+/**
+ * Algorithm names which this module recognizes
+ */
+const std::string aes256CBCName = "AES256-CBC";
+const std::string aes256GCMName = "AES256-GCM";
+
+aesMode getCipherModeFromString(const std::string& mode);
+std::string getStringFromCipherMode(aesMode);
+
+/**
+ * Generates a new, random, symmetric key for use with AES.
+ */
+SymmetricKey aesGenerate(size_t keySize, SymmetricKeyId keyId);
+
+/* Platform specific engines should implement these. */
+
+/**
+ * Interface to a symmetric cryptography engine.
+ * For use with encrypting payloads.
+ */
+class SymmetricEncryptor {
+public:
+ virtual ~SymmetricEncryptor() = default;
+
+ /**
+ * Process a chunk of data from <in> and store the ciphertext in <out>.
+ * Returns the number of bytes written to <out> which will not exceed <outLen>.
+ * Because <inLen> for this and/or previous calls may not lie on a block boundary,
+ * the number of bytes written to <out> may be more or less than <inLen>.
+ */
+ virtual StatusWith<size_t> update(const uint8_t* in,
+ size_t inLen,
+ uint8_t* out,
+ size_t outLen) = 0;
+
+ /**
+ * Append Additional AuthenticatedData (AAD) to a GCM encryption stream.
+ */
+ virtual Status addAuthenticatedData(const uint8_t* in, size_t inLen) = 0;
+
+ /**
+ * Finish an encryption by flushing any buffered bytes for a partial cipherblock to <out>.
+ * Returns the number of bytes written, not to exceed <outLen>.
+ */
+ virtual StatusWith<size_t> finalize(uint8_t* out, size_t outLen) = 0;
+
+ /**
+ * For aesMode::gcm, writes the GCM tag to <out>.
+ * Returns the number of bytes used, not to exceed <outLen>.
+ */
+ virtual StatusWith<size_t> finalizeTag(uint8_t* out, size_t outLen) = 0;
+
+ /**
+ * Create an instance of a SymmetricEncryptor object from the currently available
+ * cipher engine (e.g. OpenSSL).
+ */
+ static StatusWith<std::unique_ptr<SymmetricEncryptor>> create(const SymmetricKey& key,
+ aesMode mode,
+ const uint8_t* iv,
+ size_t inLen);
+};
+
+/**
+ * Interface to a symmetric cryptography engine.
+ * For use with encrypting payloads.
+ */
+class SymmetricDecryptor {
+public:
+ virtual ~SymmetricDecryptor() = default;
+
+ /**
+ * Process a chunk of data from <in> and store the decrypted text in <out>.
+ * Returns the number of bytes written to <out> which will not exceed <outLen>.
+ * Because <inLen> for this and/or previous calls may not lie on a block boundary,
+ * the number of bytes written to <out> may be more or less than <inLen>.
+ */
+ virtual StatusWith<size_t> update(const uint8_t* in,
+ size_t inLen,
+ uint8_t* out,
+ size_t outLen) = 0;
+
+ /**
+ * For aesMode::gcm, inform the cipher engine of additional authenticated data (AAD).
+ */
+ virtual Status addAuthenticatedData(const uint8_t* in, size_t inLen) = 0;
+
+ /**
+ * For aesMode::gcm, informs the cipher engine of the GCM tag associated with this data stream.
+ */
+ virtual Status updateTag(const uint8_t* tag, size_t tagLen) = 0;
+
+ /**
+ * Finish an decryption by flushing any buffered bytes for a partial cipherblock to <out>.
+ * Returns the number of bytes written, not to exceed <outLen>.
+ */
+ virtual StatusWith<size_t> finalize(uint8_t* out, size_t outLen) = 0;
+
+ /**
+ * Create an instance of a SymmetricDecryptor object from the currently available
+ * cipher engine (e.g. OpenSSL).
+ */
+ static StatusWith<std::unique_ptr<SymmetricDecryptor>> create(const SymmetricKey& key,
+ aesMode mode,
+ const uint8_t* iv,
+ size_t ivLen);
+};
+
+/**
+ * Returns a list of cipher modes supported by the cipher engine.
+ * e.g. {"AES256-CBC", "AES256-GCM"}
+ */
+std::set<std::string> getSupportedSymmetricAlgorithms();
+
+/**
+ * Generate a quantity of random bytes from the cipher engine.
+ */
+Status engineRandBytes(uint8_t* buffer, size_t len);
+
+} // namespace crypto
+} // namespace mongo
diff --git a/src/mongo/crypto/symmetric_crypto_apple.cpp b/src/mongo/crypto/symmetric_crypto_apple.cpp
new file mode 100644
index 00000000000..9ca5c9c0b1e
--- /dev/null
+++ b/src/mongo/crypto/symmetric_crypto_apple.cpp
@@ -0,0 +1,183 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include <CommonCrypto/CommonCryptor.h>
+#include <Security/Security.h>
+#include <memory>
+#include <set>
+
+#include "mongo/base/init.h"
+#include "mongo/base/status.h"
+#include "mongo/crypto/symmetric_crypto.h"
+#include "mongo/crypto/symmetric_key.h"
+#include "mongo/platform/random.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/str.h"
+
+namespace mongo {
+namespace crypto {
+
+namespace {
+
+template <typename Parent>
+class SymmetricImplApple : public Parent {
+public:
+ SymmetricImplApple(const SymmetricKey& key, aesMode mode, const uint8_t* iv, size_t ivLen)
+ : _ctx(nullptr, CCCryptorRelease) {
+ static_assert(
+ std::is_same<Parent, SymmetricEncryptor>::value ||
+ std::is_same<Parent, SymmetricDecryptor>::value,
+ "SymmetricImplApple must inherit from SymmetricEncryptor or SymmetricDecryptor");
+
+ uassert(ErrorCodes::UnsupportedFormat,
+ "Native crypto on this platform only supports AES256-CBC",
+ mode == aesMode::cbc);
+
+ // Note: AES256 uses a 256byte keysize,
+ // but is still functionally a 128bit block algorithm.
+ // Therefore we expect a 128 bit block length.
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "Invalid ivlen for selected algorithm, expected "
+ << kCCBlockSizeAES128
+ << ", got "
+ << ivLen,
+ ivLen == kCCBlockSizeAES128);
+
+ CCCryptorRef context = nullptr;
+ constexpr auto op =
+ std::is_same<Parent, SymmetricEncryptor>::value ? kCCEncrypt : kCCDecrypt;
+ const auto status = CCCryptorCreate(op,
+ kCCAlgorithmAES,
+ kCCOptionPKCS7Padding,
+ key.getKey(),
+ key.getKeySize(),
+ iv,
+ &context);
+ uassert(ErrorCodes::UnknownError,
+ str::stream() << "CCCryptorCreate failure: " << status,
+ status == kCCSuccess);
+
+ _ctx.reset(context);
+ }
+
+ StatusWith<size_t> update(const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen) final {
+ size_t outUsed = 0;
+ const auto status = CCCryptorUpdate(_ctx.get(), in, inLen, out, outLen, &outUsed);
+ if (status != kCCSuccess) {
+ return Status(ErrorCodes::UnknownError,
+ str::stream() << "Unable to perform CCCryptorUpdate: " << status);
+ }
+ return outUsed;
+ }
+
+ Status addAuthenticatedData(const uint8_t* in, size_t inLen) final {
+ fassert(51128, inLen == 0);
+ return Status::OK();
+ }
+
+ StatusWith<size_t> finalize(uint8_t* out, size_t outLen) final {
+ size_t outUsed = 0;
+ const auto status = CCCryptorFinal(_ctx.get(), out, outLen, &outUsed);
+ if (status != kCCSuccess) {
+ return Status(ErrorCodes::UnknownError,
+ str::stream() << "Unable to perform CCCryptorFinal: " << status);
+ }
+ return outUsed;
+ }
+
+private:
+ std::unique_ptr<_CCCryptor, decltype(&CCCryptorRelease)> _ctx;
+};
+
+class SymmetricEncryptorApple : public SymmetricImplApple<SymmetricEncryptor> {
+public:
+ using SymmetricImplApple::SymmetricImplApple;
+
+ StatusWith<size_t> finalizeTag(uint8_t* out, size_t outLen) final {
+ // CBC only, no tag to create.
+ return 0;
+ }
+};
+
+
+class SymmetricDecryptorApple : public SymmetricImplApple<SymmetricDecryptor> {
+public:
+ using SymmetricImplApple::SymmetricImplApple;
+
+ Status updateTag(const uint8_t* tag, size_t tagLen) final {
+ // CBC only, no tag to verify.
+ if (tagLen > 0) {
+ return {ErrorCodes::BadValue, "Unexpected tag for non-gcm cipher"};
+ }
+ return Status::OK();
+ }
+};
+
+} // namespace
+
+std::set<std::string> getSupportedSymmetricAlgorithms() {
+ return {aes256CBCName};
+}
+
+Status engineRandBytes(uint8_t* buffer, size_t len) {
+ auto result = SecRandomCopyBytes(kSecRandomDefault, len, buffer);
+ if (result != errSecSuccess) {
+ return {ErrorCodes::UnknownError,
+ str::stream() << "Failed generating random bytes: " << result};
+ } else {
+ return Status::OK();
+ }
+}
+
+StatusWith<std::unique_ptr<SymmetricEncryptor>> SymmetricEncryptor::create(const SymmetricKey& key,
+ aesMode mode,
+ const uint8_t* iv,
+ size_t ivLen) try {
+ std::unique_ptr<SymmetricEncryptor> encryptor =
+ std::make_unique<SymmetricEncryptorApple>(key, mode, iv, ivLen);
+ return std::move(encryptor);
+} catch (const DBException& e) {
+ return e.toStatus();
+}
+
+StatusWith<std::unique_ptr<SymmetricDecryptor>> SymmetricDecryptor::create(const SymmetricKey& key,
+ aesMode mode,
+ const uint8_t* iv,
+ size_t ivLen) try {
+ std::unique_ptr<SymmetricDecryptor> decryptor =
+ std::make_unique<SymmetricDecryptorApple>(key, mode, iv, ivLen);
+ return std::move(decryptor);
+} catch (const DBException& e) {
+ return e.toStatus();
+}
+
+} // namespace crypto
+} // namespace mongo
diff --git a/src/mongo/crypto/symmetric_crypto_openssl.cpp b/src/mongo/crypto/symmetric_crypto_openssl.cpp
new file mode 100644
index 00000000000..6329331a511
--- /dev/null
+++ b/src/mongo/crypto/symmetric_crypto_openssl.cpp
@@ -0,0 +1,255 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kStorage
+
+#include "mongo/platform/basic.h"
+
+#include <memory>
+#include <openssl/rand.h>
+#include <set>
+
+#include "mongo/base/data_cursor.h"
+#include "mongo/base/init.h"
+#include "mongo/base/status.h"
+#include "mongo/crypto/symmetric_crypto.h"
+#include "mongo/crypto/symmetric_key.h"
+#include "mongo/platform/random.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/log.h"
+#include "mongo/util/net/ssl_manager.h"
+#include "mongo/util/str.h"
+
+namespace mongo {
+namespace crypto {
+
+namespace {
+template <typename Init>
+void initCipherContext(
+ EVP_CIPHER_CTX* ctx, const SymmetricKey& key, aesMode mode, const uint8_t* iv, Init init) {
+ const auto keySize = key.getKeySize();
+ const EVP_CIPHER* cipher = nullptr;
+ if (keySize == sym256KeySize) {
+ if (mode == crypto::aesMode::cbc) {
+ cipher = EVP_get_cipherbyname("aes-256-cbc");
+ } else if (mode == crypto::aesMode::gcm) {
+ cipher = EVP_get_cipherbyname("aes-256-gcm");
+ }
+ }
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "Unrecognized AES key size/cipher mode. Size: " << keySize << " Mode: "
+ << getStringFromCipherMode(mode),
+ cipher);
+
+ const bool initOk = (1 == init(ctx, cipher, nullptr, key.getKey(), iv));
+ uassert(ErrorCodes::UnknownError,
+ str::stream() << SSLManagerInterface::getSSLErrorMessage(ERR_get_error()),
+ initOk);
+}
+
+class SymmetricEncryptorOpenSSL : public SymmetricEncryptor {
+public:
+ SymmetricEncryptorOpenSSL(const SymmetricKey& key, aesMode mode, const uint8_t* iv)
+ : _ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free), _mode(mode) {
+ initCipherContext(_ctx.get(), key, mode, iv, EVP_EncryptInit_ex);
+ }
+
+ StatusWith<size_t> update(const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen) final {
+ int len = 0;
+ if (1 != EVP_EncryptUpdate(_ctx.get(), out, &len, in, inLen)) {
+ return Status(ErrorCodes::UnknownError,
+ str::stream()
+ << SSLManagerInterface::getSSLErrorMessage(ERR_get_error()));
+ }
+ return static_cast<size_t>(len);
+ }
+
+ Status addAuthenticatedData(const uint8_t* in, size_t inLen) final {
+ fassert(51126, _mode == crypto::aesMode::gcm);
+
+ auto swUpdate = update(in, inLen, nullptr, 0);
+ if (!swUpdate.isOK()) {
+ return swUpdate.getStatus();
+ }
+
+ const auto len = swUpdate.getValue();
+ if (len != inLen) {
+ return {ErrorCodes::InternalError,
+ str::stream() << "Unexpected write length while appending AAD: " << len};
+ }
+
+ return Status::OK();
+ }
+
+ StatusWith<size_t> finalize(uint8_t* out, size_t outLen) final {
+ int len = 0;
+ if (1 != EVP_EncryptFinal_ex(_ctx.get(), out, &len)) {
+ return Status(ErrorCodes::UnknownError,
+ str::stream()
+ << SSLManagerInterface::getSSLErrorMessage(ERR_get_error()));
+ }
+ return static_cast<size_t>(len);
+ }
+
+ StatusWith<size_t> finalizeTag(uint8_t* out, size_t outLen) final {
+ if (_mode == aesMode::gcm) {
+#ifdef EVP_CTRL_GCM_GET_TAG
+ if (1 != EVP_CIPHER_CTX_ctrl(_ctx.get(), EVP_CTRL_GCM_GET_TAG, outLen, out)) {
+ return Status(ErrorCodes::UnknownError,
+ str::stream()
+ << SSLManagerInterface::getSSLErrorMessage(ERR_get_error()));
+ }
+ return crypto::aesGCMTagSize;
+#else
+ return Status(ErrorCodes::UnsupportedFormat, "GCM support is not available");
+#endif
+ }
+
+ // Otherwise, not a tagged cipher mode, write nothing.
+ return 0;
+ }
+
+private:
+ std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> _ctx;
+ const aesMode _mode;
+};
+
+class SymmetricDecryptorOpenSSL : public SymmetricDecryptor {
+public:
+ SymmetricDecryptorOpenSSL(const SymmetricKey& key, aesMode mode, const uint8_t* iv)
+ : _ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free), _mode(mode) {
+ initCipherContext(_ctx.get(), key, mode, iv, EVP_DecryptInit_ex);
+ }
+
+ StatusWith<size_t> update(const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen) final {
+ int len = 0;
+ if (1 != EVP_DecryptUpdate(_ctx.get(), out, &len, in, inLen)) {
+ return Status(ErrorCodes::UnknownError,
+ str::stream()
+ << SSLManagerInterface::getSSLErrorMessage(ERR_get_error()));
+ }
+ return static_cast<size_t>(len);
+ }
+
+ Status addAuthenticatedData(const uint8_t* in, size_t inLen) final {
+ fassert(51125, _mode == crypto::aesMode::gcm);
+
+ auto swUpdate = update(in, inLen, nullptr, 0);
+ if (!swUpdate.isOK()) {
+ return swUpdate.getStatus();
+ }
+
+ const auto len = swUpdate.getValue();
+ if (len != inLen) {
+ return {ErrorCodes::InternalError,
+ str::stream() << "Unexpected write length while appending AAD: " << len};
+ }
+
+ return Status::OK();
+ }
+
+ StatusWith<size_t> finalize(uint8_t* out, size_t outLen) final {
+ int len = 0;
+ if (1 != EVP_DecryptFinal_ex(_ctx.get(), out, &len)) {
+ return Status(ErrorCodes::UnknownError,
+ str::stream()
+ << SSLManagerInterface::getSSLErrorMessage(ERR_get_error()));
+ }
+ return static_cast<size_t>(len);
+ }
+
+ Status updateTag(const uint8_t* tag, size_t tagLen) final {
+ // validateEncryptionOption asserts that platforms without GCM will never start in GCM mode
+ if (_mode == aesMode::gcm) {
+#ifdef EVP_CTRL_GCM_GET_TAG
+ if (1 != EVP_CIPHER_CTX_ctrl(
+ _ctx.get(), EVP_CTRL_GCM_SET_TAG, tagLen, const_cast<uint8_t*>(tag))) {
+ return Status(ErrorCodes::UnknownError,
+ str::stream()
+ << "Unable to set GCM tag: "
+ << SSLManagerInterface::getSSLErrorMessage(ERR_get_error()));
+ }
+#else
+ return {ErrorCodes::UnsupportedFormat, "GCM support is not available"};
+#endif
+ } else if (tagLen != 0) {
+ return {ErrorCodes::BadValue, "Unexpected tag for non-gcm cipher"};
+ }
+
+ return Status::OK();
+ }
+
+private:
+ std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> _ctx;
+ const aesMode _mode;
+};
+
+} // namespace
+
+std::set<std::string> getSupportedSymmetricAlgorithms() {
+#if defined(EVP_CTRL_GCM_GET_TAG) && !defined(__APPLE__)
+ return {aes256CBCName, aes256GCMName};
+#else
+ return {aes256CBCName};
+#endif
+}
+
+Status engineRandBytes(uint8_t* buffer, size_t len) {
+ if (RAND_bytes(reinterpret_cast<unsigned char*>(buffer), len) == 1) {
+ return Status::OK();
+ }
+ return {ErrorCodes::UnknownError,
+ str::stream() << "Unable to acquire random bytes from OpenSSL: "
+ << SSLManagerInterface::getSSLErrorMessage(ERR_get_error())};
+}
+
+StatusWith<std::unique_ptr<SymmetricEncryptor>> SymmetricEncryptor::create(const SymmetricKey& key,
+ aesMode mode,
+ const uint8_t* iv,
+ size_t ivLen) try {
+ std::unique_ptr<SymmetricEncryptor> encryptor =
+ std::make_unique<SymmetricEncryptorOpenSSL>(key, mode, iv);
+ return std::move(encryptor);
+} catch (const DBException& e) {
+ return e.toStatus();
+}
+
+StatusWith<std::unique_ptr<SymmetricDecryptor>> SymmetricDecryptor::create(const SymmetricKey& key,
+ aesMode mode,
+ const uint8_t* iv,
+ size_t ivLen) try {
+ std::unique_ptr<SymmetricDecryptor> decryptor =
+ std::make_unique<SymmetricDecryptorOpenSSL>(key, mode, iv);
+ return std::move(decryptor);
+} catch (const DBException& e) {
+ return e.toStatus();
+}
+
+} // namespace crypto
+} // namespace mongo
diff --git a/src/mongo/crypto/symmetric_crypto_windows.cpp b/src/mongo/crypto/symmetric_crypto_windows.cpp
new file mode 100644
index 00000000000..25dd5f304b8
--- /dev/null
+++ b/src/mongo/crypto/symmetric_crypto_windows.cpp
@@ -0,0 +1,335 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kStorage
+
+#include "mongo/platform/basic.h"
+
+#include <memory>
+#include <vector>
+
+#include "mongo/base/secure_allocator.h"
+#include "mongo/base/status.h"
+#include "mongo/crypto/symmetric_crypto.h"
+#include "mongo/crypto/symmetric_key.h"
+#include "mongo/platform/shared_library.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/log.h"
+#include "mongo/util/str.h"
+
+namespace mongo {
+namespace crypto {
+
+namespace {
+
+// RtlNtStatusToDosError function, only available via GetProcAddress
+using pRtlNtStatusToDosError = ULONG(WINAPI*)(NTSTATUS Status);
+
+std::string statusWithDescription(NTSTATUS status) {
+ auto swLib = SharedLibrary::create("ntdll.dll");
+ if (swLib.getStatus().isOK()) {
+
+ auto swFunc =
+ swLib.getValue()->getFunctionAs<pRtlNtStatusToDosError>("RtlNtStatusToDosError");
+ if (swFunc.isOK()) {
+
+ pRtlNtStatusToDosError RtlNtStatusToDosErrorFunc = swFunc.getValue();
+ ULONG errorCode = RtlNtStatusToDosErrorFunc(status);
+
+ if (errorCode != ERROR_MR_MID_NOT_FOUND) {
+ return errnoWithDescription(errorCode);
+ }
+ }
+ }
+
+ return str::stream() << "Failed to get error message for NTSTATUS: " << status;
+}
+
+struct AlgoInfo {
+ BCRYPT_ALG_HANDLE algo;
+ DWORD keyBlobSize;
+};
+
+/**
+ * Initialize crypto algorithms from default system CNG provider.
+ */
+class BCryptCryptoLoader {
+public:
+ BCryptCryptoLoader() {
+ loadAlgo(_algoAESCBC, BCRYPT_AES_ALGORITHM, BCRYPT_CHAIN_MODE_CBC);
+
+ auto status =
+ ::BCryptOpenAlgorithmProvider(&_random, BCRYPT_RNG_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
+ invariant(status == STATUS_SUCCESS);
+ }
+
+ ~BCryptCryptoLoader() {
+ invariant(BCryptCloseAlgorithmProvider(_algoAESCBC.algo, 0) == STATUS_SUCCESS);
+ invariant(BCryptCloseAlgorithmProvider(_random, 0) == STATUS_SUCCESS);
+ }
+
+ AlgoInfo& getAlgo(aesMode mode) {
+ switch (mode) {
+ case aesMode::cbc:
+ return _algoAESCBC;
+ default:
+ MONGO_UNREACHABLE;
+ }
+ }
+
+ BCRYPT_ALG_HANDLE getRandom() {
+ return _random;
+ }
+
+private:
+ void loadAlgo(AlgoInfo& algo, const wchar_t* name, const wchar_t* chainingMode) {
+ NTSTATUS status = BCryptOpenAlgorithmProvider(&algo.algo, name, MS_PRIMITIVE_PROVIDER, 0);
+ invariant(status == STATUS_SUCCESS);
+
+ status = BCryptSetProperty(algo.algo,
+ BCRYPT_CHAINING_MODE,
+ reinterpret_cast<PUCHAR>(const_cast<wchar_t*>(chainingMode)),
+ sizeof(wchar_t) * wcslen(chainingMode),
+ 0);
+ invariant(status == STATUS_SUCCESS);
+
+ DWORD cbOutput = sizeof(algo.keyBlobSize);
+ status = BCryptGetProperty(algo.algo,
+ BCRYPT_OBJECT_LENGTH,
+ reinterpret_cast<PUCHAR>(&algo.keyBlobSize),
+ cbOutput,
+ &cbOutput,
+ 0);
+ invariant(status == STATUS_SUCCESS);
+ }
+
+private:
+ AlgoInfo _algoAESCBC;
+ BCRYPT_ALG_HANDLE _random;
+};
+
+static BCryptCryptoLoader& getBCryptCryptoLoader() {
+ static BCryptCryptoLoader loader;
+ return loader;
+}
+
+/**
+ * Base class to support initialize symmetric key buffers and state.
+ */
+template <typename Parent>
+class SymmetricImplWindows : public Parent {
+public:
+ SymmetricImplWindows(const SymmetricKey& key, aesMode mode, const uint8_t* iv, size_t ivLen)
+ : _keyHandle(INVALID_HANDLE_VALUE), _mode(mode) {
+ AlgoInfo& algo = getBCryptCryptoLoader().getAlgo(mode);
+
+
+ // Initialize key storage buffers
+ _keyObjectBuf->resize(algo.keyBlobSize);
+
+ SecureVector<unsigned char> keyBlob;
+ keyBlob->reserve(sizeof(BCRYPT_KEY_DATA_BLOB_HEADER) + key.getKeySize());
+
+ BCRYPT_KEY_DATA_BLOB_HEADER blobHeader;
+ blobHeader.dwMagic = BCRYPT_KEY_DATA_BLOB_MAGIC;
+ blobHeader.dwVersion = BCRYPT_KEY_DATA_BLOB_VERSION1;
+ blobHeader.cbKeyData = key.getKeySize();
+
+ std::copy(reinterpret_cast<uint8_t*>(&blobHeader),
+ reinterpret_cast<uint8_t*>(&blobHeader) + sizeof(BCRYPT_KEY_DATA_BLOB_HEADER),
+ std::back_inserter(*keyBlob));
+
+ std::copy(key.getKey(), key.getKey() + key.getKeySize(), std::back_inserter(*keyBlob));
+
+ NTSTATUS status = BCryptImportKey(algo.algo,
+ NULL,
+ BCRYPT_KEY_DATA_BLOB,
+ &_keyHandle,
+ _keyObjectBuf->data(),
+ _keyObjectBuf->size(),
+ keyBlob->data(),
+ keyBlob->size(),
+ 0);
+ uassert(ErrorCodes::OperationFailed,
+ str::stream() << "ImportKey failed: " << statusWithDescription(status),
+ status == STATUS_SUCCESS);
+
+ std::copy(iv, iv + ivLen, std::back_inserter(_iv));
+ }
+
+ ~SymmetricImplWindows() {
+ if (_keyHandle != INVALID_HANDLE_VALUE) {
+ BCryptDestroyKey(_keyHandle);
+ }
+ }
+
+ Status addAuthenticatedData(const uint8_t* in, size_t inLen) final {
+ fassert(51127, inLen == 0);
+ return Status::OK();
+ }
+
+protected:
+ const aesMode _mode;
+
+ // Buffers for key data
+ BCRYPT_KEY_HANDLE _keyHandle;
+
+ SecureVector<unsigned char> _keyObjectBuf;
+
+ // Buffer for CBC data
+ std::vector<unsigned char> _iv;
+};
+
+class SymmetricEncryptorWindows : public SymmetricImplWindows<SymmetricEncryptor> {
+public:
+ using SymmetricImplWindows::SymmetricImplWindows;
+
+ StatusWith<size_t> update(const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen) final {
+ ULONG len = 0;
+
+ NTSTATUS status = BCryptEncrypt(_keyHandle,
+ const_cast<PUCHAR>(in),
+ inLen,
+ NULL,
+ _iv.data(),
+ _iv.size(),
+ out,
+ outLen,
+ &len,
+ BCRYPT_BLOCK_PADDING);
+
+ if (status != STATUS_SUCCESS) {
+ return Status{ErrorCodes::OperationFailed,
+ str::stream() << "Encrypt failed: " << statusWithDescription(status)};
+ }
+
+ return static_cast<size_t>(len);
+ }
+
+ StatusWith<size_t> finalize(uint8_t* out, size_t outLen) final {
+ // No finalize needed
+ return 0;
+ }
+
+ StatusWith<size_t> finalizeTag(uint8_t* out, size_t outLen) final {
+ // Not a tagged cipher mode, write nothing.
+ return 0;
+ }
+};
+
+class SymmetricDecryptorWindows : public SymmetricImplWindows<SymmetricDecryptor> {
+public:
+ using SymmetricImplWindows::SymmetricImplWindows;
+
+ StatusWith<size_t> update(const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen) final {
+ ULONG len = 0;
+
+ NTSTATUS status = BCryptDecrypt(_keyHandle,
+ const_cast<PUCHAR>(in),
+ inLen,
+ NULL,
+ _iv.data(),
+ _iv.size(),
+ out,
+ outLen,
+ &len,
+ BCRYPT_BLOCK_PADDING);
+
+ if (status != STATUS_SUCCESS) {
+ return Status{ErrorCodes::OperationFailed,
+ str::stream() << "Decrypt failed: " << statusWithDescription(status)};
+ }
+
+ return static_cast<size_t>(len);
+ }
+
+ StatusWith<size_t> finalize(uint8_t* out, size_t outLen) final {
+ return 0;
+ }
+
+ Status updateTag(const uint8_t* tag, size_t tagLen) final {
+ return Status::OK();
+ }
+};
+
+} // namespace
+
+std::set<std::string> getSupportedSymmetricAlgorithms() {
+ return {aes256CBCName};
+}
+
+Status engineRandBytes(uint8_t* buffer, size_t len) {
+ NTSTATUS status = BCryptGenRandom(getBCryptCryptoLoader().getRandom(), buffer, len, 0);
+ if (status == STATUS_SUCCESS) {
+ return Status::OK();
+ }
+
+ return {ErrorCodes::UnknownError,
+ str::stream() << "Unable to acquire random bytes from BCrypt: "
+ << statusWithDescription(status)};
+}
+
+StatusWith<std::unique_ptr<SymmetricEncryptor>> SymmetricEncryptor::create(const SymmetricKey& key,
+ aesMode mode,
+ const uint8_t* iv,
+ size_t ivLen) {
+ if (mode != aesMode::cbc) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ "Native crypto on this platform only supports AES256-CBC");
+ }
+
+ try {
+ std::unique_ptr<SymmetricEncryptor> encryptor =
+ std::make_unique<SymmetricEncryptorWindows>(key, mode, iv, ivLen);
+ return std::move(encryptor);
+ } catch (const DBException& e) {
+ return e.toStatus();
+ }
+}
+
+StatusWith<std::unique_ptr<SymmetricDecryptor>> SymmetricDecryptor::create(const SymmetricKey& key,
+ aesMode mode,
+ const uint8_t* iv,
+ size_t ivLen) {
+ if (mode != aesMode::cbc) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ "Native crypto on this platform only supports AES256-CBC");
+ }
+
+ try {
+ std::unique_ptr<SymmetricDecryptor> decryptor =
+ std::make_unique<SymmetricDecryptorWindows>(key, mode, iv, ivLen);
+ return std::move(decryptor);
+ } catch (const DBException& e) {
+ return e.toStatus();
+ }
+}
+
+} // namespace crypto
+} // namespace mongo
diff --git a/src/mongo/crypto/symmetric_key.cpp b/src/mongo/crypto/symmetric_key.cpp
new file mode 100644
index 00000000000..a2bf2526bea
--- /dev/null
+++ b/src/mongo/crypto/symmetric_key.cpp
@@ -0,0 +1,100 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kStorage
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/crypto/symmetric_key.h"
+
+#include <cstring>
+
+#include "mongo/crypto/symmetric_crypto.h"
+#include "mongo/util/log.h"
+#include "mongo/util/secure_zero_memory.h"
+#include "mongo/util/str.h"
+
+namespace mongo {
+
+std::string SymmetricKeyId::_initStrRep() const {
+ return str::stream() << _name << " (" << _id << ")";
+}
+
+const std::string& SymmetricKeyId::toString() const {
+ if (!_strRep.empty()) {
+ return _strRep;
+ } else {
+ return _name;
+ }
+}
+
+SymmetricKey::SymmetricKey(const uint8_t* key,
+ size_t keySize,
+ uint32_t algorithm,
+ SymmetricKeyId keyId,
+ uint32_t initializationCount)
+ : _algorithm(algorithm),
+ _keySize(keySize),
+ _key(key, key + keySize),
+ _keyId(std::move(keyId)),
+ _initializationCount(initializationCount),
+ _invocationCount(0) {
+ if (_keySize < crypto::minKeySize || _keySize > crypto::maxKeySize) {
+ error() << "Attempt to construct symmetric key of invalid size: " << _keySize;
+ return;
+ }
+}
+
+SymmetricKey::SymmetricKey(SecureVector<uint8_t> key, uint32_t algorithm, SymmetricKeyId keyId)
+ : _algorithm(algorithm),
+ _keySize(key->size()),
+ _key(std::move(key)),
+ _keyId(std::move(keyId)),
+ _initializationCount(1),
+ _invocationCount(0) {}
+
+SymmetricKey::SymmetricKey(SymmetricKey&& sk)
+ : _algorithm(sk._algorithm),
+ _keySize(sk._keySize),
+ _key(std::move(sk._key)),
+ _keyId(std::move(sk._keyId)),
+ _initializationCount(sk._initializationCount),
+ _invocationCount(sk._invocationCount.load()) {}
+
+SymmetricKey& SymmetricKey::operator=(SymmetricKey&& sk) {
+ _algorithm = sk._algorithm;
+ _keySize = sk._keySize;
+ _key = std::move(sk._key);
+ _keyId = std::move(sk._keyId);
+ _initializationCount = sk._initializationCount;
+ _invocationCount.store(sk._invocationCount.load());
+
+ return *this;
+}
+} // namespace mongo
diff --git a/src/mongo/crypto/symmetric_key.h b/src/mongo/crypto/symmetric_key.h
new file mode 100644
index 00000000000..b09a35778b2
--- /dev/null
+++ b/src/mongo/crypto/symmetric_key.h
@@ -0,0 +1,146 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+
+#include "mongo/base/secure_allocator.h"
+#include "mongo/platform/atomic_word.h"
+
+namespace mongo {
+class Status;
+
+class SymmetricKeyId {
+public:
+ using id_type = std::uint64_t;
+
+ template <typename StringLike>
+ SymmetricKeyId(const StringLike& name, id_type id)
+ : _id(id), _name(name), _strRep(_initStrRep()) {}
+
+ template <typename StringLike>
+ SymmetricKeyId(const StringLike& name) : _name(name) {}
+
+ const std::string& toString() const;
+
+ bool operator==(const SymmetricKeyId& other) const {
+ return _id == other._id && _name == other._name;
+ }
+
+ bool operator!=(const SymmetricKeyId& other) const {
+ return !(*this == other);
+ }
+
+ const boost::optional<id_type>& id() const {
+ return _id;
+ }
+
+ const std::string& name() const {
+ return _name;
+ }
+
+private:
+ std::string _initStrRep() const;
+
+ boost::optional<id_type> _id;
+ std::string _name;
+ std::string _strRep;
+};
+
+/**
+ * Class representing a symmetric key
+ */
+class SymmetricKey {
+ SymmetricKey(const SymmetricKey&) = delete;
+ SymmetricKey& operator=(const SymmetricKey&) = delete;
+
+public:
+ SymmetricKey(const uint8_t* key,
+ size_t keySize,
+ uint32_t algorithm,
+ SymmetricKeyId keyId,
+ uint32_t initializationCount);
+ SymmetricKey(SecureVector<uint8_t> key, uint32_t algorithm, SymmetricKeyId keyId);
+
+ SymmetricKey(SymmetricKey&&);
+ SymmetricKey& operator=(SymmetricKey&&);
+
+ ~SymmetricKey() = default;
+
+ int getAlgorithm() const {
+ return _algorithm;
+ }
+
+ size_t getKeySize() const {
+ return _keySize;
+ }
+
+ // Return the number of times the key has been retrieved from the key store
+ uint32_t getInitializationCount() const {
+ return _initializationCount;
+ }
+
+ uint32_t incrementAndGetInitializationCount() {
+ _initializationCount++;
+ return _initializationCount;
+ }
+
+ uint64_t getAndIncrementInvocationCount() const {
+ return _invocationCount.fetchAndAdd(1);
+ }
+
+ const uint8_t* getKey() const {
+ return _key->data();
+ }
+
+ const SymmetricKeyId& getKeyId() const {
+ return _keyId;
+ }
+
+ void setKeyId(SymmetricKeyId keyId) {
+ _keyId = std::move(keyId);
+ }
+
+private:
+ int _algorithm;
+
+ size_t _keySize;
+
+ SecureVector<uint8_t> _key;
+
+ SymmetricKeyId _keyId;
+
+ uint32_t _initializationCount;
+ mutable AtomicWord<unsigned long long> _invocationCount;
+};
+
+using UniqueSymmetricKey = std::unique_ptr<SymmetricKey>;
+} // namespace mongo