|
|
|
|
@@ -82,9 +82,20 @@ static unsigned long log_level;
|
|
|
|
|
static int rotate_key;
|
|
|
|
|
static int request_timeout;
|
|
|
|
|
|
|
|
|
|
#ifndef DBUG_OFF
|
|
|
|
|
#define WITH_AWS_MOCK 1
|
|
|
|
|
#else
|
|
|
|
|
#define WITH_AWS_MOCK 0
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if WITH_AWS_MOCK
|
|
|
|
|
static char mock;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* AWS functionality*/
|
|
|
|
|
static int aws_decrypt_key(const char *path, KEY_INFO *info);
|
|
|
|
|
static int aws_generate_datakey(uint key_id, uint version);
|
|
|
|
|
static int read_and_decrypt_key(const char *path, KEY_INFO *info);
|
|
|
|
|
static int generate_and_save_datakey(uint key_id, uint version);
|
|
|
|
|
|
|
|
|
|
static int extract_id_and_version(const char *name, uint *id, uint *ver);
|
|
|
|
|
static unsigned int get_latest_key_version(unsigned int key_id);
|
|
|
|
|
@@ -95,6 +106,35 @@ static std::mutex mtx;
|
|
|
|
|
|
|
|
|
|
static Aws::KMS::KMSClient *client;
|
|
|
|
|
|
|
|
|
|
#if WITH_AWS_MOCK
|
|
|
|
|
/*
|
|
|
|
|
Mock routines to test plugin without actual AWS KMS interaction
|
|
|
|
|
we only need to mock 2 functions - generating encrypted key, and decrypt
|
|
|
|
|
|
|
|
|
|
This mock functions do no-op encryption, i.e encrypt and decrypt of
|
|
|
|
|
a buffer return the buffer itself.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Generate random "encrypted" key. We do not encrypt anything in mock mode.
|
|
|
|
|
*/
|
|
|
|
|
static int mock_generate_encrypted_key(Aws::Utils::ByteBuffer *result)
|
|
|
|
|
{
|
|
|
|
|
size_t len = key_spec == 0?16 : 32;
|
|
|
|
|
*result = Aws::Utils::ByteBuffer(len);
|
|
|
|
|
my_random_bytes(result->GetUnderlyingData(), (int)len);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int mock_decrypt(Aws::Utils::ByteBuffer input, Aws::Utils::ByteBuffer* output, Aws::String *errmsg)
|
|
|
|
|
{
|
|
|
|
|
/* We do not encrypt or decrypt in mock mode.*/
|
|
|
|
|
*output = input;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* Redirect AWS trace to error log */
|
|
|
|
|
class MySQLLogSystem : public Aws::Utils::Logging::FormattedLogSystem
|
|
|
|
|
{
|
|
|
|
|
@@ -163,13 +203,7 @@ static vector<string> traverse_current_directory()
|
|
|
|
|
|
|
|
|
|
Aws::SDKOptions sdkOptions;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Plugin initialization.
|
|
|
|
|
|
|
|
|
|
Create KMS client and scan datadir to find out which keys and versions
|
|
|
|
|
are present.
|
|
|
|
|
*/
|
|
|
|
|
static int plugin_init(void *p)
|
|
|
|
|
static int aws_init()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_YASSL
|
|
|
|
|
@@ -198,6 +232,28 @@ static int plugin_init(void *p)
|
|
|
|
|
my_printf_error(ER_UNKNOWN_ERROR, "Can not initialize KMS client", ME_ERROR_LOG | ME_WARNING);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int init()
|
|
|
|
|
{
|
|
|
|
|
#if WITH_AWS_MOCK
|
|
|
|
|
if(mock)
|
|
|
|
|
return 0;
|
|
|
|
|
#endif
|
|
|
|
|
return aws_init();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Plugin initialization.
|
|
|
|
|
|
|
|
|
|
Create KMS client and scan datadir to find out which keys and versions
|
|
|
|
|
are present.
|
|
|
|
|
*/
|
|
|
|
|
static int plugin_init(void *p)
|
|
|
|
|
{
|
|
|
|
|
if (init())
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
vector<string> files= traverse_current_directory();
|
|
|
|
|
for (size_t i=0; i < files.size(); i++)
|
|
|
|
|
@@ -214,14 +270,29 @@ static int plugin_init(void *p)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void aws_shutdown()
|
|
|
|
|
{
|
|
|
|
|
delete client;
|
|
|
|
|
ShutdownAWSLogging();
|
|
|
|
|
Aws::ShutdownAPI(sdkOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void shutdown()
|
|
|
|
|
{
|
|
|
|
|
#if WITH_AWS_MOCK
|
|
|
|
|
if(mock)
|
|
|
|
|
return;
|
|
|
|
|
#endif
|
|
|
|
|
aws_shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int plugin_deinit(void *p)
|
|
|
|
|
{
|
|
|
|
|
latest_version_cache.clear();
|
|
|
|
|
key_info_cache.clear();
|
|
|
|
|
delete client;
|
|
|
|
|
ShutdownAWSLogging();
|
|
|
|
|
|
|
|
|
|
Aws::ShutdownAPI(sdkOptions);
|
|
|
|
|
shutdown();
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -251,7 +322,7 @@ static int load_key(KEY_INFO *info)
|
|
|
|
|
char path[256];
|
|
|
|
|
|
|
|
|
|
format_keyfile_name(path, sizeof(path), info->key_id, info->key_version);
|
|
|
|
|
ret= aws_decrypt_key(path, info);
|
|
|
|
|
ret= read_and_decrypt_key(path, info);
|
|
|
|
|
if (ret)
|
|
|
|
|
info->load_failed= true;
|
|
|
|
|
|
|
|
|
|
@@ -317,14 +388,7 @@ static unsigned int get_latest_key_version_nolock(unsigned int key_id)
|
|
|
|
|
else // (ver == 0)
|
|
|
|
|
{
|
|
|
|
|
/* Generate a new key, version 1 */
|
|
|
|
|
if (!master_key_id[0])
|
|
|
|
|
{
|
|
|
|
|
my_printf_error(ER_UNKNOWN_ERROR,
|
|
|
|
|
"Can't generate encryption key %u, because 'aws_key_management_master_key_id' parameter is not set",
|
|
|
|
|
MYF(0), key_id);
|
|
|
|
|
return(ENCRYPTION_KEY_VERSION_INVALID);
|
|
|
|
|
}
|
|
|
|
|
if (aws_generate_datakey(key_id, 1) != 0)
|
|
|
|
|
if (generate_and_save_datakey(key_id, 1) != 0)
|
|
|
|
|
return(ENCRYPTION_KEY_VERSION_INVALID);
|
|
|
|
|
info.key_id= key_id;
|
|
|
|
|
info.key_version= 1;
|
|
|
|
|
@@ -336,11 +400,35 @@ static unsigned int get_latest_key_version_nolock(unsigned int key_id)
|
|
|
|
|
return(info.key_version);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Decrypt Byte buffer with AWS. */
|
|
|
|
|
static int aws_decrypt(Aws::Utils::ByteBuffer input, Aws::Utils::ByteBuffer* output, Aws::String *errmsg)
|
|
|
|
|
{
|
|
|
|
|
DecryptRequest request;
|
|
|
|
|
request.SetCiphertextBlob(input);
|
|
|
|
|
DecryptOutcome outcome = client->Decrypt(request);
|
|
|
|
|
if (!outcome.IsSuccess())
|
|
|
|
|
{
|
|
|
|
|
*errmsg = outcome.GetError().GetMessage();
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
*output= outcome.GetResult().GetPlaintext();
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int decrypt(Aws::Utils::ByteBuffer input, Aws::Utils::ByteBuffer* output, Aws::String *errmsg)
|
|
|
|
|
{
|
|
|
|
|
#if WITH_AWS_MOCK
|
|
|
|
|
if(mock)
|
|
|
|
|
return mock_decrypt(input,output, errmsg);
|
|
|
|
|
#endif
|
|
|
|
|
return aws_decrypt(input, output, errmsg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Decrypt a file with KMS
|
|
|
|
|
*/
|
|
|
|
|
static int aws_decrypt_key(const char *path, KEY_INFO *info)
|
|
|
|
|
static int read_and_decrypt_key(const char *path, KEY_INFO *info)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
/* Read file content into memory */
|
|
|
|
|
@@ -361,20 +449,21 @@ static int aws_decrypt_key(const char *path, KEY_INFO *info)
|
|
|
|
|
ifs.read(&contents[0], pos);
|
|
|
|
|
|
|
|
|
|
/* Decrypt data the with AWS */
|
|
|
|
|
DecryptRequest request;
|
|
|
|
|
Aws::Utils::ByteBuffer byteBuffer((unsigned char *)contents.data(), pos);
|
|
|
|
|
request.SetCiphertextBlob(byteBuffer);
|
|
|
|
|
DecryptOutcome outcome = client->Decrypt(request);
|
|
|
|
|
if (!outcome.IsSuccess())
|
|
|
|
|
|
|
|
|
|
Aws::Utils::ByteBuffer input((unsigned char *)contents.data(), pos);
|
|
|
|
|
Aws::Utils::ByteBuffer plaintext;
|
|
|
|
|
Aws::String errmsg;
|
|
|
|
|
|
|
|
|
|
if (decrypt(input, &plaintext, &errmsg))
|
|
|
|
|
{
|
|
|
|
|
my_printf_error(ER_UNKNOWN_ERROR, "AWS KMS plugin: Decrypt failed for %s : %s", ME_ERROR_LOG, path,
|
|
|
|
|
outcome.GetError().GetMessage().c_str());
|
|
|
|
|
return(-1);
|
|
|
|
|
my_printf_error(ER_UNKNOWN_ERROR, "AWS KMS plugin: Decrypt failed for %s : %s", ME_ERROR_LOG, path,
|
|
|
|
|
errmsg.c_str());
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
Aws::Utils::ByteBuffer plaintext = outcome.GetResult().GetPlaintext();
|
|
|
|
|
|
|
|
|
|
size_t len = plaintext.GetLength();
|
|
|
|
|
|
|
|
|
|
if (len > (int)sizeof(info->data))
|
|
|
|
|
if (len > sizeof(info->data))
|
|
|
|
|
{
|
|
|
|
|
my_printf_error(ER_UNKNOWN_ERROR, "AWS KMS plugin: encoding key too large for %s", ME_ERROR_LOG, path);
|
|
|
|
|
return(ENCRYPTION_KEY_BUFFER_TOO_SMALL);
|
|
|
|
|
@@ -385,9 +474,15 @@ static int aws_decrypt_key(const char *path, KEY_INFO *info)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Generate a new datakey and store it a file */
|
|
|
|
|
static int aws_generate_datakey(uint keyid, uint version)
|
|
|
|
|
int aws_generate_encrypted_key(Aws::Utils::ByteBuffer *result)
|
|
|
|
|
{
|
|
|
|
|
if (!master_key_id[0])
|
|
|
|
|
{
|
|
|
|
|
my_printf_error(ER_UNKNOWN_ERROR,
|
|
|
|
|
"Can't generate encryption key, because 'aws_key_management_master_key_id' parameter is not set",
|
|
|
|
|
MYF(0));
|
|
|
|
|
return(-1);
|
|
|
|
|
}
|
|
|
|
|
GenerateDataKeyWithoutPlaintextRequest request;
|
|
|
|
|
request.SetKeyId(master_key_id);
|
|
|
|
|
request.SetKeySpec(DataKeySpecMapper::GetDataKeySpecForName(key_spec_names[key_spec]));
|
|
|
|
|
@@ -401,11 +496,30 @@ static int aws_generate_datakey(uint keyid, uint version)
|
|
|
|
|
outcome.GetError().GetMessage().c_str());
|
|
|
|
|
return(-1);
|
|
|
|
|
}
|
|
|
|
|
*result = outcome.GetResult().GetCiphertextBlob();
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int generate_encrypted_key(Aws::Utils::ByteBuffer *output)
|
|
|
|
|
{
|
|
|
|
|
#if WITH_AWS_MOCK
|
|
|
|
|
if(mock)
|
|
|
|
|
return mock_generate_encrypted_key(output);
|
|
|
|
|
#endif
|
|
|
|
|
return aws_generate_encrypted_key(output);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Generate a new datakey and store it a file */
|
|
|
|
|
static int generate_and_save_datakey(uint keyid, uint version)
|
|
|
|
|
{
|
|
|
|
|
Aws::Utils::ByteBuffer byteBuffer;
|
|
|
|
|
|
|
|
|
|
if (generate_encrypted_key(&byteBuffer))
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
string out;
|
|
|
|
|
char filename[20];
|
|
|
|
|
Aws::Utils::ByteBuffer byteBuffer = outcome.GetResult().GetCiphertextBlob();
|
|
|
|
|
|
|
|
|
|
format_keyfile_name(filename, sizeof(filename), keyid, version);
|
|
|
|
|
int fd= open(filename, O_WRONLY |O_CREAT|O_BINARY, IF_WIN(_S_IREAD, S_IRUSR| S_IRGRP| S_IROTH));
|
|
|
|
|
if (fd < 0)
|
|
|
|
|
@@ -438,7 +552,7 @@ static int rotate_single_key(uint key_id)
|
|
|
|
|
my_printf_error(ER_UNKNOWN_ERROR, "key %u does not exist", MYF(ME_JUST_WARNING), key_id);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
else if (aws_generate_datakey(key_id, ver + 1))
|
|
|
|
|
else if (generate_and_save_datakey(key_id, ver + 1))
|
|
|
|
|
{
|
|
|
|
|
my_printf_error(ER_UNKNOWN_ERROR, "Could not generate datakey for key id= %u, ver= %u",
|
|
|
|
|
MYF(ME_JUST_WARNING), key_id, ver);
|
|
|
|
|
@@ -594,6 +708,13 @@ static MYSQL_SYSVAR_STR(region, region,
|
|
|
|
|
"AWS region. For example us-east-1, or eu-central-1. If no value provided, SDK default is used.",
|
|
|
|
|
NULL, NULL, "");
|
|
|
|
|
|
|
|
|
|
#if WITH_AWS_MOCK
|
|
|
|
|
static MYSQL_SYSVAR_BOOL(mock, mock,
|
|
|
|
|
PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
|
|
|
|
|
"Mock AWS KMS calls (for testing).",
|
|
|
|
|
NULL, NULL, 0);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
static struct st_mysql_sys_var* settings[]= {
|
|
|
|
|
MYSQL_SYSVAR(master_key_id),
|
|
|
|
|
MYSQL_SYSVAR(key_spec),
|
|
|
|
|
@@ -601,6 +722,9 @@ static struct st_mysql_sys_var* settings[]= {
|
|
|
|
|
MYSQL_SYSVAR(log_level),
|
|
|
|
|
MYSQL_SYSVAR(request_timeout),
|
|
|
|
|
MYSQL_SYSVAR(region),
|
|
|
|
|
#if WITH_AWS_MOCK
|
|
|
|
|
MYSQL_SYSVAR(mock),
|
|
|
|
|
#endif
|
|
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|