2021-07-11 09:22:30 +00:00
|
|
|
#include <Disks/DiskEncrypted.h>
|
2021-05-18 20:48:16 +00:00
|
|
|
|
|
|
|
#if USE_SSL
|
2021-07-11 09:22:30 +00:00
|
|
|
#include <Disks/DiskFactory.h>
|
|
|
|
#include <IO/FileEncryptionCommon.h>
|
|
|
|
#include <IO/ReadBufferFromEncryptedFile.h>
|
|
|
|
#include <IO/WriteBufferFromEncryptedFile.h>
|
2021-07-11 19:26:39 +00:00
|
|
|
#include <boost/algorithm/hex.hpp>
|
2021-05-14 21:52:51 +00:00
|
|
|
|
2021-05-17 16:58:51 +00:00
|
|
|
|
2021-05-13 06:37:05 +00:00
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
|
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
2021-07-11 19:26:39 +00:00
|
|
|
extern const int BAD_ARGUMENTS;
|
2021-05-13 06:37:05 +00:00
|
|
|
extern const int INCORRECT_DISK_INDEX;
|
2021-07-23 08:18:11 +00:00
|
|
|
extern const int DATA_ENCRYPTION_ERROR;
|
2021-05-13 06:37:05 +00:00
|
|
|
}
|
|
|
|
|
2021-07-11 19:26:39 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
using DiskEncryptedPtr = std::shared_ptr<DiskEncrypted>;
|
|
|
|
using namespace FileEncryption;
|
|
|
|
|
2021-07-17 13:35:15 +00:00
|
|
|
constexpr Algorithm DEFAULT_ENCRYPTION_ALGORITHM = Algorithm::AES_128_CTR;
|
|
|
|
|
|
|
|
String unhexKey(const String & hex)
|
2021-07-11 19:26:39 +00:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
return boost::algorithm::unhex(hex);
|
|
|
|
}
|
|
|
|
catch (const std::exception &)
|
|
|
|
{
|
2021-07-17 13:35:15 +00:00
|
|
|
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cannot read key_hex, check for valid characters [0-9a-fA-F] and length");
|
2021-07-11 19:26:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct DiskEncryptedSettings
|
|
|
|
{
|
|
|
|
DiskPtr wrapped_disk;
|
|
|
|
String path_on_wrapped_disk;
|
2021-07-23 08:18:11 +00:00
|
|
|
std::unordered_map<UInt64, String> keys;
|
|
|
|
UInt64 current_key_id;
|
|
|
|
Algorithm current_algorithm;
|
2021-07-11 19:26:39 +00:00
|
|
|
|
|
|
|
DiskEncryptedSettings(
|
|
|
|
const String & disk_name, const Poco::Util::AbstractConfiguration & config, const String & config_prefix, const DisksMap & map)
|
|
|
|
{
|
2021-07-17 13:35:15 +00:00
|
|
|
try
|
2021-07-11 19:26:39 +00:00
|
|
|
{
|
2021-07-23 08:18:11 +00:00
|
|
|
current_algorithm = DEFAULT_ENCRYPTION_ALGORITHM;
|
2021-07-17 13:35:15 +00:00
|
|
|
if (config.has(config_prefix + ".algorithm"))
|
2021-07-23 08:18:11 +00:00
|
|
|
parseFromString(current_algorithm, config.getString(config_prefix + ".algorithm"));
|
2021-07-17 13:35:15 +00:00
|
|
|
|
2021-07-23 08:18:11 +00:00
|
|
|
Strings config_keys;
|
|
|
|
config.keys(config_prefix, config_keys);
|
|
|
|
for (const std::string & config_key : config_keys)
|
2021-07-17 13:35:15 +00:00
|
|
|
{
|
2021-07-23 08:18:11 +00:00
|
|
|
String key;
|
|
|
|
UInt64 key_id;
|
|
|
|
|
|
|
|
if ((config_key == "key") || config_key.starts_with("key["))
|
|
|
|
{
|
|
|
|
key = config.getString(config_prefix + "." + config_key, "");
|
|
|
|
key_id = config.getUInt64(config_prefix + "." + config_key + "[@id]", 0);
|
|
|
|
}
|
|
|
|
else if ((config_key == "key_hex") || config_key.starts_with("key_hex["))
|
|
|
|
{
|
|
|
|
key = unhexKey(config.getString(config_prefix + "." + config_key, ""));
|
|
|
|
key_id = config.getUInt64(config_prefix + "." + config_key + "[@id]", 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
|
|
|
|
auto it = keys.find(key_id);
|
|
|
|
if (it != keys.end())
|
|
|
|
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Multiple keys have the same ID {}", key_id);
|
|
|
|
keys[key_id] = key;
|
2021-07-17 13:35:15 +00:00
|
|
|
}
|
|
|
|
|
2021-07-23 08:18:11 +00:00
|
|
|
if (keys.empty())
|
|
|
|
throw Exception(ErrorCodes::BAD_ARGUMENTS, "No keys, an encrypted disk needs keys to work", current_key_id);
|
|
|
|
|
|
|
|
current_key_id = config.getUInt64(config_prefix + ".current_key_id", 0);
|
|
|
|
if (!keys.contains(current_key_id))
|
|
|
|
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Key with ID {} not found", current_key_id);
|
|
|
|
FileEncryption::checkKeySize(current_algorithm, keys[current_key_id].size());
|
2021-07-17 13:35:15 +00:00
|
|
|
|
|
|
|
String wrapped_disk_name = config.getString(config_prefix + ".disk", "");
|
|
|
|
if (wrapped_disk_name.empty())
|
|
|
|
throw Exception(
|
|
|
|
ErrorCodes::BAD_ARGUMENTS,
|
|
|
|
"Name of the wrapped disk must not be empty. An encrypted disk is a wrapper over another disk");
|
|
|
|
|
|
|
|
auto wrapped_disk_it = map.find(wrapped_disk_name);
|
|
|
|
if (wrapped_disk_it == map.end())
|
|
|
|
throw Exception(
|
|
|
|
ErrorCodes::BAD_ARGUMENTS,
|
|
|
|
"The wrapped disk must have been announced earlier. No disk with name {}",
|
|
|
|
wrapped_disk_name);
|
|
|
|
wrapped_disk = wrapped_disk_it->second;
|
|
|
|
|
|
|
|
path_on_wrapped_disk = config.getString(config_prefix + ".path", "");
|
|
|
|
}
|
|
|
|
catch (Exception & e)
|
|
|
|
{
|
|
|
|
e.addMessage("Disk " + disk_name);
|
|
|
|
throw;
|
2021-07-11 19:26:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
bool inline isSameDiskType(const IDisk & one, const IDisk & another)
|
|
|
|
{
|
|
|
|
return typeid(one) == typeid(another);
|
|
|
|
}
|
|
|
|
}
|
2021-05-13 06:37:05 +00:00
|
|
|
|
|
|
|
class DiskEncryptedReservation : public IReservation
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
DiskEncryptedReservation(DiskEncryptedPtr disk_, std::unique_ptr<IReservation> reservation_)
|
|
|
|
: disk(std::move(disk_)), reservation(std::move(reservation_))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
UInt64 getSize() const override { return reservation->getSize(); }
|
|
|
|
|
2021-05-19 22:06:25 +00:00
|
|
|
DiskPtr getDisk(size_t i) const override
|
|
|
|
{
|
2021-05-13 06:37:05 +00:00
|
|
|
if (i != 0)
|
|
|
|
throw Exception("Can't use i != 0 with single disk reservation", ErrorCodes::INCORRECT_DISK_INDEX);
|
|
|
|
return disk;
|
|
|
|
}
|
|
|
|
|
|
|
|
Disks getDisks() const override { return {disk}; }
|
|
|
|
|
|
|
|
void update(UInt64 new_size) override { reservation->update(new_size); }
|
|
|
|
|
|
|
|
private:
|
|
|
|
DiskEncryptedPtr disk;
|
|
|
|
std::unique_ptr<IReservation> reservation;
|
|
|
|
};
|
|
|
|
|
|
|
|
ReservationPtr DiskEncrypted::reserve(UInt64 bytes)
|
|
|
|
{
|
2021-05-14 21:52:51 +00:00
|
|
|
auto reservation = delegate->reserve(bytes);
|
2021-05-13 06:37:05 +00:00
|
|
|
if (!reservation)
|
|
|
|
return {};
|
|
|
|
return std::make_unique<DiskEncryptedReservation>(std::static_pointer_cast<DiskEncrypted>(shared_from_this()), std::move(reservation));
|
|
|
|
}
|
|
|
|
|
2021-07-17 13:35:15 +00:00
|
|
|
DiskEncrypted::DiskEncrypted(
|
|
|
|
const String & name_,
|
|
|
|
DiskPtr wrapped_disk_,
|
|
|
|
const String & path_on_wrapped_disk_,
|
2021-07-23 08:18:11 +00:00
|
|
|
const std::unordered_map<UInt64, String> & keys_,
|
|
|
|
UInt64 current_key_id_,
|
|
|
|
FileEncryption::Algorithm current_algorithm_)
|
|
|
|
: DiskDecorator(wrapped_disk_)
|
|
|
|
, name(name_)
|
|
|
|
, disk_path(path_on_wrapped_disk_)
|
|
|
|
, keys(keys_)
|
|
|
|
, current_key_id(current_key_id_)
|
|
|
|
, current_algorithm(current_algorithm_)
|
2021-07-11 09:22:30 +00:00
|
|
|
{
|
|
|
|
initialize();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiskEncrypted::initialize()
|
|
|
|
{
|
2021-07-11 19:26:39 +00:00
|
|
|
disk_absolute_path = delegate->getPath() + disk_path;
|
|
|
|
|
2021-07-11 09:22:30 +00:00
|
|
|
// use wrapped_disk as an EncryptedDisk store
|
|
|
|
if (disk_path.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (disk_path.back() != '/')
|
2021-07-11 19:26:39 +00:00
|
|
|
throw Exception("Disk path must ends with '/', but '" + disk_path + "' doesn't.", ErrorCodes::BAD_ARGUMENTS);
|
2021-07-11 09:22:30 +00:00
|
|
|
|
|
|
|
delegate->createDirectories(disk_path);
|
|
|
|
}
|
|
|
|
|
2021-07-23 08:18:11 +00:00
|
|
|
|
|
|
|
String DiskEncrypted::getKey(UInt64 key_id) const
|
|
|
|
{
|
|
|
|
auto it = keys.find(key_id);
|
|
|
|
if (it == keys.end())
|
|
|
|
throw Exception(ErrorCodes::DATA_ENCRYPTION_ERROR, "Key with ID {} not found", key_id);
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
2021-07-11 19:26:39 +00:00
|
|
|
void DiskEncrypted::copy(const String & from_path, const std::shared_ptr<IDisk> & to_disk, const String & to_path)
|
|
|
|
{
|
|
|
|
/// Check if we can copy the file without deciphering.
|
|
|
|
if (isSameDiskType(*this, *to_disk))
|
|
|
|
{
|
|
|
|
/// Disk type is the same, check if the key is the same too.
|
|
|
|
if (auto * to_encrypted_disk = typeid_cast<DiskEncrypted *>(to_disk.get()))
|
|
|
|
{
|
2021-07-23 08:18:11 +00:00
|
|
|
if (keys == to_encrypted_disk->keys)
|
2021-07-11 19:26:39 +00:00
|
|
|
{
|
2021-07-23 08:18:11 +00:00
|
|
|
/// Keys are the same so we can simply copy the encrypted file.
|
2021-07-17 13:35:15 +00:00
|
|
|
delegate->copy(wrappedPath(from_path), to_encrypted_disk->delegate, to_encrypted_disk->wrappedPath(to_path));
|
2021-07-11 19:26:39 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Copy the file through buffers with deciphering.
|
|
|
|
copyThroughBuffers(from_path, to_disk, to_path);
|
|
|
|
}
|
|
|
|
|
2021-05-13 06:37:05 +00:00
|
|
|
std::unique_ptr<ReadBufferFromFileBase> DiskEncrypted::readFile(
|
|
|
|
const String & path,
|
|
|
|
size_t buf_size,
|
|
|
|
size_t estimated_size,
|
|
|
|
size_t aio_threshold,
|
|
|
|
size_t mmap_threshold,
|
2021-05-13 16:21:45 +00:00
|
|
|
MMappedFileCache * mmap_cache) const
|
|
|
|
{
|
2021-07-23 08:18:11 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
auto wrapped_path = wrappedPath(path);
|
|
|
|
auto buffer = delegate->readFile(wrapped_path, buf_size, estimated_size, aio_threshold, mmap_threshold, mmap_cache);
|
|
|
|
FileEncryption::Header header;
|
|
|
|
header.read(*buffer);
|
|
|
|
String key = getKey(header.key_id);
|
|
|
|
if (calculateKeyHash(key) != header.key_hash)
|
|
|
|
throw Exception(ErrorCodes::DATA_ENCRYPTION_ERROR, "Wrong key, could not read file");
|
|
|
|
return std::make_unique<ReadBufferFromEncryptedFile>(buf_size, std::move(buffer), key, header);
|
|
|
|
}
|
|
|
|
catch (Exception & e)
|
|
|
|
{
|
|
|
|
e.addMessage("File " + quoteString(path));
|
|
|
|
throw;
|
|
|
|
}
|
2021-05-13 06:37:05 +00:00
|
|
|
}
|
|
|
|
|
2021-07-11 09:22:30 +00:00
|
|
|
std::unique_ptr<WriteBufferFromFileBase> DiskEncrypted::writeFile(const String & path, size_t buf_size, WriteMode mode)
|
2021-05-13 16:21:45 +00:00
|
|
|
{
|
2021-07-23 08:18:11 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
auto wrapped_path = wrappedPath(path);
|
|
|
|
FileEncryption::Header header;
|
|
|
|
String key;
|
|
|
|
UInt64 old_file_size = 0;
|
|
|
|
if (mode == WriteMode::Append && exists(path))
|
|
|
|
{
|
|
|
|
old_file_size = getFileSize(path);
|
|
|
|
if (old_file_size)
|
|
|
|
{
|
|
|
|
/// Append mode: we continue to use the same header.
|
|
|
|
auto read_buffer = delegate->readFile(wrapped_path, FileEncryption::Header::kSize);
|
|
|
|
header.read(*read_buffer);
|
|
|
|
key = getKey(header.key_id);
|
|
|
|
if (calculateKeyHash(key) != header.key_hash)
|
|
|
|
throw Exception(ErrorCodes::DATA_ENCRYPTION_ERROR, "Wrong key, could not append file");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!old_file_size)
|
|
|
|
{
|
|
|
|
/// Rewrite mode: we generate a new header.
|
|
|
|
key = getKey(current_key_id);
|
|
|
|
header.algorithm = current_algorithm;
|
|
|
|
header.key_id = current_key_id;
|
|
|
|
header.key_hash = calculateKeyHash(key);
|
|
|
|
header.init_vector = InitVector::random();
|
|
|
|
}
|
|
|
|
auto buffer = delegate->writeFile(wrapped_path, buf_size, mode);
|
|
|
|
return std::make_unique<WriteBufferFromEncryptedFile>(buf_size, std::move(buffer), key, header, old_file_size);
|
|
|
|
}
|
|
|
|
catch (Exception & e)
|
2021-05-17 16:15:21 +00:00
|
|
|
{
|
2021-07-23 08:18:11 +00:00
|
|
|
e.addMessage("File " + quoteString(path));
|
|
|
|
throw;
|
2021-05-14 21:52:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
size_t DiskEncrypted::getFileSize(const String & path) const
|
|
|
|
{
|
|
|
|
auto wrapped_path = wrappedPath(path);
|
|
|
|
size_t size = delegate->getFileSize(wrapped_path);
|
2021-07-23 08:18:11 +00:00
|
|
|
return size > FileEncryption::Header::kSize ? (size - FileEncryption::Header::kSize) : 0;
|
2021-05-13 06:37:05 +00:00
|
|
|
}
|
|
|
|
|
2021-05-13 16:21:45 +00:00
|
|
|
void DiskEncrypted::truncateFile(const String & path, size_t size)
|
|
|
|
{
|
|
|
|
auto wrapped_path = wrappedPath(path);
|
2021-07-23 08:18:11 +00:00
|
|
|
delegate->truncateFile(wrapped_path, size ? (size + FileEncryption::Header::kSize) : 0);
|
2021-05-13 06:37:05 +00:00
|
|
|
}
|
|
|
|
|
2021-05-13 16:21:45 +00:00
|
|
|
SyncGuardPtr DiskEncrypted::getDirectorySyncGuard(const String & path) const
|
|
|
|
{
|
|
|
|
auto wrapped_path = wrappedPath(path);
|
2021-05-14 21:52:51 +00:00
|
|
|
return delegate->getDirectorySyncGuard(wrapped_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiskEncrypted::applyNewSettings(
|
|
|
|
const Poco::Util::AbstractConfiguration & config,
|
2021-06-18 05:36:50 +00:00
|
|
|
ContextPtr /*context*/,
|
2021-05-14 21:52:51 +00:00
|
|
|
const String & config_prefix,
|
|
|
|
const DisksMap & map)
|
|
|
|
{
|
2021-07-11 19:26:39 +00:00
|
|
|
DiskEncryptedSettings settings{name, config, config_prefix, map};
|
|
|
|
delegate = settings.wrapped_disk;
|
|
|
|
disk_path = settings.path_on_wrapped_disk;
|
2021-07-23 08:18:11 +00:00
|
|
|
keys = settings.keys;
|
|
|
|
current_key_id = settings.current_key_id;
|
|
|
|
current_algorithm = settings.current_algorithm;
|
2021-05-14 21:52:51 +00:00
|
|
|
initialize();
|
2021-05-13 06:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void registerDiskEncrypted(DiskFactory & factory)
|
|
|
|
{
|
|
|
|
auto creator = [](const String & name,
|
|
|
|
const Poco::Util::AbstractConfiguration & config,
|
|
|
|
const String & config_prefix,
|
2021-06-18 05:36:50 +00:00
|
|
|
ContextPtr /*context*/,
|
2021-07-11 19:26:39 +00:00
|
|
|
const DisksMap & map) -> DiskPtr
|
|
|
|
{
|
|
|
|
DiskEncryptedSettings settings{name, config, config_prefix, map};
|
2021-07-17 13:35:15 +00:00
|
|
|
return std::make_shared<DiskEncrypted>(
|
2021-07-23 08:18:11 +00:00
|
|
|
name,
|
|
|
|
settings.wrapped_disk,
|
|
|
|
settings.path_on_wrapped_disk,
|
|
|
|
settings.keys,
|
|
|
|
settings.current_key_id,
|
|
|
|
settings.current_algorithm);
|
2021-05-13 06:37:05 +00:00
|
|
|
};
|
|
|
|
factory.registerDiskType("encrypted", creator);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2021-05-18 20:48:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
#endif
|