1
0
mirror of https://github.com/mariadb-corporation/mariadb-columnstore-engine.git synced 2025-07-27 21:01:50 +03:00

MCOL-1175: add ability to encrypt CEJ password and use in Columnstore.xml (#2045)

This commit is contained in:
benthompson15
2021-07-13 11:42:36 -05:00
committed by GitHub
parent d802407c50
commit 2ae3da45eb
10 changed files with 1204 additions and 3 deletions

View File

@ -1,5 +1,5 @@
include_directories( ${ENGINE_COMMON_INCLUDES} )
include_directories( ${ENGINE_COMMON_INCLUDES} ${ENGINE_SRC_DIR}/tools/passwd)
########### next target ###############
@ -55,7 +55,8 @@ set(joblist_LIB_SRCS
tupleunion.cpp
unique32generator.cpp
virtualtable.cpp
windowfunctionstep.cpp)
windowfunctionstep.cpp
${ENGINE_SRC_DIR}/tools/passwd/secrets.cpp)
add_library(joblist SHARED ${joblist_LIB_SRCS})

View File

@ -37,6 +37,7 @@ using namespace boost;
#include "jl_logger.h"
#include "cgroupconfigurator.h"
#include "liboamcpp.h"
#include "secrets.h"
using namespace config;
@ -266,6 +267,11 @@ ResourceManager::ResourceManager(bool runningInExeMgr) :
fAllowedDiskAggregation = getBoolVal(fRowAggregationStr,
"AllowDiskBasedAggregation",
defaultAllowDiskAggregation);
if (!load_encryption_keys())
{
Logger log;
log.logMessage(logging::LOG_TYPE_ERROR, "Error loading CEJ password encryption keys");
}
}
int ResourceManager::getEmPriority() const
@ -381,7 +387,9 @@ bool ResourceManager::getMysqldInfo(
// MCS will read username and pass from disk if the config changed.
bool reReadConfig = true;
u = getStringVal("CrossEngineSupport", "User", hostUserUnassignedValue, reReadConfig);
w = getStringVal("CrossEngineSupport", "Password", "", reReadConfig);
std::string encryptedPW = getStringVal("CrossEngineSupport", "Password", "", reReadConfig);
//This will return back the plaintext password if there is no MCSDATADIR/.secrets file present
w = decrypt_password(encryptedPW);
// MCS will not read username and pass from disk if the config changed.
h = getStringVal("CrossEngineSupport", "Host", hostUserUnassignedValue);
p = getUintVal("CrossEngineSupport", "Port", 0);

View File

@ -22,6 +22,8 @@ usr/bin/controllernode
usr/bin/cpimport
usr/bin/cpimport.bin
usr/bin/cplogger
usr/bin/cspasswd
usr/bin/cskeys
usr/bin/dbbuilder
usr/bin/dbrmctl
usr/bin/ddlcleanup

View File

@ -393,4 +393,6 @@
#define MCSMYCNFDIR "${MARIADB_MYCNFDIR}"
#define MCSDATADIR "${ENGINE_DATADIR}"
#endif

View File

@ -11,3 +11,4 @@ add_subdirectory(cleartablelock)
add_subdirectory(ddlcleanup)
add_subdirectory(idbmeminfo)
add_subdirectory(rebuildEM)
add_subdirectory(passwd)

View File

@ -0,0 +1,17 @@
include_directories( ${ENGINE_COMMON_INCLUDES} )
########### next target ###############
set(cspasswd_SRCS cspasswd.cpp secrets.cpp)
set(cskeys_SRCS cskeys.cpp secrets.cpp)
add_executable(cspasswd ${cspasswd_SRCS})
add_executable(cskeys ${cskeys_SRCS})
target_link_libraries(cspasswd ${ENGINE_LDFLAGS} ${MARIADB_CLIENT_LIBS} ${ENGINE_EXEC_LIBS})
target_link_libraries(cskeys ${ENGINE_LDFLAGS} ${MARIADB_CLIENT_LIBS} ${ENGINE_EXEC_LIBS})
install(TARGETS cspasswd DESTINATION ${ENGINE_BINDIR} COMPONENT columnstore-engine)
install(TARGETS cskeys DESTINATION ${ENGINE_BINDIR} COMPONENT columnstore-engine)

126
tools/passwd/cskeys.cpp Normal file
View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2025-05-25
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include <chrono>
#include <cstdio>
#include <getopt.h>
#include <unistd.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include "secrets.h"
#include "mcsconfig.h"
using std::string;
using ByteVec = std::vector<uint8_t>;
struct option options[] =
{
{"help", no_argument, nullptr, 'h'},
{"user", required_argument, nullptr, 'u'},
{nullptr, 0, nullptr, 0 }
};
const string default_user = "mysql";
ByteVec generate_encryption_key();
void print_usage(const char* executable, const char* default_directory)
{
const char msg[] =
R"(usage: %s [-h|--help] [directory]
This utility generates a random AES encryption key and init vector and writes
them to disk. The data is written to the file '%s', in the specified
directory. The key and init vector are used by the utility 'cspasswd' to
encrypt passwords used in Columnstore configuration files, as well as by Columnstore
itself to decrypt the passwords.
Re-creating the file invalidates all existing encrypted passwords in the
configuration files.
-h, --help Display this help
-u, --user Designate the owner of the generated file (default: '%s')
directory : The directory where to store the file in (default: '%s')
)";
printf(msg, executable, SECRETS_FILENAME, default_user.c_str(), default_directory);
}
int main(int argc, char** argv)
{
CSPasswdLogging* keysLog = CSPasswdLogging::get();
const string default_directory = string(MCSDATADIR);
string username = default_user;
int c;
while ((c = getopt_long(argc, argv, "hu:", options, nullptr)) != -1)
{
switch (c)
{
case 'h':
print_usage(argv[0], default_directory.c_str());
return EXIT_SUCCESS;
case 'u':
username = optarg;
break;
default:
print_usage(argv[0], default_directory.c_str());
return EXIT_FAILURE;
}
}
string filepath = default_directory;
if (optind < argc)
{
filepath = argv[optind];
}
filepath.append("/").append(SECRETS_FILENAME);
// Check that the file doesn't exist.
errno = 0;
auto filepathc = filepath.c_str();
if (access(filepathc, F_OK) == 0)
{
printf("Secrets file '%s' already exists. Delete it before generating a new encryption key.\n",
filepathc);
return EXIT_FAILURE;
}
else if (errno != ENOENT)
{
printf("stat() for secrets file '%s' failed unexpectedly. Error %i, %s.\n",
filepathc, errno, strerror(errno));
return EXIT_FAILURE;
}
int rval = EXIT_FAILURE;
auto new_key = generate_encryption_key();
if (!new_key.empty() && secrets_write_keys(new_key, filepath, username))
{
rval = EXIT_SUCCESS;
}
return rval;
}
ByteVec generate_encryption_key()
{
int keylen = EVP_CIPHER_key_length(secrets_cipher());
ByteVec key(keylen);
// Generate random bytes using OpenSSL.
if (RAND_bytes(key.data(), keylen) != 1)
{
auto errornum = ERR_get_error();
printf("OpenSSL RAND_bytes() failed. %s.\n", ERR_error_string(errornum, nullptr));
key.clear();
}
return key;
}

