338 lines
9.3 KiB
C
338 lines
9.3 KiB
C
/*
|
|
* This file is distributed as part of the SkySQL Gateway. It 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.
|
|
*
|
|
* 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Copyright SkySQL Ab 2013
|
|
*/
|
|
|
|
#include <secrets.h>
|
|
#include <time.h>
|
|
#include <skygw_utils.h>
|
|
#include <log_manager.h>
|
|
#include <ctype.h>
|
|
#include <mysql_client_server_protocol.h>
|
|
|
|
extern int lm_enabled_logfiles_bitmask;
|
|
/**
|
|
* Generate a random printable character
|
|
*
|
|
* @return A random printable character
|
|
*/
|
|
static unsigned char
|
|
secrets_randomchar()
|
|
{
|
|
return (char)((rand() % ('~' - ' ')) + ' ');
|
|
}
|
|
|
|
static int
|
|
secrets_random_str(unsigned char *output, int len)
|
|
{
|
|
int i;
|
|
srand((unsigned long )time(0L) ^ (unsigned long )output);
|
|
|
|
for ( i = 0; i < len; ++i )
|
|
{
|
|
output[i] = secrets_randomchar();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* secrets_readKeys
|
|
*
|
|
* This routine reads data from a binary file and extracts the AES encryption key
|
|
* and the AES Init Vector
|
|
*
|
|
* @return The keys structure or NULL on error
|
|
*/
|
|
static MAXKEYS *
|
|
secrets_readKeys()
|
|
{
|
|
char secret_file[255];
|
|
char *home;
|
|
MAXKEYS *keys;
|
|
struct stat secret_stats;
|
|
int fd;
|
|
int len;
|
|
|
|
home = getenv("MAXSCALE_HOME");
|
|
|
|
if (home == NULL) {
|
|
home = "/usr/local/skysql/MaxScale";
|
|
}
|
|
snprintf(secret_file, 255, "%s/etc/.secrets", home);
|
|
|
|
/* Try to access secrets file */
|
|
if (access(secret_file, R_OK) == -1) {
|
|
int eno = errno;
|
|
errno = 0;
|
|
LOGIF(LE, (skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : access for secrets file "
|
|
"[%s] failed. Error %d, %s.",
|
|
secret_file,
|
|
eno,
|
|
strerror(eno))));
|
|
return NULL;
|
|
}
|
|
|
|
/* open secret file */
|
|
if ((fd = open(secret_file, O_RDONLY)) < 0)
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
LOGIF(LE, (skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : Failed opening secret "
|
|
"file [%s]. Error %d, %s.",
|
|
secret_file,
|
|
eno,
|
|
strerror(eno))));
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/* accessing file details */
|
|
if (fstat(fd, &secret_stats) < 0) {
|
|
int eno = errno;
|
|
errno = 0;
|
|
LOGIF(LE, (skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : fstat for secret file %s "
|
|
"failed. Error %d, %s.",
|
|
secret_file,
|
|
eno,
|
|
strerror(eno))));
|
|
return NULL;
|
|
}
|
|
|
|
if (secret_stats.st_size != sizeof(MAXKEYS))
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
LOGIF(LE, (skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : Secrets file %s has "
|
|
"incorrect size. Error %d, %s.",
|
|
secret_file,
|
|
eno,
|
|
strerror(eno))));
|
|
return NULL;
|
|
}
|
|
if (secret_stats.st_mode != (S_IRUSR|S_IFREG))
|
|
{
|
|
LOGIF(LE, (skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : Ignoring secrets file "
|
|
"%s, invalid permissions.",
|
|
secret_file)));
|
|
return NULL;
|
|
}
|
|
|
|
if ((keys = (MAXKEYS *)malloc(sizeof(MAXKEYS))) == NULL)
|
|
{
|
|
LOGIF(LE, (skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : Memory allocation failed "
|
|
"for key structure.")));
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Read all data from file.
|
|
* MAXKEYS (secrets.h) is struct for key, _not_ length-related macro.
|
|
*/
|
|
len = read(fd, keys, sizeof(MAXKEYS));
|
|
|
|
if (len != sizeof(MAXKEYS))
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
free(keys);
|
|
LOGIF(LE, (skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : Read from secrets file "
|
|
"%s failed. Read %d, expected %d bytes. Error %d, %s.",
|
|
secret_file,
|
|
len,
|
|
sizeof(MAXKEYS),
|
|
eno,
|
|
strerror(eno))));
|
|
return NULL;
|
|
}
|
|
|
|
/* Close the file */
|
|
if (close(fd) < 0) {
|
|
int eno = errno;
|
|
errno = 0;
|
|
free(keys);
|
|
LOGIF(LE, (skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : Failed closing the "
|
|
"secrets file %s. Error %d, %s.",
|
|
secret_file,
|
|
eno,
|
|
strerror(eno))));
|
|
return NULL;
|
|
}
|
|
ss_dassert(keys != NULL);
|
|
return keys;
|
|
}
|
|
|
|
/**
|
|
* secrets_writeKeys
|
|
*
|
|
* This routine writes into a binary file the AES encryption key
|
|
* and the AES Init Vector
|
|
*
|
|
* @param secret_file The file with secret keys
|
|
* @return 0 on success and 1 on failure
|
|
*/
|
|
int secrets_writeKeys(char *secret_file)
|
|
{
|
|
int fd;
|
|
MAXKEYS key;
|
|
|
|
/* Open for writing | Create | Truncate the file for writing */
|
|
if ((fd = open(secret_file, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR)) < 0)
|
|
{
|
|
LOGIF(LE, (skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : failed opening secret "
|
|
"file [%s]. Error %d, %s.",
|
|
secret_file,
|
|
errno,
|
|
strerror(errno))));
|
|
return 1;
|
|
}
|
|
|
|
srand(time(NULL));
|
|
secrets_random_str(key.enckey, MAXSCALE_KEYLEN);
|
|
secrets_random_str(key.initvector, MAXSCALE_IV_LEN);
|
|
|
|
/* Write data */
|
|
if (write(fd, &key, sizeof(key)) < 0)
|
|
{
|
|
LOGIF(LE, (skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : failed writing into "
|
|
"secret file [%s]. Error %d, %s.",
|
|
secret_file,
|
|
errno,
|
|
strerror(errno))));
|
|
return 1;
|
|
}
|
|
|
|
/* close file */
|
|
if (close(fd) < 0)
|
|
{
|
|
LOGIF(LE, (skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : failed closing the "
|
|
"secret file [%s]. Error %d, %s.",
|
|
secret_file,
|
|
errno,
|
|
strerror(errno))));
|
|
}
|
|
chmod(secret_file, S_IRUSR);
|
|
|
|
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
|
|
*/
|
|
char *
|
|
decryptPassword(char *crypt)
|
|
{
|
|
MAXKEYS *keys;
|
|
AES_KEY aeskey;
|
|
unsigned char *plain;
|
|
char *ptr;
|
|
unsigned char encrypted[80];
|
|
int enlen;
|
|
|
|
keys = secrets_readKeys();
|
|
if (!keys)
|
|
return 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))
|
|
{
|
|
free(keys);
|
|
return strdup(crypt);
|
|
}
|
|
}
|
|
|
|
enlen = strlen(crypt) / 2;
|
|
gw_hex2bin(encrypted, crypt, strlen(crypt));
|
|
|
|
if ((plain = (unsigned char *)malloc(80)) == NULL)
|
|
{
|
|
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);
|
|
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 password The password to encrypt
|
|
* @return The encrypted password
|
|
*/
|
|
char *
|
|
encryptPassword(char *password)
|
|
{
|
|
MAXKEYS *keys;
|
|
AES_KEY aeskey;
|
|
int padded_len;
|
|
char *hex_output;
|
|
unsigned char padded_passwd[80];
|
|
unsigned char encrypted[80];
|
|
|
|
if ((keys = secrets_readKeys()) == NULL)
|
|
return NULL;
|
|
|
|
memset(padded_passwd, 0, 80);
|
|
strcpy((char *)padded_passwd, password);
|
|
padded_len = ((strlen(password) / 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 *)malloc(padded_len * 2);
|
|
gw_bin2hex(hex_output, encrypted, padded_len);
|
|
free(keys);
|
|
|
|
return hex_output;
|
|
}
|