Skip to content

Commit bd1e706

Browse files
committed
PS-11106 [9.7] Component Percona keyring encrypted file
- Created component_percona_keyring_encrypted_file based on component_keyring_file. - Added cmake flag: WITH_COMPONENT_PERCONA_KEYRING_ENCRYPTED_FILE - New PBKDF2-based encrypt/decrypt API (aes.cc): Added aes_encrypt_pbkdf2 and aes_decrypt_pbkdf2 functions that derive a 256-bit AES key from a password using PKCS5_PBKDF2_HMAC (SHA-256). Refactored the internal EVP encrypt/decrypt logic into private helpers to avoid code duplication. - Encrypted backend (backend.cc): Renamed Keyring_file_backend to Keyring_encrypted_file_backend and wired in password-based encryption. On write, generates a random salt, IV, and iteration count; on read, parses v1 on-disk header ([version:1][salt:32][iterations:4 BE][iv:16][ciphertext]) and decrypts before JSON parsing. - Password config options (config.cc): The component configuration now requires exactly one of password (inline) or password_file (path to a file containing the password). Validation errors are emitted for missing, empty, or conflicting combinations. The keyring_component_status table reports <SET> or <NONE> for the password field.
1 parent fc79442 commit bd1e706

177 files changed

Lines changed: 27069 additions & 40 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

components/keyrings/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ ADD_SUBDIRECTORY(common)
2727

2828
# Keyring_file component
2929
ADD_SUBDIRECTORY(keyring_file)
30+
ADD_SUBDIRECTORY(percona_keyring_encrypted_file)
3031
ADD_SUBDIRECTORY(keyring_kmip)
3132
ADD_SUBDIRECTORY(keyring_kms)
3233
ADD_SUBDIRECTORY(keyring_vault)

components/keyrings/common/encryption/aes.cc

Lines changed: 113 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
2929

3030
#include <openssl/aes.h>
3131
#include <openssl/bio.h>
32+
#include <openssl/crypto.h>
3233
#include <openssl/err.h>
34+
#include <openssl/evp.h>
3335

3436
#include <openssl/sha.h>
3537

@@ -134,13 +136,14 @@ size_t get_ciphertext_size(size_t input_size, const Keyring_aes_opmode mode) {
134136
: input_size;
135137
}
136138

