
The internal header directory conflicted with in-source builds causing a build failure. This is fixed by renaming the internal header directory to something other than maxscale. The renaming pointed out a few problems in a couple of source files that appeared to include internal headers when the headers were in fact public headers. Fixed maxctrl in-source builds by making the copying of the sources optional.
426 lines
11 KiB
C++
426 lines
11 KiB
C++
/*
|
|
* 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: 2020-01-01
|
|
*
|
|
* 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 <maxscale/secrets.h>
|
|
|
|
#include <ctype.h>
|
|
#include <openssl/aes.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
|
|
#include <maxscale/alloc.h>
|
|
#include <maxscale/log_manager.h>
|
|
#include <maxscale/paths.h>
|
|
#include <maxscale/protocol/mysql.h>
|
|
#include <maxscale/random_jkiss.h>
|
|
#include <maxscale/utils.h>
|
|
|
|
#include "internal/secrets.h"
|
|
|
|
/**
|
|
* Generate a random printable character
|
|
*
|
|
* @return A random printable character
|
|
*/
|
|
static unsigned char
|
|
secrets_randomchar()
|
|
{
|
|
return (char) ((random_jkiss() % ('~' - ' ')) + ' ');
|
|
}
|
|
|
|
static int
|
|
secrets_random_str(unsigned char *output, int len)
|
|
{
|
|
for (int i = 0; i < len; ++i)
|
|
{
|
|
output[i] = secrets_randomchar();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* This routine reads data from a binary file named ".secrets" and extracts the AES encryption key
|
|
* and the AES Init Vector.
|
|
* If the path parameter is not null the custom path is interpreted as a folder
|
|
* containing the .secrets file. Otherwise the default location is used.
|
|
* @return The keys structure or NULL on error
|
|
*/
|
|
static MAXKEYS *
|
|
secrets_readKeys(const char* path)
|
|
{
|
|
static const char NAME[] = ".secrets";
|
|
char secret_file[PATH_MAX + 1 + sizeof(NAME)]; // Worst case: maximum path + "/" + name.
|
|
MAXKEYS *keys;
|
|
struct stat secret_stats;
|
|
static int reported = 0;
|
|
|
|
if (path != NULL)
|
|
{
|
|
size_t len = strlen(path);
|
|
if (len > PATH_MAX)
|
|
{
|
|
MXS_ERROR("Too long (%lu > %d) path provided.", len, PATH_MAX);
|
|
return NULL;
|
|
}
|
|
|
|
if (stat(path, &secret_stats) == 0)
|
|
{
|
|
if (S_ISDIR(secret_stats.st_mode))
|
|
{
|
|
sprintf(secret_file, "%s/%s", path, NAME);
|
|
}
|
|
else
|
|
{
|
|
// If the provided path does not refer to a directory, then the
|
|
// file name *must* be ".secrets".
|
|
char *file;
|
|
if ((file = strrchr(secret_file, '.')) == NULL || strcmp(file, NAME) != 0)
|
|
{
|
|
MXS_ERROR("The name of the secrets file must be \"%s\".", NAME);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("The provided path \"%s\" does not exist or cannot be accessed. "
|
|
"Error: %d, %s.", path, errno, mxs_strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
clean_up_pathname(secret_file);
|
|
}
|
|
else
|
|
{
|
|
// We assume that get_datadir() returns a path shorter than PATH_MAX.
|
|
sprintf(secret_file, "%s/%s", get_datadir(), NAME);
|
|
}
|
|
/* Try to access secrets file */
|
|
if (access(secret_file, R_OK) == -1)
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
if (eno == ENOENT)
|
|
{
|
|
if (!reported)
|
|
{
|
|
MXS_NOTICE("Encrypted password file %s can't be accessed "
|
|
"(%s). Password encryption is not used.",
|
|
secret_file,
|
|
mxs_strerror(eno));
|
|
reported = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Access for secrets file "
|
|
"[%s] failed. Error %d, %s.",
|
|
secret_file,
|
|
eno,
|
|
mxs_strerror(eno));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* open secret file */
|
|
int fd = open(secret_file, O_RDONLY);
|
|
if (fd < 0)
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
MXS_ERROR("Failed opening secret "
|
|
"file [%s]. Error %d, %s.",
|
|
secret_file,
|
|
eno,
|
|
mxs_strerror(eno));
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/* accessing file details */
|
|
if (fstat(fd, &secret_stats) < 0)
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
close(fd);
|
|
MXS_ERROR("fstat for secret file %s "
|
|
"failed. Error %d, %s.",
|
|
secret_file,
|
|
eno,
|
|
mxs_strerror(eno));
|
|
return NULL;
|
|
}
|
|
|
|
if (secret_stats.st_size != sizeof(MAXKEYS))
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
close(fd);
|
|
MXS_ERROR("Secrets file %s has "
|
|
"incorrect size. Error %d, %s.",
|
|
secret_file,
|
|
eno,
|
|
mxs_strerror(eno));
|
|
return NULL;
|
|
}
|
|
if (secret_stats.st_mode != (S_IRUSR | S_IFREG))
|
|
{
|
|
close(fd);
|
|
MXS_ERROR("Ignoring secrets file %s, invalid permissions."
|
|
"The only permission on the file should be owner:read.",
|
|
secret_file);
|
|
return NULL;
|
|
}
|
|
|
|
if ((keys = (MAXKEYS *) MXS_MALLOC(sizeof(MAXKEYS))) == NULL)
|
|
{
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Read all data from file.
|
|
* MAXKEYS (secrets.h) is struct for key, _not_ length-related macro.
|
|
*/
|
|
ssize_t len = read(fd, keys, sizeof(MAXKEYS));
|
|
|
|
if (len != sizeof(MAXKEYS))
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
close(fd);
|
|
MXS_FREE(keys);
|
|
MXS_ERROR("Read from secrets file "
|
|
"%s failed. Read %ld, expected %d bytes. Error %d, %s.",
|
|
secret_file,
|
|
len,
|
|
(int)sizeof(MAXKEYS),
|
|
eno,
|
|
mxs_strerror(eno));
|
|
return NULL;
|
|
}
|
|
|
|
/* Close the file */
|
|
if (close(fd) < 0)
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
MXS_FREE(keys);
|
|
MXS_ERROR("Failed closing the "
|
|
"secrets file %s. Error %d, %s.",
|
|
secret_file,
|
|
eno,
|
|
mxs_strerror(eno));
|
|
return NULL;
|
|
}
|
|
ss_dassert(keys != NULL);
|
|
|
|
/** Successfully loaded keys, log notification */
|
|
if (!reported)
|
|
{
|
|
MXS_NOTICE("Using encrypted passwords. Encryption key: '%s'.", secret_file);
|
|
reported = 1;
|
|
}
|
|
|
|
return keys;
|
|
}
|
|
|
|
/**
|
|
* secrets_writeKeys
|
|
*
|
|
* This routine writes into a binary file the AES encryption key
|
|
* and the AES Init Vector
|
|
*
|
|
* @param dir The directory where the ".secrets" file should be created.
|
|
* @return 0 on success and 1 on failure
|
|
*/
|
|
int secrets_write_keys(const char *dir)
|
|
{
|
|
int fd, randfd;
|
|
unsigned int randval;
|
|
MAXKEYS key;
|
|
char secret_file[PATH_MAX + 10];
|
|
|
|
if (strlen(dir) > PATH_MAX)
|
|
{
|
|
MXS_ERROR("Pathname too long.");
|
|
return 1;
|
|
}
|
|
|
|
snprintf(secret_file, PATH_MAX + 9, "%s/.secrets", dir);
|
|
clean_up_pathname(secret_file);
|
|
|
|
/* Open for writing | Create | Truncate the file for writing */
|
|
if ((fd = open(secret_file, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR)) < 0)
|
|
{
|
|
MXS_ERROR("failed opening secret "
|
|
"file [%s]. Error %d, %s.",
|
|
secret_file,
|
|
errno,
|
|
mxs_strerror(errno));
|
|
return 1;
|
|
}
|
|
|
|
/* Open for writing | Create | Truncate the file for writing */
|
|
if ((randfd = open("/dev/random", O_RDONLY)) < 0)
|
|
{
|
|
MXS_ERROR("failed opening /dev/random. Error %d, %s.",
|
|
errno,
|
|
mxs_strerror(errno));
|
|
close(fd);
|
|
return 1;
|
|
}
|
|
|
|
if (read(randfd, (void*) &randval, sizeof(unsigned int)) < 1)
|
|
{
|
|
MXS_ERROR("failed to read /dev/random.");
|
|
close(fd);
|
|
close(randfd);
|
|
return 1;
|
|
}
|
|
|
|
close(randfd);
|
|
secrets_random_str(key.enckey, MAXSCALE_KEYLEN);
|
|
secrets_random_str(key.initvector, MAXSCALE_IV_LEN);
|
|
|
|
/* Write data */
|
|
if (write(fd, &key, sizeof(key)) < 0)
|
|
{
|
|
MXS_ERROR("failed writing into "
|
|
"secret file [%s]. Error %d, %s.",
|
|
secret_file,
|
|
errno,
|
|
mxs_strerror(errno));
|
|
close(fd);
|
|
return 1;
|
|
}
|
|
|
|
/* close file */
|
|
if (close(fd) < 0)
|
|
{
|
|
MXS_ERROR("failed closing the "
|
|
"secret file [%s]. Error %d, %s.",
|
|
secret_file,
|
|
errno,
|
|
mxs_strerror(errno));
|
|
}
|
|
|
|
if (chmod(secret_file, S_IRUSR) < 0)
|
|
{
|
|
MXS_ERROR("failed to change the permissions of the"
|
|
"secret file [%s]. Error %d, %s.",
|
|
secret_file,
|
|
errno,
|
|
mxs_strerror(errno));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Decrypt a password that is stored inthe MaxScale configuration file.
|
|
* If the password is not encrypted, ie is not a HEX string, then the
|
|
* original is returned, this allows for backward compatibility with
|
|
* unencrypted password.
|
|
*
|
|
* Note the return is always a malloc'd string that the caller must free
|
|
*
|
|
* @param crypt The encrypted password
|
|
* @return The decrypted password or NULL if allocation failure.
|
|
*/
|
|
char *
|
|
decrypt_password(const char *crypt)
|
|
{
|
|
MAXKEYS *keys;
|
|
AES_KEY aeskey;
|
|
unsigned char *plain;
|
|
const char *ptr;
|
|
unsigned char encrypted[80];
|
|
int enlen;
|
|
|
|
keys = secrets_readKeys(NULL);
|
|
if (!keys)
|
|
{
|
|
return MXS_STRDUP(crypt);
|
|
}
|
|
/*
|
|
** If the input is not a HEX string return the input
|
|
** it probably was not encrypted
|
|
*/
|
|
for (ptr = crypt; *ptr; ptr++)
|
|
{
|
|
if (!isxdigit(*ptr))
|
|
{
|
|
MXS_FREE(keys);
|
|
return MXS_STRDUP(crypt);
|
|
}
|
|
}
|
|
|
|
enlen = strlen(crypt) / 2;
|
|
gw_hex2bin(encrypted, crypt, strlen(crypt));
|
|
|
|
if ((plain = (unsigned char *) MXS_MALLOC(enlen + 1)) == NULL)
|
|
{
|
|
MXS_FREE(keys);
|
|
return NULL;
|
|
}
|
|
|
|
AES_set_decrypt_key(keys->enckey, 8 * MAXSCALE_KEYLEN, &aeskey);
|
|
|
|
AES_cbc_encrypt(encrypted, plain, enlen, &aeskey, keys->initvector, AES_DECRYPT);
|
|
plain[enlen] = '\0';
|
|
MXS_FREE(keys);
|
|
|
|
return (char *) plain;
|
|
}
|
|
|
|
/**
|
|
* Encrypt a password that can be stored in the MaxScale configuration file.
|
|
*
|
|
* Note the return is always a malloc'd string that the caller must free
|
|
* @param path Path the the .secrets file
|
|
* @param password The password to encrypt
|
|
* @return The encrypted password
|
|
*/
|
|
char *
|
|
encrypt_password(const char* path, const char *password)
|
|
{
|
|
MAXKEYS *keys;
|
|
AES_KEY aeskey;
|
|
int padded_len;
|
|
char *hex_output;
|
|
unsigned char padded_passwd[MXS_PASSWORD_MAXLEN + 1];
|
|
unsigned char encrypted[MXS_PASSWORD_MAXLEN + 1];
|
|
|
|
if ((keys = secrets_readKeys(path)) == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
memset(padded_passwd, 0, MXS_PASSWORD_MAXLEN + 1);
|
|
strncpy((char *) padded_passwd, password, MXS_PASSWORD_MAXLEN);
|
|
padded_len = ((strlen((char*)padded_passwd) / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;
|
|
|
|
AES_set_encrypt_key(keys->enckey, 8 * MAXSCALE_KEYLEN, &aeskey);
|
|
|
|
AES_cbc_encrypt(padded_passwd, encrypted, padded_len, &aeskey, keys->initvector, AES_ENCRYPT);
|
|
hex_output = (char *) MXS_MALLOC(padded_len * 2 + 1);
|
|
if (hex_output)
|
|
{
|
|
gw_bin2hex(hex_output, encrypted, padded_len);
|
|
}
|
|
MXS_FREE(keys);
|
|
|
|
return hex_output;
|
|
}
|