270
tools/passwd/cspasswd.cpp Normal file
View File

@ -0,0 +1,270 @@
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2025-05-25
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
/**
* @file cspasswd.cpp - Implementation of pasword encoding
* Modified from MariaDB internal implementations
*/
#include <cstdio>
#include <getopt.h>
#include <termios.h>
#include <unistd.h>
#include <iostream>
#include "secrets.h"
#include "mcsconfig.h"
using std::cin;
using std::cout;
using std::endl;
using std::flush;
using std::string;
struct option options[] =
{
{"help", no_argument, nullptr, 'h'},
{"decrypt", no_argument, nullptr, 'd'},
{"interactive", no_argument, nullptr, 'i'},
{nullptr, 0, nullptr, 0 }
};
void print_usage(const char* executable, const char* directory)
{
const char msg[] =
R"(Usage: %s [-h|--help] [-i|--interactive] [-d|--decrypt] [path] password
Encrypt a Columnstore plaintext password using the encryption key in the key file
'%s'. The key file may be generated using the 'cskeys'-utility.
-h, --help Display this help.
-d, --decrypt Decrypt an encrypted password instead.
-i, --interactive - If cspasswd is reading from a pipe, it will read a line and
use that as the password.
- If cspasswd is connected to a terminal console, it will prompt
for the password.
If '-i' is specified, a single argument is assumed to be the path
and two arguments is treated like an error.
path The key file directory (default: '%s')
password The password to encrypt or decrypt
)";
printf(msg, executable, SECRETS_FILENAME, directory);
}
bool read_password(string* pPassword)
{
bool rv = false;
string password;
if (isatty(STDIN_FILENO))
{
struct termios tty;
tcgetattr(STDIN_FILENO, &tty);
bool echo = (tty.c_lflag & ECHO);
if (echo)
{
tty.c_lflag &= ~ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &tty);
}
cout << "Enter password : " << flush;
string s1;
std::getline(std::cin, s1);
cout << endl;
cout << "Repeat password: " << flush;
string s2;
std::getline(std::cin, s2);
cout << endl;
if (echo)
{
tty.c_lflag |= ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &tty);
}
if (s1 == s2)
{
password = s1;
rv = true;
}
else
{
cout << "Passwords are not identical." << endl;
}
}
else
{
std::getline(std::cin, password);
rv = true;
}
if (rv)
{
*pPassword = password;
}
return rv;
}
int main(int argc, char** argv)
{
std::ios::sync_with_stdio();
CSPasswdLogging* passwdLog = CSPasswdLogging::get();
const string default_directory = string(MCSDATADIR);
enum class Mode
{
ENCRYPT,
DECRYPT
};
auto mode = Mode::ENCRYPT;
bool interactive = false;
int c;
while ((c = getopt_long(argc, argv, "hdi", options, NULL)) != -1)
{
switch (c)
{
case 'h':
print_usage(argv[0], default_directory.c_str());
return EXIT_SUCCESS;
case 'd':
mode = Mode::DECRYPT;
break;
case 'i':
interactive = true;
break;
default:
print_usage(argv[0], default_directory.c_str());
return EXIT_FAILURE;
}
}
string input;
string path = default_directory;
switch (argc - optind)
{
case 2:
// Two args provided.
path = argv[optind];
if (!interactive)
{
input = argv[optind + 1];
}
else
{
print_usage(argv[0], default_directory.c_str());
return EXIT_FAILURE;
}
break;
case 1:
// One arg provided.
if (!interactive)
{
input = argv[optind];
}
else
{
path = argv[optind];
}
break;
case 0:
if (!interactive)
{
print_usage(argv[0], default_directory.c_str());
return EXIT_FAILURE;
}
break;
default:
print_usage(argv[0], default_directory.c_str());
return EXIT_FAILURE;
}
if (interactive)
{
if (!read_password(&input))
{
return EXIT_FAILURE;
}
}
int rval = EXIT_FAILURE;
string filepath = path;
filepath.append("/").append(SECRETS_FILENAME);
auto keydata = secrets_readkeys(filepath);
if (keydata.ok)
{
bool encrypting = (mode == Mode::ENCRYPT);
bool new_mode = keydata.iv.empty(); // false -> constant IV from file
if (keydata.key.empty())
{
printf("Password encryption key file '%s' not found, cannot %s password.\n",
filepath.c_str(), encrypting ? "encrypt" : "decrypt");
}
else if (encrypting)
{
string encrypted = new_mode ? encrypt_password(keydata.key, input) :
encrypt_password_old(keydata.key, keydata.iv, input);
if (!encrypted.empty())
{
printf("%s\n", encrypted.c_str());
rval = EXIT_SUCCESS;
}
else
{
printf("Password encryption failed.\n");
}
}
else
{
auto is_hex = std::all_of(input.begin(), input.end(), isxdigit);
if (is_hex && input.length() % 2 == 0)
{
string decrypted = new_mode ? decrypt_password(keydata.key, input) :
decrypt_password_old(keydata.key, keydata.iv, input);
if (!decrypted.empty())
{
printf("%s\n", decrypted.c_str());
rval = EXIT_SUCCESS;
}
else
{
printf("Password decryption failed.\n");
}
}
else
{
printf("Input is not a valid hex-encoded encrypted password.\n");
}
}
}
else
{
printf("Could not read encryption key file '%s'.\n", filepath.c_str());
}
return rval;
}