137-
aes_return_status aes_encrypt(const unsigned char *source,
138-
unsigned int source_length, unsigned char *dest,
139-
const unsigned char *key, unsigned int key_length,
140-
Keyring_aes_opmode mode, const unsigned char *iv,
141-
bool padding, size_t *encrypted_length) {
142-
if (encrypted_length == nullptr) return AES_OUTPUT_SIZE_NULL;
139+
namespace {
143140

141+
aes_return_status aes_evp_encrypt(const unsigned char *source,
142+
unsigned int source_length,
143+
unsigned char *dest, const EVP_CIPHER *cipher,
144+
const unsigned char *raw_key,
145+
const unsigned char *iv, bool padding,
146+
size_t *encrypted_length) {
144147
#if OPENSSL_VERSION_NUMBER < 0x10100000L
145148
EVP_CIPHER_CTX stack_ctx;
146149
EVP_CIPHER_CTX *ctx = &stack_ctx;
@@ -159,39 +162,30 @@ aes_return_status aes_encrypt(const unsigned char *source,
159162
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
160163
});
161164

162-
const EVP_CIPHER *cipher = aes_evp_type(mode);
163-
if (cipher == nullptr) return AES_INVALID_BLOCK_MODE;
164-
165-
/* The real key to be used for encryption */
166-
std::unique_ptr<unsigned char[]> rkey;
167-
size_t rkey_size;
168-
if (!aes_create_key(key, key_length, rkey, &rkey_size, mode))
169-
return AES_KEY_TRANSFORMATION_ERROR;
170-
171165
if (EVP_CIPHER_iv_length(cipher) > 0 && !iv) return AES_IV_EMPTY;
172166

173167
int u_len, f_len;
174168

175-
if (!EVP_EncryptInit(ctx, cipher, rkey.get(), iv))
169+
if (EVP_EncryptInit(ctx, cipher, raw_key, iv) == 0)
170+
return AES_ENCRYPTION_ERROR;
171+
if (EVP_CIPHER_CTX_set_padding(ctx, padding) == 0)
172+
return AES_ENCRYPTION_ERROR;
173+
if (EVP_EncryptUpdate(ctx, dest, &u_len, source, source_length) == 0)
176174
return AES_ENCRYPTION_ERROR;
177-
if (!EVP_CIPHER_CTX_set_padding(ctx, padding)) return AES_ENCRYPTION_ERROR;
178-
if (!EVP_EncryptUpdate(ctx, dest, &u_len, source, source_length))
175+
if (EVP_EncryptFinal(ctx, dest + u_len, &f_len) == 0)
179176
return AES_ENCRYPTION_ERROR;
180-
if (!EVP_EncryptFinal(ctx, dest + u_len, &f_len)) return AES_ENCRYPTION_ERROR;
181177

182178
/* All is well */
183179
*encrypted_length = static_cast<size_t>(u_len + f_len);
184180
return AES_OP_OK;
185181
}
186182

187-
aes_return_status aes_decrypt(const unsigned char *source,
188-
unsigned int source_length, unsigned char *dest,
189-
const unsigned char *key, unsigned int key_length,
190-
enum Keyring_aes_opmode mode,
191-
const unsigned char *iv, bool padding,
192-
size_t *decrypted_length) {
193-
if (decrypted_length == nullptr) return AES_OUTPUT_SIZE_NULL;
194-
183+
aes_return_status aes_evp_decrypt(const unsigned char *source,
184+
unsigned int source_length,
185+
unsigned char *dest, const EVP_CIPHER *cipher,
186+
const unsigned char *raw_key,
187+
const unsigned char *iv, bool padding,
188+
size_t *decrypted_length) {
195189
#if OPENSSL_VERSION_NUMBER < 0x10100000L
196190
EVP_CIPHER_CTX stack_ctx;
197191
EVP_CIPHER_CTX *ctx = &stack_ctx;
@@ -210,30 +204,109 @@ aes_return_status aes_decrypt(const unsigned char *source,
210204
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
211205
});
212206

213-
const EVP_CIPHER *cipher = aes_evp_type(mode);
214-
if (cipher == nullptr) return AES_INVALID_BLOCK_MODE;
215-
216-
/* The real key to be used for encryption */
217-
std::unique_ptr<unsigned char[]> rkey;
218-
size_t rkey_size;
219-
if (!aes_create_key(key, key_length, rkey, &rkey_size, mode))
220-
return AES_KEY_TRANSFORMATION_ERROR;
221-
222207
if (EVP_CIPHER_iv_length(cipher) > 0 && !iv) return AES_IV_EMPTY;
223208

224209
int u_len, f_len;
225210

226-
if (!EVP_DecryptInit(ctx, aes_evp_type(mode), rkey.get(), iv))
211+
if (EVP_DecryptInit(ctx, cipher, raw_key, iv) == 0)
212+
return AES_DECRYPTION_ERROR;
213+
if (EVP_CIPHER_CTX_set_padding(ctx, padding) == 0)
227214
return AES_DECRYPTION_ERROR;
228-
if (!EVP_CIPHER_CTX_set_padding(ctx, padding)) return AES_DECRYPTION_ERROR;
229-
if (!EVP_DecryptUpdate(ctx, dest, &u_len, source, source_length))
215+
if (EVP_DecryptUpdate(ctx, dest, &u_len, source, source_length) == 0)
230216
return AES_DECRYPTION_ERROR;
231-
if (!EVP_DecryptFinal_ex(ctx, dest + u_len, &f_len))
217+
if (EVP_DecryptFinal_ex(ctx, dest + u_len, &f_len) == 0)
232218
return AES_DECRYPTION_ERROR;
233219

234220
/* All is well */
235221
*decrypted_length = static_cast<size_t>(u_len + f_len);
236222
return AES_OP_OK;
237223
}
238224

225+
} // namespace
226+
227+
aes_return_status aes_encrypt(const unsigned char *source,
228+
unsigned int source_length, unsigned char *dest,
229+
const unsigned char *key, unsigned int key_length,
230+
Keyring_aes_opmode mode, const unsigned char *iv,
231+
bool padding, size_t *encrypted_length) {
232+
if (encrypted_length == nullptr) return AES_OUTPUT_SIZE_NULL;
233+
234+
const EVP_CIPHER *cipher = aes_evp_type(mode);
235+
if (cipher == nullptr) return AES_INVALID_BLOCK_MODE;
236+
237+
/* The real key to be used for encryption */
238+
std::unique_ptr<unsigned char[]> rkey;
239+
size_t rkey_size = 0;
240+
if (!aes_create_key(key, key_length, rkey, &rkey_size, mode))
241+
return AES_KEY_TRANSFORMATION_ERROR;
242+
243+
return aes_evp_encrypt(source, source_length, dest, cipher, rkey.get(), iv,
244+
padding, encrypted_length);
245+
}
246+
247+
aes_return_status aes_decrypt(const unsigned char *source,
248+
unsigned int source_length, unsigned char *dest,
249+
const unsigned char *key, unsigned int key_length,
250+
enum Keyring_aes_opmode mode,
251+
const unsigned char *iv, bool padding,
252+
size_t *decrypted_length) {
253+
if (decrypted_length == nullptr) return AES_OUTPUT_SIZE_NULL;
254+
255+
const EVP_CIPHER *cipher = aes_evp_type(mode);
256+
if (cipher == nullptr) return AES_INVALID_BLOCK_MODE;
257+
258+
/* The real key to be used for encryption */
259+
std::unique_ptr<unsigned char[]> rkey;
260+
size_t rkey_size = 0;
261+
if (!aes_create_key(key, key_length, rkey, &rkey_size, mode))
262+
return AES_KEY_TRANSFORMATION_ERROR;
263+
264+
return aes_evp_decrypt(source, source_length, dest, cipher, rkey.get(), iv,
265+
padding, decrypted_length);
266+
}
267+
268+
aes_return_status aes_encrypt_pbkdf2(
269+
const unsigned char *source, unsigned int source_length,
270+
unsigned char *dest, const unsigned char *password, size_t password_len,
271+
const unsigned char *salt, size_t salt_len, unsigned int iterations,
272+
Keyring_aes_opmode mode, const unsigned char *iv, bool padding,
273+
size_t *encrypted_length) {
274+
if (encrypted_length == nullptr) return AES_OUTPUT_SIZE_NULL;
275+
const EVP_CIPHER *cipher = aes_evp_type(mode);
276+
if (cipher == nullptr) return AES_INVALID_BLOCK_MODE;
277+
unsigned char raw_key[32];
278+
auto zero_key =
279+
create_scope_guard([&] { OPENSSL_cleanse(raw_key, sizeof(raw_key)); });
280+
if (PKCS5_PBKDF2_HMAC(reinterpret_cast<const char *>(password),
281+
static_cast<int>(password_len), salt,
282+
static_cast<int>(salt_len),
283+
static_cast<int>(iterations), EVP_sha256(),
284+
static_cast<int>(sizeof(raw_key)), raw_key) != 1)
285+
return AES_KEY_TRANSFORMATION_ERROR;
286+
return aes_evp_encrypt(source, source_length, dest, cipher, raw_key, iv,
287+
padding, encrypted_length);
288+
}
289+
290+
aes_return_status aes_decrypt_pbkdf2(
291+
const unsigned char *source, unsigned int source_length,
292+
unsigned char *dest, const unsigned char *password, size_t password_len,
293+
const unsigned char *salt, size_t salt_len, unsigned int iterations,
294+
Keyring_aes_opmode mode, const unsigned char *iv, bool padding,
295+
size_t *decrypted_length) {
296+
if (decrypted_length == nullptr) return AES_OUTPUT_SIZE_NULL;
297+
const EVP_CIPHER *cipher = aes_evp_type(mode);
298+
if (cipher == nullptr) return AES_INVALID_BLOCK_MODE;
299+
unsigned char raw_key[32];
300+
auto zero_key =
301+
create_scope_guard([&] { OPENSSL_cleanse(raw_key, sizeof(raw_key)); });
302+
if (PKCS5_PBKDF2_HMAC(reinterpret_cast<const char *>(password),
303+
static_cast<int>(password_len), salt,
304+
static_cast<int>(salt_len),
305+
static_cast<int>(iterations), EVP_sha256(),
306+
static_cast<int>(sizeof(raw_key)), raw_key) != 1)
307+
return AES_KEY_TRANSFORMATION_ERROR;
308+
return aes_evp_decrypt(source, source_length, dest, cipher, raw_key, iv,
309+
padding, decrypted_length);
310+
}
311+
239312
} // namespace keyring_common::aes_encryption

