summaryrefslogtreecommitdiffstats
path: root/plugin/file_key_management
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:04:16 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:04:16 +0000
commita68fb2d8219f6bccc573009600e9f23e89226a5e (patch)
treed742d35d14ae816e99293d2b01face30e9f3a46b /plugin/file_key_management
parentInitial commit. (diff)
downloadmariadb-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.txt4
-rw-r--r--plugin/file_key_management/file_key_management_plugin.cc207
-rw-r--r--plugin/file_key_management/parser.cc411
-rw-r--r--plugin/file_key_management/parser.h55
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);
+};