diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:04:16 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:04:16 +0000 |
commit | a68fb2d8219f6bccc573009600e9f23e89226a5e (patch) | |
tree | d742d35d14ae816e99293d2b01face30e9f3a46b /plugin/file_key_management | |
parent | Initial commit. (diff) | |
download | mariadb-10.6-upstream.tar.xz mariadb-10.6-upstream.zip |
Adding upstream version 1:10.6.11.upstream/1%10.6.11upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugin/file_key_management')
-rw-r--r-- | plugin/file_key_management/CMakeLists.txt | 4 | ||||
-rw-r--r-- | plugin/file_key_management/file_key_management_plugin.cc | 207 | ||||
-rw-r--r-- | plugin/file_key_management/parser.cc | 411 | ||||
-rw-r--r-- | plugin/file_key_management/parser.h | 55 |
4 files changed, 677 insertions, 0 deletions
diff --git a/plugin/file_key_management/CMakeLists.txt b/plugin/file_key_management/CMakeLists.txt new file mode 100644 index 00000000..9b09da9b --- /dev/null +++ b/plugin/file_key_management/CMakeLists.txt @@ -0,0 +1,4 @@ +SET(FILE_KEY_MANAGEMENT_PLUGIN_SOURCES file_key_management_plugin.cc parser.cc) + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/sql) +MYSQL_ADD_PLUGIN(FILE_KEY_MANAGEMENT ${FILE_KEY_MANAGEMENT_PLUGIN_SOURCES} MODULE_ONLY) diff --git a/plugin/file_key_management/file_key_management_plugin.cc b/plugin/file_key_management/file_key_management_plugin.cc new file mode 100644 index 00000000..927045ce --- /dev/null +++ b/plugin/file_key_management/file_key_management_plugin.cc @@ -0,0 +1,207 @@ +/* Copyright (c) 2002, 2012, eperi GmbH. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +#include <my_global.h> +#include <typelib.h> +#include "parser.h" +#include <mysql/plugin_encryption.h> +#include <string.h> + +static char* filename; +static char* filekey; +static unsigned long encryption_algorithm; + +static const char *encryption_algorithm_names[]= +{ + "aes_cbc", +#ifdef HAVE_EncryptAes128Ctr + "aes_ctr", +#endif + 0 +}; + +static TYPELIB encryption_algorithm_typelib= +{ + array_elements(encryption_algorithm_names)-1,"", + encryption_algorithm_names, NULL +}; + + +static MYSQL_SYSVAR_STR(filename, filename, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Path and name of the key file.", + NULL, NULL, ""); + +static MYSQL_SYSVAR_STR(filekey, filekey, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Key to encrypt / decrypt the keyfile.", + NULL, NULL, ""); + +#ifdef HAVE_EncryptAes128Ctr +#define recommendation ", aes_ctr is the recommended one" +#else +#define recommendation "" +#endif +static MYSQL_SYSVAR_ENUM(encryption_algorithm, encryption_algorithm, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Encryption algorithm to use" recommendation ".", + NULL, NULL, 0, &encryption_algorithm_typelib); + +static struct st_mysql_sys_var* settings[] = { + MYSQL_SYSVAR(filename), + MYSQL_SYSVAR(filekey), + MYSQL_SYSVAR(encryption_algorithm), + NULL +}; + +std::map<unsigned int,keyentry> keys; + +static keyentry *get_key(unsigned int key_id) +{ + keyentry &key= keys[key_id]; + if (key.id == 0) + return 0; + return &key; +} + +/* the version is always the same, no automatic key rotation */ +static unsigned int get_latest_version(uint key_id) +{ + return get_key(key_id) ? 1 : ENCRYPTION_KEY_VERSION_INVALID; +} + +static unsigned int get_key_from_key_file(unsigned int key_id, + unsigned int key_version, unsigned char* dstbuf, unsigned *buflen) +{ + if (key_version != 1) + return ENCRYPTION_KEY_VERSION_INVALID; + + keyentry* entry = get_key(key_id); + + if (entry == NULL) + return ENCRYPTION_KEY_VERSION_INVALID; + + if (*buflen < entry->length) + { + *buflen= entry->length; + return ENCRYPTION_KEY_BUFFER_TOO_SMALL; + } + + *buflen= entry->length; + if (dstbuf) + memcpy(dstbuf, entry->key, entry->length); + + return 0; +} + +// let's simplify the condition below +#ifndef HAVE_EncryptAes128Gcm +#define MY_AES_GCM MY_AES_CTR +#ifndef HAVE_EncryptAes128Ctr +#define MY_AES_CTR MY_AES_CBC +#endif +#endif + +static inline enum my_aes_mode mode(int flags) +{ + /* + If encryption_algorithm is AES_CTR then + if no-padding, use AES_CTR + else use AES_GCM (like CTR but appends a "checksum" block) + else + use AES_CBC + */ + if (encryption_algorithm) + if (flags & ENCRYPTION_FLAG_NOPAD) + return MY_AES_CTR; + else + return MY_AES_GCM; + else + return MY_AES_CBC; +} + +static int ctx_init(void *ctx, const unsigned char* key, unsigned int klen, + const unsigned char* iv, unsigned int ivlen, int flags, + unsigned int key_id, unsigned int key_version) +{ + return my_aes_crypt_init(ctx, mode(flags), flags, key, klen, iv, ivlen); +} + +static int ctx_update(void *ctx, const unsigned char *src, unsigned int slen, + unsigned char *dst, unsigned int *dlen) +{ + return my_aes_crypt_update(ctx, src, slen, dst, dlen); +} + + +static int ctx_finish(void *ctx, unsigned char *dst, unsigned int *dlen) +{ + return my_aes_crypt_finish(ctx, dst, dlen); +} + +static unsigned int get_length(unsigned int slen, unsigned int key_id, + unsigned int key_version) +{ + return my_aes_get_size(mode(0), slen); +} + +static uint ctx_size(uint, uint) +{ + return my_aes_ctx_size(mode(0)); +} + +struct st_mariadb_encryption file_key_management_plugin= { + MariaDB_ENCRYPTION_INTERFACE_VERSION, + get_latest_version, + get_key_from_key_file, + ctx_size, + ctx_init, + ctx_update, + ctx_finish, + get_length +}; + +static int file_key_management_plugin_init(void *p) +{ + Parser parser(filename, filekey); + return parser.parse(&keys); +} + +static int file_key_management_plugin_deinit(void *p) +{ + keys.clear(); + return 0; +} + +/* + Plugin library descriptor +*/ +maria_declare_plugin(file_key_management) +{ + MariaDB_ENCRYPTION_PLUGIN, + &file_key_management_plugin, + "file_key_management", + "Denis Endro eperi GmbH", + "File-based key management plugin", + PLUGIN_LICENSE_GPL, + file_key_management_plugin_init, + file_key_management_plugin_deinit, + 0x0100 /* 1.0 */, + NULL, /* status variables */ + settings, + "1.0", + MariaDB_PLUGIN_MATURITY_STABLE +} +maria_declare_plugin_end; diff --git a/plugin/file_key_management/parser.cc b/plugin/file_key_management/parser.cc new file mode 100644 index 00000000..57e0139a --- /dev/null +++ b/plugin/file_key_management/parser.cc @@ -0,0 +1,411 @@ +/* Copyright (C) 2014 eperi GmbH. + Copyright (C) 2015 MariaDB Corporation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +/******************************************************************//** + @file Parser.cc + A class to parse the key file + +How it works... +The location and usage can be configured via the configuration file. +Example + +[mysqld] +... +file_key_management_filename = /home/mdb/keys.enc +file_key_management_filekey = secret +... + +The keys are read from a file. +The filename is set up via the file_key_management_filename +configuration value. +file_key_management_filename is used to configure the absolute +path to this file. + +Examples: +file_key_management_filename = \\\\unc\\keys.enc (windows share) +file_key_management_filename = e:/tmp/keys.enc (windows path) +file_key_management_filename = /tmp/keys.enc (linux path) + +The key file contains AES keys as hex-encoded strings. +Supported are keys of size 128, 192 or 256 bits. +Example: +1;F5502320F8429037B8DAEF761B189D12 +2;770A8A65DA156D24EE2A093277530142770A8A65DA156D24EE2A093277530142 + +1 is the key identifier which can be used for table creation, +it is followed by a AES key + +The key file could be encrypted and the key to decrypt the file can +be given with the optional file_key_management_filekey +parameter. + +The file key can also be located if FILE: is prepended to the +key. Then the following part is interpreted as absolute path to the +file containing the file key (which must be a text - not binary - string). + +Example: + +file_key_management_filekey = FILE:y:/secret256.enc + +If the key file can not be read at server startup, for example if the +file key is not present, the plugin will not start +access to encrypted tables will not be possible. + +Open SSL command line utility can be used to create an encrypted key file. +Example: +openssl enc -aes-256-cbc -md sha1 -k "secret" -in keys.txt -out keys.enc +***********************************************************************/ + +#include <my_global.h> +#include "parser.h" +#include <m_string.h> +#include <mysys_err.h> + +#define FILE_PREFIX "FILE:" +#define MAX_KEY_FILE_SIZE 1024*1024 +#define MAX_SECRET_SIZE 256 + +/* + The values below are what one gets after + openssl enc -aes-256-cbc -md sha1 -k "secret" -in keys.txt -out keys.enc +*/ +#define OpenSSL_prefix "Salted__" +#define OpenSSL_prefix_len (sizeof(OpenSSL_prefix) - 1) +#define OpenSSL_salt_len 8 +#define OpenSSL_key_len 32 +#define OpenSSL_iv_len 16 + +/** + Calculate key and iv from a given salt and secret as in the + openssl command-line tool + + @param salt [in] the given salt as extracted from the encrypted file + @param secret [in] the given secret as String, provided by the user + @param key [out] 32 Bytes of key are written to this pointer + @param iv [out] 16 Bytes of iv are written to this pointer +*/ + +void Parser::bytes_to_key(const unsigned char *salt, const char *input, + unsigned char *key, unsigned char *iv) +{ + unsigned char digest[MY_SHA1_HASH_SIZE]; + int key_left = OpenSSL_key_len; + int iv_left = OpenSSL_iv_len; + const size_t ilen= strlen(input); + const size_t slen= OpenSSL_salt_len; // either this or explicit (size_t) casts below + + my_sha1_multi(digest, input, ilen, salt, slen, NullS); + + while (iv_left) + { + int left= MY_SHA1_HASH_SIZE; + if (key_left) + { + int store = MY_MIN(key_left, MY_SHA1_HASH_SIZE); + memcpy(&key[OpenSSL_key_len - key_left], digest, store); + + key_left -= store; + left -= store; + } + + if (iv_left && left) + { + int store= MY_MIN(iv_left, left); + memcpy(&iv[OpenSSL_iv_len - iv_left], &digest[MY_SHA1_HASH_SIZE - left], store); + + iv_left -= store; + } + + if (iv_left) + my_sha1_multi(digest, digest, MY_SHA1_HASH_SIZE, + input, ilen, salt, slen, NullS); + } +} + + +bool Parser::parse(std::map<uint,keyentry> *keys) +{ + const char *secret= filekey; + char buf[MAX_SECRET_SIZE + 1]; + + //If secret starts with FILE: interpret the secret as a filename. + if (strncmp(filekey, FILE_PREFIX,sizeof(FILE_PREFIX) -1) == 0) + { + if (read_filekey(filekey + sizeof(FILE_PREFIX) - 1, buf)) + return 1; + secret= buf; + } + + return parse_file(keys, secret); +} + + +/* + secret is limited to MAX_SECRET_SIZE characters +*/ + +bool Parser::read_filekey(const char *filekey, char *secret) +{ + int f= open(filekey, O_RDONLY|O_BINARY); + if (f == -1) + { + my_error(EE_FILENOTFOUND, ME_ERROR_LOG, filekey, errno); + return 1; + } + + int len= read(f, secret, MAX_SECRET_SIZE + 1); + if (len <= 0) + { + my_error(EE_READ, ME_ERROR_LOG, filekey, errno); + close(f); + return 1; + } + close(f); + + while (secret[len - 1] == '\r' || secret[len - 1] == '\n') len--; + if (len > MAX_SECRET_SIZE) + { + my_printf_error(EE_READ, + "Cannot read %s, the filekey is too long, " + "max secret size is %dB ", + ME_ERROR_LOG, filekey, MAX_SECRET_SIZE); + return 1; + } + secret[len]= '\0'; + return 0; +} + + +/** + Get the keys from the key file <filename> and decrypt it with the + key <secret>. Store the keys with id smaller then <maxKeyId> in an + array of structs keyentry. + + @return 0 when ok, 1 for an error + */ + +bool Parser::parse_file(std::map<uint,keyentry> *keys, const char *secret) +{ + char *buffer= read_and_decrypt_file(secret); + + if (!buffer) + return 1; + + keyentry key; + char *line=buffer; + + while (*line) + { + line_number++; + switch (parse_line(&line, &key)) { + case 1: // comment + break; + case -1: // error + free(buffer); + return 1; + case 0: + (*keys)[key.id] = key; + break; + } + } + + free(buffer); + if (keys->size() == 0 || (*keys)[1].id == 0) + { + report_error("System key id 1 is missing", 0); + return 1; + } + + return 0; +} + +void Parser::report_error(const char *reason, size_t position) +{ + my_printf_error(EE_READ, "%s at %s line %u, column %zu", + ME_ERROR_LOG, reason, filename, line_number, position + 1); +} + +/* + return 0 - new key + 1 - comment + -1 - error +*/ +int Parser::parse_line(char **line_ptr, keyentry *key) +{ + int res= 1; + char *p= *line_ptr; + while (isspace(*p) && *p != '\n') p++; + if (*p != '#' && *p != '\n') + { + if (!isdigit(*p)) + { + report_error("Syntax error", p - *line_ptr); + return -1; + } + + longlong id = 0; + while (isdigit(*p)) + { + id = id * 10 + *p - '0'; + if (id > UINT_MAX32) + { + report_error("Invalid key id", p - *line_ptr); + return -1; + } + p++; + } + + if (id < 1) + { + report_error("Invalid key id", p - *line_ptr); + return -1; + } + + if (*p != ';') + { + report_error("Syntax error", p - *line_ptr); + return -1; + } + + p++; + key->id= (unsigned int)id; + key->length=0; + while (isxdigit(p[0]) && isxdigit(p[1]) && key->length < sizeof(key->key)) + { + key->key[key->length++] = from_hex(p[0]) * 16 + from_hex(p[1]); + p+=2; + } + if (isxdigit(*p) || + (key->length != 16 && key->length != 24 && key->length != 32)) + { + report_error("Invalid key", p - *line_ptr); + return -1; + } + + res= 0; + } + while (*p && *p != '\n') p++; + *line_ptr= *p == '\n' ? p + 1 : p; + return res; +} + +/** + Decrypt the key file 'filename' if it is encrypted with the key + 'secret'. Store the content of the decrypted file in 'buffer'. The + buffer has to be freed in the calling function. + */ +#ifdef _WIN32 +#define lseek _lseeki64 +#endif + +char* Parser::read_and_decrypt_file(const char *secret) +{ + int f; + if (!filename || !filename[0]) + { + my_printf_error(EE_CANT_OPEN_STREAM, "file-key-management-filename is not set", + ME_ERROR_LOG); + goto err0; + } + + f= open(filename, O_RDONLY|O_BINARY, 0); + if (f < 0) + { + my_error(EE_FILENOTFOUND, ME_ERROR_LOG, filename, errno); + goto err0; + } + + my_off_t file_size; + file_size= lseek(f, 0, SEEK_END); + + if (file_size == MY_FILEPOS_ERROR || (my_off_t)lseek(f, 0, SEEK_SET) == MY_FILEPOS_ERROR) + { + my_error(EE_CANT_SEEK, MYF(0), filename, errno); + goto err1; + } + + if (file_size > MAX_KEY_FILE_SIZE) + { + my_error(EE_READ, MYF(0), filename, EFBIG); + goto err1; + } + + //Read file into buffer + uchar *buffer; + buffer= (uchar*)malloc((size_t)file_size + 1); + if (!buffer) + { + my_error(EE_OUTOFMEMORY, ME_ERROR_LOG| ME_FATAL, file_size); + goto err1; + } + + if (read(f, buffer, (int)file_size) != (int)file_size) + { + my_printf_error(EE_READ, + "read from %s failed, errno %d", + MYF(ME_ERROR_LOG|ME_FATAL), filename, errno); + goto err2; + } + +// Check for file encryption + uchar *decrypted; + if (file_size > OpenSSL_prefix_len && strncmp((char*)buffer, OpenSSL_prefix, OpenSSL_prefix_len) == 0) + { + uchar key[OpenSSL_key_len]; + uchar iv[OpenSSL_iv_len]; + + decrypted= (uchar*)malloc((size_t)file_size); + if (!decrypted) + { + my_error(EE_OUTOFMEMORY, ME_ERROR_LOG | ME_FATAL, file_size); + goto err2; + } + bytes_to_key(buffer + OpenSSL_prefix_len, secret, key, iv); + uint32 d_size; + if (my_aes_crypt(MY_AES_CBC, ENCRYPTION_FLAG_DECRYPT, + buffer + OpenSSL_prefix_len + OpenSSL_salt_len, + (unsigned int)file_size - OpenSSL_prefix_len - OpenSSL_salt_len, + decrypted, &d_size, key, OpenSSL_key_len, + iv, OpenSSL_iv_len)) + + { + my_printf_error(EE_READ, "Cannot decrypt %s. Wrong key?", ME_ERROR_LOG, filename); + goto err3; + } + + free(buffer); + buffer= decrypted; + file_size= d_size; + } + else if (*secret) + { + my_printf_error(EE_READ, "Cannot decrypt %s. Not encrypted", ME_ERROR_LOG, filename); + goto err2; + } + + buffer[file_size]= '\0'; + close(f); + return (char*) buffer; + +err3: + free(decrypted); +err2: + free(buffer); +err1: + close(f); +err0: + return NULL; +} diff --git a/plugin/file_key_management/parser.h b/plugin/file_key_management/parser.h new file mode 100644 index 00000000..e2fda758 --- /dev/null +++ b/plugin/file_key_management/parser.h @@ -0,0 +1,55 @@ +/* Copyright (C) 2014 eperi GmbH. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +/******************************************************************//** +@file Parser.h +A structure and class to keep keys for encryption/decryption. + +Created 09/15/2014 +***********************************************************************/ + +#include <my_crypt.h> +#include <ctype.h> +#include <map> +#include <stdlib.h> /* size_t */ + +struct keyentry { + unsigned int id; + unsigned char key[MY_AES_MAX_KEY_LENGTH]; + unsigned int length; +}; + +class Parser +{ + const char *filename; + const char *filekey; + unsigned int line_number; + + unsigned int from_hex(char c) + { return c <= '9' ? c - '0' : tolower(c) - 'a' + 10; } + + void bytes_to_key(const unsigned char *salt, const char *secret, + unsigned char *key, unsigned char *iv); + bool read_filekey(const char *filekey, char *secret); + bool parse_file(std::map<unsigned int ,keyentry> *keys, const char *secret); + void report_error(const char *reason, size_t position); + int parse_line(char **line_ptr, keyentry *key); + char* read_and_decrypt_file(const char *secret); + +public: + Parser(const char* fn, const char *fk) : + filename(fn), filekey(fk), line_number(0) { } + bool parse(std::map<unsigned int ,keyentry> *keys); +}; |