components/keyrings/common/encryption/aes.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,28 @@ aes_return_status aes_decrypt(const unsigned char *source,
9292
Keyring_aes_opmode mode, const unsigned char *iv,
9393
bool padding, size_t *decrypted_length);
9494

95+
/**
96+
Encrypt using a password: derives a 256-bit AES key via PBKDF2-HMAC-SHA256
97+
and then encrypts with the requested mode.
98+
*/
99+
aes_return_status aes_encrypt_pbkdf2(
100+
const unsigned char *source, unsigned int source_length,
101+
unsigned char *dest, const unsigned char *password, size_t password_len,
102+
const unsigned char *salt, size_t salt_len, unsigned int iterations,
103+
Keyring_aes_opmode mode, const unsigned char *iv, bool padding,
104+
size_t *encrypted_length);
105+
106+
/**
107+
Decrypt using a password: derives a 256-bit AES key via PBKDF2-HMAC-SHA256
108+
and then decrypts with the requested mode.
109+
*/
110+
aes_return_status aes_decrypt_pbkdf2(
111+
const unsigned char *source, unsigned int source_length,
112+
unsigned char *dest, const unsigned char *password, size_t password_len,
113+
const unsigned char *salt, size_t salt_len, unsigned int iterations,
114+
Keyring_aes_opmode mode, const unsigned char *iv, bool padding,
115+
size_t *decrypted_length);
116+
95117
} // namespace keyring_common::aes_encryption
96118

