/src/duckdb/src/common/encryption_functions.cpp
Line | Count | Source |
1 | | #include "duckdb/common/exception/conversion_exception.hpp" |
2 | | #include "duckdb/common/encryption_key_manager.hpp" |
3 | | #include "duckdb/common/encryption_functions.hpp" |
4 | | #include "duckdb/main/attached_database.hpp" |
5 | | #include "mbedtls_wrapper.hpp" |
6 | | #include "duckdb/storage/storage_manager.hpp" |
7 | | #include "duckdb/storage/storage_info.hpp" |
8 | | #include "duckdb/common/typedefs.hpp" |
9 | | |
10 | | namespace duckdb { |
11 | | |
12 | | constexpr uint32_t AdditionalAuthenticatedData::INITIAL_AAD_CAPACITY; |
13 | | |
14 | 0 | AdditionalAuthenticatedData::~AdditionalAuthenticatedData() = default; |
15 | | |
16 | 0 | data_ptr_t AdditionalAuthenticatedData::data() const { |
17 | 0 | return additional_authenticated_data->GetData(); |
18 | 0 | } |
19 | | |
20 | 0 | idx_t AdditionalAuthenticatedData::size() const { |
21 | 0 | return additional_authenticated_data->GetPosition(); |
22 | 0 | } |
23 | | |
24 | 0 | void AdditionalAuthenticatedData::WriteStringData(const std::string &val) const { |
25 | 0 | additional_authenticated_data->WriteData(reinterpret_cast<const_data_ptr_t>(val.data()), val.size()); |
26 | 0 | } |
27 | | |
28 | 0 | EncryptionEngine::EncryptionEngine() { |
29 | 0 | } |
30 | | |
31 | 0 | EncryptionEngine::~EncryptionEngine() { |
32 | 0 | } |
33 | | |
34 | 0 | const_data_ptr_t EncryptionEngine::GetKeyFromCache(DatabaseInstance &db, const string &key_name) { |
35 | 0 | auto &keys = EncryptionKeyManager::Get(db); |
36 | 0 | return keys.GetKey(key_name); |
37 | 0 | } |
38 | | |
39 | 0 | bool EncryptionEngine::ContainsKey(DatabaseInstance &db, const string &key_name) { |
40 | 0 | auto &keys = EncryptionKeyManager::Get(db); |
41 | 0 | return keys.HasKey(key_name); |
42 | 0 | } |
43 | | |
44 | 0 | void EncryptionEngine::AddKeyToCache(DatabaseInstance &db, data_ptr_t key, const string &key_name, bool wipe) { |
45 | 0 | auto &keys = EncryptionKeyManager::Get(db); |
46 | 0 | if (!keys.HasKey(key_name)) { |
47 | 0 | keys.AddKey(key_name, key); |
48 | 0 | } else { |
49 | 0 | duckdb_mbedtls::MbedTlsWrapper::AESStateMBEDTLS::SecureClearData(key, |
50 | 0 | MainHeader::DEFAULT_ENCRYPTION_KEY_LENGTH); |
51 | 0 | } |
52 | 0 | } |
53 | | |
54 | 0 | string EncryptionEngine::AddKeyToCache(DatabaseInstance &db, data_ptr_t key) { |
55 | 0 | auto &keys = EncryptionKeyManager::Get(db); |
56 | 0 | const auto key_id = keys.GenerateRandomKeyID(); |
57 | |
|
58 | 0 | if (!keys.HasKey(key_id)) { |
59 | 0 | keys.AddKey(key_id, key); |
60 | 0 | } else { |
61 | 0 | duckdb_mbedtls::MbedTlsWrapper::AESStateMBEDTLS::SecureClearData(key, |
62 | 0 | MainHeader::DEFAULT_ENCRYPTION_KEY_LENGTH); |
63 | 0 | } |
64 | |
|
65 | 0 | return key_id; |
66 | 0 | } |
67 | | |
68 | 0 | void EncryptionEngine::AddTempKeyToCache(DatabaseInstance &db) { |
69 | | //! Add a temporary key to the cache |
70 | 0 | const auto length = MainHeader::DEFAULT_ENCRYPTION_KEY_LENGTH; |
71 | 0 | data_t temp_key[length]; |
72 | | |
73 | | // we cannot generate temporary keys with read-only enabled |
74 | 0 | auto metadata = make_uniq<EncryptionStateMetadata>(EncryptionTypes::GCM, length, EncryptionTypes::V0_1); |
75 | 0 | auto encryption_state = db.GetEncryptionUtil(false)->CreateEncryptionState(std::move(metadata)); |
76 | 0 | encryption_state->GenerateRandomData(temp_key, length); |
77 | |
|
78 | 0 | string key_id = "temp_key"; |
79 | 0 | AddKeyToCache(db, temp_key, key_id); |
80 | 0 | } |
81 | | |
82 | | void EncryptionEngine::EncryptBlock(AttachedDatabase &attached_db, const string &key_id, FileBuffer &block, |
83 | 0 | FileBuffer &temp_buffer_manager, uint64_t delta) { |
84 | 0 | auto &db = attached_db.GetDatabase(); |
85 | 0 | data_ptr_t block_offset_internal = temp_buffer_manager.InternalBuffer(); |
86 | 0 | auto encrypt_key = GetKeyFromCache(db, key_id); |
87 | 0 | auto version = attached_db.GetStorageManager().GetEncryptionVersion(); |
88 | 0 | auto cipher = attached_db.GetStorageManager().GetCipher(); |
89 | 0 | auto metadata = make_uniq<EncryptionStateMetadata>(cipher, MainHeader::DEFAULT_ENCRYPTION_KEY_LENGTH, version); |
90 | 0 | auto encryption_state = db.GetEncryptionUtil(attached_db.IsReadOnly())->CreateEncryptionState(std::move(metadata)); |
91 | |
|
92 | 0 | EncryptionTag tag; |
93 | 0 | EncryptionNonce nonce(cipher, version); |
94 | 0 | encryption_state->GenerateRandomData(nonce.data(), nonce.size()); |
95 | | |
96 | | //! store the nonce at the start of the block |
97 | 0 | memcpy(block_offset_internal, nonce.data(), nonce.size()); |
98 | 0 | encryption_state->InitializeEncryption(nonce, encrypt_key); |
99 | |
|
100 | 0 | auto checksum_offset = block.InternalBuffer() + delta; |
101 | 0 | auto encryption_checksum_offset = block_offset_internal + delta; |
102 | 0 | auto size = block.size + Storage::DEFAULT_BLOCK_HEADER_SIZE; |
103 | | |
104 | | //! encrypt the data including the checksum |
105 | 0 | auto aes_res = encryption_state->Process(checksum_offset, size, encryption_checksum_offset, size); |
106 | |
|
107 | 0 | if (aes_res != size) { |
108 | 0 | throw IOException("Block encryption failure: in- and output size differ (%llu/%llu)", size, aes_res); |
109 | 0 | } |
110 | | |
111 | | //! Finalize and extract the tag |
112 | 0 | encryption_state->Finalize(block.InternalBuffer() + delta, 0, tag.data(), tag.size()); |
113 | | |
114 | | //! store the generated tag *behind* the nonce (but still at the beginning of the block) |
115 | 0 | memcpy(block_offset_internal + nonce.size(), tag.data(), tag.size()); |
116 | 0 | } |
117 | | |
118 | | void EncryptionEngine::DecryptBlock(AttachedDatabase &attached_db, const string &key_id, data_ptr_t internal_buffer, |
119 | 0 | uint64_t block_size, uint64_t delta) { |
120 | | //! initialize encryption state |
121 | 0 | auto &db = attached_db.GetDatabase(); |
122 | 0 | auto version = attached_db.GetStorageManager().GetEncryptionVersion(); |
123 | 0 | auto cipher = attached_db.GetStorageManager().GetCipher(); |
124 | 0 | auto metadata = make_uniq<EncryptionStateMetadata>(cipher, MainHeader::DEFAULT_ENCRYPTION_KEY_LENGTH, version); |
125 | 0 | auto decrypt_key = GetKeyFromCache(db, key_id); |
126 | 0 | auto encryption_state = db.GetEncryptionUtil(attached_db.IsReadOnly())->CreateEncryptionState(std::move(metadata)); |
127 | | |
128 | | //! load the stored nonce and tag |
129 | 0 | EncryptionTag tag; |
130 | 0 | EncryptionNonce nonce(cipher, version); |
131 | 0 | memcpy(nonce.data(), internal_buffer, nonce.size()); |
132 | 0 | memcpy(tag.data(), internal_buffer + nonce.size(), tag.size()); |
133 | | |
134 | | //! Initialize the decryption |
135 | 0 | encryption_state->InitializeDecryption(nonce, decrypt_key); |
136 | |
|
137 | 0 | auto checksum_offset = internal_buffer + delta; |
138 | 0 | auto size = block_size + Storage::DEFAULT_BLOCK_HEADER_SIZE; |
139 | | |
140 | | //! decrypt the block including the checksum |
141 | 0 | auto aes_res = encryption_state->Process(checksum_offset, size, checksum_offset, size); |
142 | |
|
143 | 0 | if (aes_res != block_size + Storage::DEFAULT_BLOCK_HEADER_SIZE) { |
144 | 0 | throw IOException("Block decryption failure: in- and output size differ (%llu/%llu)", size, aes_res); |
145 | 0 | } |
146 | | |
147 | | //! check the tag |
148 | 0 | encryption_state->Finalize(internal_buffer + delta, 0, tag.data(), tag.size()); |
149 | 0 | } |
150 | | |
151 | | void EncryptionEngine::EncryptTemporaryBuffer(DatabaseInstance &db, data_ptr_t buffer, idx_t buffer_size, |
152 | 0 | data_ptr_t metadata) { |
153 | 0 | if (!ContainsKey(db, "temp_key")) { |
154 | 0 | AddTempKeyToCache(db); |
155 | 0 | } |
156 | |
|
157 | 0 | auto temp_key = GetKeyFromCache(db, "temp_key"); |
158 | | // we cannot encrypt temp buffers in read-only mode |
159 | 0 | auto encryption_util = db.GetEncryptionUtil(false); |
160 | | // we hard-code GCM here for now, it's the safest and we don't know what is configured here |
161 | 0 | auto state_metadata = make_uniq<EncryptionStateMetadata>( |
162 | 0 | EncryptionTypes::GCM, MainHeader::DEFAULT_ENCRYPTION_KEY_LENGTH, EncryptionTypes::V0_1); |
163 | 0 | auto encryption_state = encryption_util->CreateEncryptionState(std::move(state_metadata)); |
164 | | |
165 | | // zero-out the metadata buffer |
166 | 0 | memset(metadata, 0, DEFAULT_ENCRYPTED_BUFFER_HEADER_SIZE); |
167 | |
|
168 | 0 | EncryptionTag tag; |
169 | 0 | EncryptionNonce nonce(EncryptionTypes::CipherType::GCM, EncryptionTypes::V0_1); |
170 | |
|
171 | 0 | encryption_state->GenerateRandomData(nonce.data(), nonce.size()); |
172 | | |
173 | | //! store the nonce at the start of metadata buffer |
174 | 0 | memcpy(metadata, nonce.data(), nonce.size()); |
175 | 0 | encryption_state->InitializeEncryption(nonce, temp_key); |
176 | |
|
177 | 0 | auto aes_res = encryption_state->Process(buffer, buffer_size, buffer, buffer_size); |
178 | |
|
179 | 0 | if (aes_res != buffer_size) { |
180 | 0 | throw IOException("Temporary buffer encryption failure: in- and output size differ (%llu/%llu)", buffer_size, |
181 | 0 | aes_res); |
182 | 0 | } |
183 | | |
184 | | //! Finalize and extract the tag |
185 | 0 | encryption_state->Finalize(buffer, 0, tag.data(), tag.size()); |
186 | | |
187 | | //! store the generated tag after consequetively the nonce |
188 | 0 | memcpy(metadata + nonce.size(), tag.data(), tag.size()); |
189 | | |
190 | | // check if tag is correctly stored |
191 | 0 | D_ASSERT(memcmp(tag.data(), metadata + nonce.size(), tag.size()) == 0); |
192 | 0 | } |
193 | | |
194 | | static void DecryptBuffer(EncryptionState &encryption_state, const_data_ptr_t temp_key, data_ptr_t buffer, |
195 | 0 | idx_t buffer_size, data_ptr_t metadata) { |
196 | | //! load the stored nonce and tag |
197 | 0 | EncryptionTag tag; |
198 | 0 | EncryptionNonce nonce(encryption_state.metadata->GetCipher(), encryption_state.metadata->GetVersion()); |
199 | 0 | memcpy(nonce.data(), metadata, nonce.size()); |
200 | 0 | memcpy(tag.data(), metadata + nonce.size(), tag.size()); |
201 | | |
202 | | //! Initialize the decryption |
203 | 0 | encryption_state.InitializeDecryption(nonce, temp_key); |
204 | |
|
205 | 0 | auto aes_res = encryption_state.Process(buffer, buffer_size, buffer, buffer_size); |
206 | |
|
207 | 0 | if (aes_res != buffer_size) { |
208 | 0 | throw IOException("Buffer decryption failure: in- and output size differ (%llu/%llu)", buffer_size, aes_res); |
209 | 0 | } |
210 | | |
211 | | //! check the tag |
212 | 0 | encryption_state.Finalize(buffer, 0, tag.data(), tag.size()); |
213 | 0 | } |
214 | | |
215 | | void EncryptionEngine::DecryptTemporaryBuffer(DatabaseInstance &db, data_ptr_t buffer, idx_t buffer_size, |
216 | 0 | data_ptr_t metadata) { |
217 | | //! initialize encryption state |
218 | 0 | auto encryption_util = db.GetEncryptionUtil(false); |
219 | 0 | auto temp_key = GetKeyFromCache(db, "temp_key"); |
220 | 0 | auto state_metadata = make_uniq<EncryptionStateMetadata>( |
221 | 0 | EncryptionTypes::GCM, MainHeader::DEFAULT_ENCRYPTION_KEY_LENGTH, EncryptionTypes::EncryptionVersion::V0_1); |
222 | 0 | auto encryption_state = encryption_util->CreateEncryptionState(std::move(state_metadata)); |
223 | |
|
224 | 0 | DecryptBuffer(*encryption_state, temp_key, buffer, buffer_size, metadata); |
225 | 0 | } |
226 | | |
227 | | } // namespace duckdb |