693
tools/passwd/secrets.cpp Normal file
View File

@ -0,0 +1,693 @@
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2025-05-25
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include "secrets.h"
#include <cctype>
#include <fstream>
#include <pwd.h>
#include <sys/stat.h>
#include <openssl/aes.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/ossl_typ.h>
#include <openssl/rand.h>
#define BOOST_SPIRIT_THREADSAFE
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include "idberrorinfo.h"
#include "logger.h"
#include "mcsconfig.h"
#include "exceptclasses.h"
#include "columnstoreversion.h"
using std::string;
const char* const SECRETS_FILENAME = ".secrets";
namespace
{
CSPasswdLogging *passwdLog = NULL;
boost::mutex m;
}
CSPasswdLogging::CSPasswdLogging()
{
//TODO: make this configurable
setlogmask (LOG_UPTO (LOG_DEBUG));
openlog ("CSPasswd", LOG_PID | LOG_NDELAY | LOG_PERROR | LOG_CONS, LOG_LOCAL1);
}
CSPasswdLogging::~CSPasswdLogging()
{
syslog(LOG_INFO, "CloseLog");
closelog();
}
CSPasswdLogging * CSPasswdLogging::get()
{
if (passwdLog)
return passwdLog;
boost::mutex::scoped_lock s(m);
if (passwdLog)
return passwdLog;
passwdLog = new CSPasswdLogging();
return passwdLog;
}
void CSPasswdLogging::log(int priority,const char *format, ...)
{
va_list args;
va_start(args, format);
#ifdef DEBUG
va_list args2;
va_copy(args2, args);
vfprintf(stderr, format, args2);
fprintf(stderr, "\n");
va_end(args2);
#endif
vsyslog(priority, format, args);
va_end(args);
}
namespace
{
using HexLookupTable = std::array<uint8_t, 256>;
HexLookupTable init_hex_lookup_table() noexcept;
// Hex char -> byte val lookup table.
const HexLookupTable hex_lookup_table = init_hex_lookup_table();
/* used in the bin2hex function */
const char hex_upper[] = "0123456789ABCDEF";
const char hex_lower[] = "0123456789abcdef";
HexLookupTable init_hex_lookup_table() noexcept
{
auto char_val = [](char c) -> uint8_t {
if (c >= '0' && c <= '9')
{
return c - '0';
}
else if (c >= 'A' && c <= 'F')
{
return c - 'A' + 10;
}
else if (c >= 'a' && c <= 'f')
{
return c - 'a' + 10;
}
else
{
return '\177';
}
};
HexLookupTable rval;
for (size_t i = 0; i < rval.size(); i++)
{
rval[i] = char_val(i);
}
return rval;
}
bool hex2bin(const char* in, unsigned int in_len, uint8_t* out)
{
// Input length must be multiple of two.
if (!in || in_len == 0 || in_len % 2 != 0)
{
return false;
}
const char* in_end = in + in_len;
while (in < in_end)
{
// One byte is formed from two hex chars, with the first char forming the high bits.
uint8_t high_half = hex_lookup_table[*in++];
uint8_t low_half = hex_lookup_table[*in++];
uint8_t total = (high_half << 4) | low_half;
*out++ = total;
}
return true;
}
char* bin2hex(const uint8_t* in, unsigned int len, char* out)
{
const uint8_t* in_end = in + len;
if (len == 0 || in == NULL)
{
return NULL;
}
for (; in != in_end; ++in)
{
*out++ = hex_upper[((uint8_t) * in) >> 4];
*out++ = hex_upper[((uint8_t) * in) & 0x0F];
}
*out = '\0';
return out;
}
struct ThisUnit
{
ByteVec key; /**< Password decryption key, assigned at startup */
ByteVec iv; /**< Decryption init vector, assigned at startup. Only used with old-format keys */
};
ThisUnit this_unit;
enum class ProcessingMode
{
ENCRYPT,
DECRYPT,
DECRYPT_IGNORE_ERRORS
};
const char field_desc[] = "description";
const char field_version[] = "columnstore_version";
const char field_cipher[] = "encryption_cipher";
const char field_key[] = "encryption_key";
const char desc[] = "Columnstore encryption/decryption key";
#define SECRETS_CIPHER EVP_aes_256_cbc
#define STRINGIFY(X) #X
#define STRINGIFY2(X) STRINGIFY(X)
const char CIPHER_NAME[] = STRINGIFY2(SECRETS_CIPHER);
void print_openSSL_errors(const char* operation);
/**
* Encrypt or decrypt the input buffer to output buffer.
*
* @param key Encryption key
* @param mode Encrypting or decrypting
* @param input Input buffer
* @param input_len Input length
* @param output Output buffer
* @param output_len Produced output length is written here
* @return True on success
*/
bool encrypt_or_decrypt(const uint8_t* key, const uint8_t* iv, ProcessingMode mode,
const uint8_t* input, int input_len, uint8_t* output, int* output_len)
{
auto ctx = EVP_CIPHER_CTX_new();
int enc = (mode == ProcessingMode::ENCRYPT) ? AES_ENCRYPT : AES_DECRYPT;
bool ignore_errors = (mode == ProcessingMode::DECRYPT_IGNORE_ERRORS);
bool ok = false;
if (EVP_CipherInit_ex(ctx, secrets_cipher(), nullptr, key, iv, enc) == 1 || ignore_errors)
{
int output_written = 0;
if (EVP_CipherUpdate(ctx, output, &output_written, input, input_len) == 1 || ignore_errors)
{
int total_output_len = output_written;
if (EVP_CipherFinal_ex(ctx, output + total_output_len, &output_written) == 1 || ignore_errors)
{
total_output_len += output_written;
*output_len = total_output_len;
ok = true;
}
}
}
EVP_CIPHER_CTX_free(ctx);
if (!ok)
{
const char* operation = (mode == ProcessingMode::ENCRYPT) ? "when encrypting password" :
"when decrypting password";
print_openSSL_errors(operation);
}
return ok;
}
void print_openSSL_errors(const char* operation)
{
// It's unclear how thread(unsafe) OpenSSL error functions are. Minimize such possibilities by
// using a local buffer.
constexpr size_t bufsize = 256; // Should be enough according to some googling.
char buf[bufsize];
buf[0] = '\0';
auto errornum = ERR_get_error();
auto errornum2 = ERR_get_error();
ERR_error_string_n(errornum, buf, bufsize);
if (errornum2 == 0)
{
// One error.
CSPasswdLogging::get()->log(LOG_ERR,"OpenSSL error %s. %s", operation, buf);
}
else
{
// Multiple errors, print all as separate messages.
CSPasswdLogging::get()->log(LOG_ERR,"Multiple OpenSSL errors %s. Detailed messages below.", operation);
CSPasswdLogging::get()->log(LOG_ERR,"%s", buf);
while (errornum2 != 0)
{
ERR_error_string_n(errornum2, buf, bufsize);
CSPasswdLogging::get()->log(LOG_ERR,"%s", buf);
errornum2 = ERR_get_error();
}
}
}
}
const EVP_CIPHER* secrets_cipher()
{
return SECRETS_CIPHER();
}
int secrets_keylen()
{
return EVP_CIPHER_key_length(secrets_cipher());
}
int secrets_ivlen()
{
return EVP_CIPHER_iv_length(secrets_cipher());
}
/**
* Reads binary or text data from file and extracts the encryption key and, if old key format is used,
* the init vector. The source file needs to have expected permissions. If the source file does not exist,
* returns empty results.
*
* @param filepath Path to key file.
* @return Result structure. Ok if file was loaded successfully or if file did not exist. False on error.
*/
ReadKeyResult secrets_readkeys(const string& filepath)
{
ReadKeyResult rval;
auto filepathc = filepath.c_str();
const int binary_key_len = secrets_keylen();
const int binary_iv_len = secrets_ivlen();
const int binary_total_len = binary_key_len + binary_iv_len;
// Before opening the file, check its size and permissions.
struct stat filestats { 0 };
bool stat_error = false;
bool old_format = false;
errno = 0;
if (stat(filepathc, &filestats) == 0)
{
auto filesize = filestats.st_size;
if (filesize == binary_total_len)
{
old_format = true;
CSPasswdLogging::get()->log(LOG_WARNING,"File format of '%s' is deprecated. Please generate a new encryption key ('maxkeys') "
"and re-encrypt passwords ('maxpasswd').", filepathc);
}
auto filemode = filestats.st_mode;
if (!S_ISREG(filemode))
{
CSPasswdLogging::get()->log(LOG_ERR,"Secrets file '%s' is not a regular file.", filepathc);
stat_error = true;
}
else if ((filemode & (S_IRWXU | S_IRWXG | S_IRWXO)) != S_IRUSR)
{
CSPasswdLogging::get()->log(LOG_ERR,"Secrets file '%s' permissions are wrong. The only permission on the file should be "
"owner:read.", filepathc);
stat_error = true;
}
}
else if (errno == ENOENT)
{
// The file does not exist. This is ok. Return empty result.
rval.ok = true;
return rval;
}
else
{
CSPasswdLogging::get()->log(LOG_ERR,"stat() for secrets file '%s' failed. Error %d, %s.",
filepathc, errno, strerror(errno));
stat_error = true;
}
if (stat_error)
{
return rval;
}
if (old_format)
{
errno = 0;
std::ifstream file(filepath, std::ios_base::binary);
if (file.is_open())
{
// Read all data from file.
char readbuf[binary_total_len];
file.read(readbuf, binary_total_len);
if (file.good())
{
// Success, copy contents to key structure.
rval.key.assign(readbuf, readbuf + binary_key_len);
rval.iv.assign(readbuf + binary_key_len, readbuf + binary_total_len);
rval.ok = true;
}
else
{
CSPasswdLogging::get()->log(LOG_ERR,"Read from secrets file %s failed. Read %li, expected %i bytes. Error %d, %s.",
filepathc, file.gcount(), binary_total_len, errno, strerror(errno));
}
}
else
{
CSPasswdLogging::get()->log(LOG_ERR,"Could not open secrets file '%s'. Error %d, %s.",
filepathc, errno, strerror(errno));
}
}
else
{
// File contents should be json.
//json_error_t err;
boost::property_tree::ptree jsontree;
try
{
boost::property_tree::read_json(filepath, jsontree);
}
catch(boost::property_tree::json_parser::json_parser_error &je)
{
std::cout << "Error reading JSON from secrets file: " << je.filename() << " on line: " << je.line() << std::endl;
std::cout << je.message() << std::endl;
}
catch(...)
{
printf("Error reading JSON from secrets file '%s' failed. Error %d, %s.\n",
filepathc, errno, strerror(errno));
}
//json_t* obj = json_load_file(filepathc, 0, &err);
string enc_cipher = jsontree.get<string>(field_cipher);
string enc_key = jsontree.get<string>(field_key);
//const char* enc_cipher = json_string_value(json_object_get(obj, field_cipher));
//const char* enc_key = json_string_value(json_object_get(obj, field_key));
bool cipher_ok = !enc_cipher.empty() && (enc_cipher == CIPHER_NAME);
if (cipher_ok && !enc_key.empty())
{
int read_hex_key_len = enc_key.length();
int expected_hex_key_len = 2 * binary_key_len;
if (read_hex_key_len == expected_hex_key_len)
{
rval.key.resize(binary_key_len);
hex2bin(enc_key.c_str(), read_hex_key_len, rval.key.data());
rval.ok = true;
}
else
{
CSPasswdLogging::get()->log(LOG_ERR,"Wrong encryption key length in secrets file '%s': found %i, expected %i.",
filepathc, read_hex_key_len, expected_hex_key_len);
}
}
else
{
CSPasswdLogging::get()->log(LOG_ERR,"Secrets file '%s' does not contain expected string fields '%s' and '%s', "
"or '%s' is not '%s'.",
filepathc, field_cipher, field_key, field_cipher, CIPHER_NAME);
}
jsontree.clear();
}
return rval;
}
string decrypt_password(const string& input)
{
const auto& key = this_unit.key;
string rval;
if (key.empty())
{
// Password encryption is not used, return original.
rval = input;
}
else
{
// If the input is not a HEX string, return the input as is.
auto is_hex = std::all_of(input.begin(), input.end(), isxdigit);
if (is_hex)
{
const auto& iv = this_unit.iv;
rval = iv.empty() ? ::decrypt_password(key, input) : decrypt_password_old(key, iv, input);
}
else
{
rval = input;
}
}
return rval;
}
/**
* Decrypt passwords encrypted with an old (pre 2.5) .secrets-file. The decryption also depends on whether
* the password was encrypted using maxpasswd 2.4 or 2.5.
*
* @param key Encryption key
* @param iv Init vector
* @param input Encrypted password in hex form
* @return Decrypted password or empty on error
*/
string decrypt_password_old(const ByteVec& key, const ByteVec& iv, const std::string& input)
{
string rval;
// Convert to binary.
size_t hex_len = input.length();
auto bin_len = hex_len / 2;
unsigned char encrypted_bin[bin_len];
hex2bin(input.c_str(), hex_len, encrypted_bin);
unsigned char plain[bin_len]; // Decryption output cannot be longer than input data.
int decrypted_len = 0;
if (encrypt_or_decrypt(key.data(), iv.data(), ProcessingMode::DECRYPT_IGNORE_ERRORS, encrypted_bin,
bin_len, plain, &decrypted_len))
{
if (decrypted_len > 0)
{
// Success, password was encrypted using 2.5. Decrypted data should be text.
auto output_data = reinterpret_cast<const char*>(plain);
rval.assign(output_data, decrypted_len);
}
else
{
// Failure, password was likely encrypted in 2.4. Try to decrypt using 2.4 code.
AES_KEY aeskey;
AES_set_decrypt_key(key.data(), 8 * key.size(), &aeskey);
auto iv_copy = iv;
memset(plain, '\0', bin_len);
AES_cbc_encrypt(encrypted_bin, plain, bin_len, &aeskey, iv_copy.data(), AES_DECRYPT);
rval = reinterpret_cast<const char*>(plain);
}
}
return rval;
}
string decrypt_password(const ByteVec& key, const std::string& input)
{
int total_hex_len = input.length();
string rval;
// Extract IV.
auto ptr = input.data();
int iv_bin_len = secrets_ivlen();
int iv_hex_len = 2 * iv_bin_len;
uint8_t iv_bin[iv_bin_len];
if (total_hex_len >= iv_hex_len)
{
hex2bin(ptr, iv_hex_len, iv_bin);
int encrypted_hex_len = total_hex_len - iv_hex_len;
int encrypted_bin_len = encrypted_hex_len / 2;
unsigned char encrypted_bin[encrypted_bin_len];
hex2bin(ptr + iv_hex_len, encrypted_hex_len, encrypted_bin);
uint8_t decrypted[encrypted_bin_len]; // Decryption output cannot be longer than input data.
int decrypted_len = 0;
if (encrypt_or_decrypt(key.data(), iv_bin, ProcessingMode::DECRYPT, encrypted_bin, encrypted_bin_len,
decrypted, &decrypted_len))
{
// Decrypted data should be text.
auto output_data = reinterpret_cast<const char*>(decrypted);
rval.assign(output_data, decrypted_len);
}
}
return rval;
}
/**
* Encrypt a password that can be stored in the Columnstore configuration file.
*
* @param key Encryption key and init vector
* @param input The plaintext password to encrypt.
* @return The encrypted password, or empty on failure.
*/
string encrypt_password_old(const ByteVec& key, const ByteVec& iv, const string& input)
{
string rval;
// Output can be a block length longer than input.
auto input_len = input.length();
unsigned char encrypted_bin[input_len + AES_BLOCK_SIZE];
// Although input is text, interpret as binary.
auto input_data = reinterpret_cast<const uint8_t*>(input.c_str());
int encrypted_len = 0;
if (encrypt_or_decrypt(key.data(), iv.data(), ProcessingMode::ENCRYPT,
input_data, input_len, encrypted_bin, &encrypted_len))
{
int hex_len = 2 * encrypted_len;
char hex_output[hex_len + 1];
bin2hex(encrypted_bin, encrypted_len, hex_output);
rval.assign(hex_output, hex_len);
}
return rval;
}
string encrypt_password(const ByteVec& key, const string& input)
{
string rval;
// Generate random IV.
auto ivlen = secrets_ivlen();
unsigned char iv_bin[ivlen];
if (RAND_bytes(iv_bin, ivlen) != 1)
{
printf("OpenSSL RAND_bytes() failed. %s.\n", ERR_error_string(ERR_get_error(), nullptr));
return rval;
}
// Output can be a block length longer than input.
auto input_len = input.length();
unsigned char encrypted_bin[input_len + EVP_CIPHER_block_size(secrets_cipher())];
// Although input is text, interpret as binary.
auto input_data = reinterpret_cast<const uint8_t*>(input.c_str());
int encrypted_len = 0;
if (encrypt_or_decrypt(key.data(), iv_bin, ProcessingMode::ENCRYPT,
input_data, input_len, encrypted_bin, &encrypted_len))
{
// Form one string with IV in front.
int iv_hex_len = 2 * ivlen;
int encrypted_hex_len = 2 * encrypted_len;
int total_hex_len = iv_hex_len + encrypted_hex_len;
char hex_output[total_hex_len + 1];
bin2hex(iv_bin, ivlen, hex_output);
bin2hex(encrypted_bin, encrypted_len, hex_output + iv_hex_len);
rval.assign(hex_output, total_hex_len);
}
return rval;
}
bool load_encryption_keys()
{
if(this_unit.key.empty() || this_unit.iv.empty())
{
string path(MCSDATADIR);
path.append("/").append(SECRETS_FILENAME);
auto ret = secrets_readkeys(path);
if (ret.ok)
{
if (!ret.key.empty())
{
CSPasswdLogging::get()->log(LOG_INFO,"Using encrypted passwords. Encryption key read from '%s'.", path.c_str());
this_unit.key = move(ret.key);
this_unit.iv = move(ret.iv);
}
else
{
CSPasswdLogging::get()->log(LOG_INFO,"Password encryption key file '%s' not found, using configured passwords as "
"plaintext.", path.c_str());
}
return ret.ok;
}
}
return true;
}
/**
* Write encryption key to JSON-file. Also sets file permissions and owner.
*
* @param key Encryption key in binary form
* @param filepath The full path to the file to write to
* @param owner The final owner of the file. Changing the owner does not always succeed.
* @return True on total success. Even if false is returned, the file may have been written.
*/
bool secrets_write_keys(const ByteVec& key, const string& filepath, const string& owner)
{
auto keylen = key.size();
char key_hex[2 * keylen + 1];
bin2hex(key.data(), keylen, key_hex);
boost::property_tree::ptree jsontree;
jsontree.put(field_desc,desc);
jsontree.put(field_version,columnstore_version.c_str());
jsontree.put(field_cipher,CIPHER_NAME);
jsontree.put(field_key,(const char*)key_hex);
auto filepathc = filepath.c_str();
bool write_ok = false;
errno = 0;
try
{
write_json(filepathc, jsontree);
}
catch(boost::property_tree::json_parser::json_parser_error &je)
{
std::cout << "Write to secrets file: " << je.filename() << " on line: " << je.line() << std::endl;
std::cout << je.message() << std::endl;
}
catch(...)
{
printf("Write to secrets file '%s' failed. Error %d, %s.\n",
filepathc, errno, strerror(errno));
}
write_ok = true;
jsontree.clear();
bool rval = false;
if (write_ok)
{
// Change file permissions to prevent modifications.
errno = 0;
if (chmod(filepathc, S_IRUSR) == 0)
{
printf("Permissions of '%s' set to owner:read.\n", filepathc);
auto ownerz = owner.c_str();
auto userinfo = getpwnam(ownerz);
if (userinfo)
{
if (chown(filepathc, userinfo->pw_uid, userinfo->pw_gid) == 0)
{
printf("Ownership of '%s' given to %s.\n", filepathc, ownerz);
rval = true;
}
else
{
printf("Failed to give '%s' ownership of '%s': %d, %s.\n",
ownerz, filepathc, errno, strerror(errno));
}
}
else
{
printf("Could not find user '%s' when attempting to change ownership of '%s': %d, %s.\n",
ownerz, filepathc, errno, strerror(errno));
}
}
else
{
printf("Failed to change the permissions of the secrets file '%s'. Error %d, %s.\n",
filepathc, errno, strerror(errno));
}
}
return rval;
}