97119
#endif // !AES_INCLUDED
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright (c) 2021, 2026, Oracle and/or its affiliates.
2+
# Copyright (c) 2026 Percona LLC and/or its affiliates. All rights reserved.
3+
#
4+
# This program is free software; you can redistribute it and/or modify
5+
# it under the terms of the GNU General Public License, version 2.0,
6+
# as published by the Free Software Foundation.
7+
#
8+
# This program is designed to work with certain software (including
9+
# but not limited to OpenSSL) that is licensed under separate terms,
10+
# as designated in a particular file or component or in included license
11+
# documentation. The authors of MySQL hereby grant you an additional
12+
# permission to link the program and your derivative works with the
13+
# separately licensed software that they have either included with
14+
# the program or referenced in the documentation.
15+
#
16+
# This program is distributed in the hope that it will be useful,
17+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
# GNU General Public License, version 2.0, for more details.
20+
#
21+
# You should have received a copy of the GNU General Public License
22+
# along with this program; if not, write to the Free Software
23+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24+
25+
IF (NOT DEFINED WITH_COMPONENT_PERCONA_KEYRING_ENCRYPTED_FILE AND
26+
NOT DEFINED WITHOUT_COMPONENT_PERCONA_KEYRING_ENCRYPTED_FILE)
27+
SET(WITH_COMPONENT_PERCONA_KEYRING_ENCRYPTED_FILE 1)
28+
ENDIF()
29+
30+
IF(NOT WITH_COMPONENT_PERCONA_KEYRING_ENCRYPTED_FILE)
31+
RETURN()
32+
ENDIF()
33+
34+
ADD_DEFINITIONS(-DLOG_COMPONENT_TAG="component_percona_keyring_encrypted_file")
35+
36+
INCLUDE_DIRECTORIES(
37+
${CMAKE_CURRENT_SOURCE_DIR}
38+
${BOOST_PATCHES_DIR}
39+
${BOOST_INCLUDE_DIR}
40+
)
41+
42+
43+
SET(PERCONA_KEYRING_ENCRYPTED_FILE_SOURCE
44+
# Encryption handling
45+
service_implementation/keyring_encryption_service_definition.cc
46+
47+
# Generator handling
48+
service_implementation/keyring_generator_service_definition.cc
49+
50+
# Keyring load handling
51+
service_implementation/keyring_load_service_definition.cc
52+
53+
# Keys metadata iterator handling
54+
service_implementation/keyring_keys_metadata_iterator_service_definition.cc
55+
56+
# Metadata query handling
57+
service_implementation/keyring_metadata_query_service_definition.cc
58+
59+
# Reader handling
60+
service_implementation/keyring_reader_service_definition.cc
61+
62+
# Writer handling
63+
service_implementation/keyring_writer_service_definition.cc
64+
65+
# Backend handling
66+
backend/backend.cc
67+
68+
# Config handling
69+
config/config.cc
70+
71+
# Keyring file component handling
72+
percona_keyring_encrypted_file.cc
73+
74+
# Component callbacks
75+
component_callbacks.cc
76+
)
77+
78+
SET(PERCONA_KEYRING_ENCRYPTED_FILE_LIBRARIES
79+
keyring_common
80+
OpenSSL::SSL OpenSSL::Crypto
81+
library_mysys
82+
)
83+
84+
MYSQL_ADD_COMPONENT(percona_keyring_encrypted_file
85+
${PERCONA_KEYRING_ENCRYPTED_FILE_SOURCE}
86+
LINK_LIBRARIES ${PERCONA_KEYRING_ENCRYPTED_FILE_LIBRARIES}
87+
MODULE_ONLY
88+
)
89+
90+
TARGET_LINK_OPTIONS(component_percona_keyring_encrypted_file PRIVATE "${LINK_FLAG_NO_UNDEFINED}")
91+
92+
IF(APPLE)
93+
SET_TARGET_PROPERTIES(component_percona_keyring_encrypted_file PROPERTIES
94+
LINK_FLAGS "-undefined dynamic_lookup")
95+
ENDIF()
96+

0 commit comments

Comments
 (0)