diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2019-06-18 00:19:33 -0400 |
---|---|---|
committer | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2019-06-18 00:19:33 -0400 |
commit | c436b8090417baf847143e97f5d221285b1898e1 (patch) | |
tree | bb5369a793c8ec6646b20e92b1a0f85b7979682f /src/mongo/crypto | |
parent | 5eda33f9fa40a1a17f9f63f904a8c147700d648c (diff) | |
download | mongo-c436b8090417baf847143e97f5d221285b1898e1.tar.gz |
SERVER-41644 Expose explicit encryption helpers in community shell
Diffstat (limited to 'src/mongo/crypto')
-rw-r--r-- | src/mongo/crypto/SConscript | 33 | ||||
-rw-r--r-- | src/mongo/crypto/aead_encryption.cpp | 391 | ||||
-rw-r--r-- | src/mongo/crypto/aead_encryption.h | 94 | ||||
-rw-r--r-- | src/mongo/crypto/aead_encryption_test.cpp | 152 | ||||
-rw-r--r-- | src/mongo/crypto/symmetric_crypto.cpp | 107 | ||||
-rw-r--r-- | src/mongo/crypto/symmetric_crypto.h | 196 | ||||
-rw-r--r-- | src/mongo/crypto/symmetric_crypto_apple.cpp | 183 | ||||
-rw-r--r-- | src/mongo/crypto/symmetric_crypto_openssl.cpp | 255 | ||||
-rw-r--r-- | src/mongo/crypto/symmetric_crypto_windows.cpp | 335 | ||||
-rw-r--r-- | src/mongo/crypto/symmetric_key.cpp | 100 | ||||
-rw-r--r-- | src/mongo/crypto/symmetric_key.h | 146 |
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 |