81
tools/passwd/secrets.h Normal file
View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2018 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2025-05-25
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include <vector>
#include <string>
#include "idberrorinfo.h"
#include "logger.h"
class CSPasswdLogging
{
public:
static CSPasswdLogging *get();
~CSPasswdLogging();
void log(int priority, const char *format, ...);
private:
CSPasswdLogging();
};
/**
* Decrypt an encrypted password using the key loaded at startup. If the password is not encrypted,
* ie is not a HEX string, return the original.
*
* @param input The encrypted password
* @return The decrypted password.
*/
std::string decrypt_password(const std::string& input);
#include <memory>
using ByteVec = std::vector<uint8_t>;
struct evp_cipher_st;
extern const char* const SECRETS_FILENAME;
/**
* Returns the cipher used for password encryption.
*
* @return Cipher
*/
const evp_cipher_st* secrets_cipher();
/**
* Returns encryption key length.
*
* @return Encryption key length
*/
int secrets_keylen();
/**
* Returns initialization vector length.
*
* @return initialization vector length
*/
int secrets_ivlen();
bool load_encryption_keys();
std::string encrypt_password_old(const ByteVec& key, const ByteVec& iv, const std::string& input);
std::string encrypt_password(const ByteVec& key, const std::string& input);
std::string decrypt_password_old(const ByteVec& key, const ByteVec& iv, const std::string& input);
std::string decrypt_password(const ByteVec& key, const std::string& input);
struct ReadKeyResult
{
bool ok {false};
ByteVec key;
ByteVec iv;
};
ReadKeyResult secrets_readkeys(const std::string& filepath);
bool secrets_write_keys(const ByteVec& key, const std::string& filepath, const std::string& owner);