|
- /*
- Copyright (c) 2015, 2021, Oracle and/or its affiliates.
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License, version 2.0,
- as published by the Free Software Foundation.
-
- This program is also distributed with certain software (including
- but not limited to OpenSSL) that is licensed under separate terms,
- as designated in a particular file or component or in included license
- documentation. The authors of MySQL hereby grant you an additional
- permission to link the program and your derivative works with the
- separately licensed software that they have included with MySQL.
-
- 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, version 2.0, 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-1301 USA
- */
-
- /* C Headers */
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <stdint.h>
-
- /* C++ Headers */
- #include <iostream>
- #include <cstdlib>
- #include <cstdio>
- #include <string>
- #include <sstream>
- #include <fstream>
- #include <algorithm>
-
- /* MySQL Headers */
- #include <my_sys.h>
- #include <my_dir.h>
- #include <my_default.h>
- #include <my_global.h>
- #include <my_config.h>
- #include <my_getopt.h>
- #include <welcome_copyright_notice.h> /* ORACLE_WELCOME_COPYRIGHT_NOTICE */
- #include <mysql_version.h>
- #include "path.h"
- #include "logger.h"
-
- #if HAVE_CHOWN
- #include <pwd.h>
- #endif
- /* Utility Version */
- #define MY_VERSION "1.0.0"
-
- /* Forward declarations */
-
- using namespace std;
- typedef string Sql_string_t;
-
- static Sql_string_t create_string(const char *ptr);
-
- /* Global Variables */
- enum certs
- {
- CA_CERT=0,
- CA_KEY,
- CA_REQ,
- SERVER_CERT,
- SERVER_KEY,
- SERVER_REQ,
- CLIENT_CERT,
- CLIENT_KEY,
- CLIENT_REQ,
- PRIVATE_KEY,
- PUBLIC_KEY,
- OPENSSL_RND
- };
-
- enum extfiles
- {
- CAV3_EXT=0,
- CERTV3_EXT
- };
-
- Sql_string_t cert_files[] =
- {
- create_string("ca.pem"),
- create_string("ca-key.pem"),
- create_string("ca-req.pem"),
- create_string("server-cert.pem"),
- create_string("server-key.pem"),
- create_string("server-req.pem"),
- create_string("client-cert.pem"),
- create_string("client-key.pem"),
- create_string("client-req.pem"),
- create_string("private_key.pem"),
- create_string("public_key.pem"),
- create_string(".rnd")
- };
-
- Sql_string_t ext_files[] =
- {
- create_string("cav3.ext"),
- create_string("certv3.ext")
- };
-
- #define MAX_PATH_LEN (FN_REFLEN - strlen(FN_DIRSEP) \
- - cert_files[SERVER_CERT].length() - 1)
- /*
- Higest number of fixed characters in subject line is 47:
- MySQL_SERVER_<suffix>_Auto_Generated_Server_Certificate
- Maximum size of subject is 64. So suffix can't be longer
- than 17 characters.
- */
- #define MAX_SUFFIX_LEN 17
-
- Log info(cout,"NOTE");
- Log error(cerr,"ERROR");
-
- char **defaults_argv= 0;
- static char *opt_datadir= 0;
- static char default_data_dir[]= MYSQL_DATADIR;
- static char *opt_suffix= 0;
- static char default_suffix[]= MYSQL_SERVER_VERSION;
- #if HAVE_CHOWN
- static char *opt_userid= 0;
- struct passwd *user_info= 0;
- #endif /* HAVE_CHOWN */
- Path dir_string;
- Sql_string_t suffix_string;
- my_bool opt_verbose;
-
- static const char *load_default_groups[]=
- {
- "mysql_ssl_rsa_setup",
- "mysql_install_db",
- "mysqld",
- 0
- };
-
- static struct my_option my_options[]= {
- {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG,
- NO_ARG, 0, 0, 0, 0, 0, 0},
- {"verbose", 'v', "Be more verbose when running program",
- &opt_verbose, 0, 0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
- {"version", 'V', "Print program version and exit", 0, 0, 0, GET_NO_ARG,
- NO_ARG, 0, 0, 0, 0, 0, 0},
- {"datadir", 'd', "Directory to store generated files.", &opt_datadir, &opt_datadir, 0,
- GET_STR_ALLOC, REQUIRED_ARG, (longlong)&default_data_dir, 0, 0, 0, 0, 0},
- {"suffix", 's',
- "Suffix to be added in certificate subject line", &opt_suffix,
- &opt_suffix, 0, GET_STR_ALLOC, REQUIRED_ARG, (longlong) &default_suffix,
- 0, 0, 0, 0, 0},
- #if HAVE_CHOWN
- {"uid", 0, "The effective user id to be used for file permission",
- &opt_userid, &opt_userid, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0,
- 0, 0, 0},
- #endif /* HAVE_CHOWN */
- /* END TOKEN */
- {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
- };
-
- /* Helper Functions */
-
- /**
- The string class will break if constructed with a NULL pointer. This wrapper
- provides a systematic protection when importing char pointers.
- */
- static
- Sql_string_t create_string(const char *ptr)
- {
- return (ptr ? Sql_string_t(ptr) : Sql_string_t(""));
- }
-
-
- static
- int execute_command(const Sql_string_t &command,
- const Sql_string_t &error_message)
- {
- stringstream cmd_string;
-
- cmd_string << command;
- if (!opt_verbose)
- {
- #ifndef _WIN32
- cmd_string << " > /dev/null 2>&1";
- #else
- cmd_string << " > NUL 2>&1";
- #endif /* _WIN32 */
- }
-
- info << "Executing : " << cmd_string.str() << endl;
- if (system(cmd_string.str().c_str()))
- {
- error << error_message << endl;
- return 1;
- }
-
- return 0;
- }
-
-
- static
- int set_file_pair_permission(const Sql_string_t &priv,
- const Sql_string_t &pub)
- {
- if (MY_TEST(my_chmod(priv.c_str(),
- USER_READ| USER_WRITE, MYF(MY_FAE+MY_WME))) ||
- MY_TEST(my_chmod(pub.c_str(),
- USER_READ|USER_WRITE|GROUP_READ|OTHERS_READ,
- MYF(MY_FAE+MY_WME))))
- {
- error << "Error setting file permissions for" << priv.c_str()
- << " and " << pub.c_str() << endl;
- return 1;
- }
- #if HAVE_CHOWN
- if (user_info)
- {
- if(chown(priv.c_str(), user_info->pw_uid, user_info->pw_gid) ||
- chown(pub.c_str(), user_info->pw_uid, user_info->pw_gid))
- {
- error << "Failed to change file permission" << endl;
- return 1;
- }
- }
- #endif /* HAVE_CHOWN */
- return 0;
- }
-
-
- static
- bool file_exists(const Sql_string_t &filename)
- {
- MY_STAT file_stat;
- if (my_stat(filename.c_str(), &file_stat, MYF(0)) == NULL)
- return false;
-
- return true;
- }
-
-
- static
- int remove_file(const Sql_string_t &filename, bool report_error=true)
- {
- if (my_delete(filename.c_str(), MYF(0)))
- {
- if (report_error)
- error << "Error deleting : " << filename << endl;
- return 1;
- }
- return 0;
- }
-
-
- static
- void free_resources()
- {
- if (opt_datadir)
- my_free(opt_datadir);
- if (opt_suffix)
- my_free(opt_suffix);
- #if HAVE_CHOWN
- if (opt_userid)
- my_free(opt_userid);
- #endif
- if (defaults_argv && *defaults_argv)
- free_defaults(defaults_argv);
- }
-
-
- class RSA_priv
- {
- public:
- RSA_priv(uint32_t key_size= 2048)
- : m_key_size(key_size) {};
-
- ~RSA_priv() {};
-
- Sql_string_t operator()(const Sql_string_t &key_file)
- {
- stringstream command;
- command << "openssl genrsa " << " -out " << key_file << " " << m_key_size;
-
- return command.str();
- }
- private:
- uint32_t m_key_size;
- };
-
-
- class RSA_pub
- {
- public:
- Sql_string_t operator()(const Sql_string_t &priv_key_file,
- const Sql_string_t &pub_key_file)
- {
- stringstream command;
- command << "openssl rsa -in " << priv_key_file << " -pubout -out "
- << pub_key_file;
- return command.str();
- }
- };
-
-
- class X509_key
- {
- public:
- X509_key(const Sql_string_t &version,
- uint32_t validity= 10*365L)
- : m_validity(validity)
- {
- m_subj_prefix << "-subj /CN=MySQL_Server_" << version;
- }
-
- Sql_string_t operator()(Sql_string_t suffix,
- const Sql_string_t &key_file,
- const Sql_string_t &req_file)
- {
- stringstream command;
- command << "openssl req -newkey rsa:2048 -days " << m_validity
- << " -nodes -keyout " << key_file << " "
- << m_subj_prefix.str() << suffix << " -out " << req_file
- << " && openssl rsa -in " << key_file << " -out " << key_file;
-
- return command.str();
- }
-
- private:
- uint32_t m_validity;
- stringstream m_subj_prefix;
- };
-
- class X509v3_ext_writer
- {
- public:
- X509v3_ext_writer()
- {
- m_cav3_ext_options << "basicConstraints=CA:TRUE" << std::endl;
-
- m_certv3_ext_options << "basicConstraints=CA:FALSE" << std::endl;
- }
- ~X509v3_ext_writer() {};
-
- bool operator()(const Sql_string_t &cav3_ext_file,
- const Sql_string_t &certv3_ext_file)
- {
- if (!cav3_ext_file.length() ||
- !certv3_ext_file.length())
- return true;
-
- std::ofstream ext_file;
-
- ext_file.open(cav3_ext_file.c_str(),
- std::ios::out|std::ios::trunc);
- if (!ext_file.is_open())
- return true;
- ext_file << m_cav3_ext_options.str();
- ext_file.close();
-
- ext_file.open(certv3_ext_file.c_str(),
- std::ios::out|std::ios::trunc);
- if (!ext_file.is_open())
- {
- remove_file(cav3_ext_file.c_str(), false);
- return true;
- }
- ext_file << m_certv3_ext_options.str();
- ext_file.close();
-
- return false;
- }
- private:
- stringstream m_cav3_ext_options;
- stringstream m_certv3_ext_options;
- };
-
- class X509_cert
- {
- public:
- X509_cert(uint32_t validity= 10*365L)
- : m_validity(validity) {};
-
- ~X509_cert() {};
-
- Sql_string_t operator()(const Sql_string_t &req_file,
- const Sql_string_t &cert_file,
- uint32_t serial,
- bool self_signed,
- const Sql_string_t &sign_key_file,
- const Sql_string_t &sign_cert_file,
- const Sql_string_t &ext_file)
- {
- stringstream command;
- command << "openssl x509 -sha256 -days " << m_validity;
- command << " -extfile " << ext_file;
- command << " -set_serial " << serial << " -req -in " << req_file;
- if (self_signed)
- command << " -signkey " << sign_key_file;
- else
- command << " -CA " << sign_cert_file << " -CAkey " << sign_key_file;
- command << " -out " << cert_file;
-
- return command.str();
- }
-
- protected:
- uint32_t m_validity;
- };
-
-
- static
- void print_version(void)
- {
- cout << my_progname << " Version : " << MY_VERSION
- << " Distribution : " << MYSQL_SERVER_VERSION
- << " For : " << SYSTEM_TYPE << " On : " << MACHINE_TYPE << endl;
- }
-
-
- static
- void usage(void)
- {
- print_version();
- cout << (ORACLE_WELCOME_COPYRIGHT_NOTICE(COPYRIGHT_NOTICE_STONEDB_BEGIN_YEAR)) << endl
- << "MySQL SSL Certificate and RSA Key Generation Utility" << endl
- << "Usage : " << my_progname << " [OPTIONS]" << endl;
-
- my_print_help(my_options);
- my_print_variables(my_options);
- }
-
-
- my_bool
- my_arguments_get_one_option(int optid,
- const struct my_option *opt MY_ATTRIBUTE((unused)),
- char *argument)
- {
- switch(optid){
- case '?':
- usage();
- free_resources();
- exit(0);
- case 'V':
- print_version();
- free_resources();
- exit(0);
- }
- return 0;
- }
-
-
- static inline
- bool is_not_alnum_underscore(char c)
- {
- return !(isalnum(c) || c == '_');
- }
-
- static
- bool check_suffix()
- {
- return (strcmp(opt_suffix, default_suffix) &&
- (find_if(suffix_string.begin(), suffix_string.end(),
- is_not_alnum_underscore) != suffix_string.end()));
- }
-
-
- int main(int argc, char *argv[])
- {
- int ret_val= 0;
- Sql_string_t openssl_check("openssl version");
- my_bool save_skip_unknown= my_getopt_skip_unknown;
-
- MY_INIT(argv[0]);
- DBUG_ENTER("main");
- DBUG_PROCESS(argv[0]);
-
- /* Parse options : Command Line/Config file */
-
- #ifdef _WIN32
- /* Convert command line parameters from UTF16LE to UTF8MB4. */
- my_win_translate_command_line_args(&my_charset_utf8mb4_bin, &argc, &argv);
- #endif
- my_getopt_use_args_separator= TRUE;
- if (load_defaults("my", load_default_groups, &argc, &argv))
- {
- my_end(0);
- free_resources();
- exit(1);
- }
-
- MY_MODE file_creation_mode= get_file_perm(USER_READ | USER_WRITE);
- MY_MODE saved_umask= umask(~(file_creation_mode));
-
- defaults_argv= argv;
- my_getopt_use_args_separator= FALSE;
- my_getopt_skip_unknown= TRUE;
-
- if (handle_options(&argc, &argv, my_options,
- my_arguments_get_one_option))
- {
- error << "Error parsing options" << endl;
- ret_val= 1;
- goto end;
- }
-
- my_getopt_skip_unknown= save_skip_unknown;
-
- /* Process opt_verbose */
- if (opt_verbose != TRUE)
- info.enabled(false);
-
- /* Process opt_datadir */
-
- dir_string.path(create_string(opt_datadir));
- if (dir_string.to_str().length() > MAX_PATH_LEN)
- {
- error << "Dir path is too long" << endl;
- ret_val= 1;
- goto end;
- }
-
- if (!dir_string.normalize_path() || !dir_string.exists())
- {
- error << "Failed to access directory pointed by --datadir. "
- << "Please make sure that directory exists and is "
- << "accessible by mysql_ssl_rsa_setup. Supplied value : "
- << dir_string.to_str() << endl;
- ret_val= 1;
- goto end;
- }
-
- info << "Destination directory: " << dir_string.to_str() << endl;
-
- /* Process opt_suffix */
-
- suffix_string.append(opt_suffix);
- if (suffix_string.length() > MAX_SUFFIX_LEN)
- {
- error << "Maximum number of characters allowed as the value for "
- << "--suffix are " << MAX_SUFFIX_LEN << endl;
- ret_val= 1;
- goto end;
- }
-
- if (check_suffix())
- {
- error << "Invalid string for --suffix option. Either use default value for "
- << "the option or provide a string with alphanumeric characters "
- << "and/or _ only." << endl;
- ret_val= 1;
- goto end;
- }
-
- if ((ret_val= execute_command(openssl_check, "Could not find OpenSSL on the system")))
- {
- goto end;
- }
- else
- {
- char save_wd[FN_REFLEN];
- bool files_exist= false;
- Sql_string_t verify("openssl verify -CAfile ");
-
- if (my_getwd(save_wd, FN_REFLEN-1, MYF(MY_WME)))
- {
- error << "Error saving current working directory" << endl;
- ret_val= 1;
- goto end;
- }
-
- if (my_setwd(dir_string.to_str().c_str(), MYF(MY_WME)))
- {
- error << "Error changing working directory" << endl;
- ret_val= 1;
- goto end;
- }
- #if HAVE_CHOWN
- if (opt_userid && geteuid() == 0)
- {
- user_info= getpwnam(opt_userid);
- if (!user_info)
- {
- error << "Error fetching user information" << endl;
- ret_val= 1;
- goto end;
- }
- }
- #endif /* HAVE_CHOWN */
-
- /*
- SSL Certificate Generation.
- 1. Check for ca.pem, server_cert.pem and server_key.pem at
- the directory location provided by --dir.
- 2. If none of these files are present, generate following
- files:
- ca.pem, ca_key.pem
- server_cert.pem, server_key.pem
- client_cert.pem, client_key.pem
- 3. If everything goes smoothly, set permission on files.
- */
-
- files_exist= file_exists(cert_files[CA_CERT]) ||
- file_exists(cert_files[SERVER_CERT]) ||
- file_exists(cert_files[SERVER_KEY]);
-
- if (files_exist)
- {
- info << "Certificate files are present in given dir. Skipping generation." << endl;
- }
- else
- {
- Sql_string_t empty_string("");
- X509_key x509_key(suffix_string);
- X509_cert x509_cert;
- X509v3_ext_writer x509v3_ext_writer;
-
- /* Delete existing files if any */
- remove_file(cert_files[CA_REQ], false);
- remove_file(cert_files[SERVER_REQ], false);
- remove_file(cert_files[CLIENT_REQ], false);
- remove_file(cert_files[CLIENT_CERT], false);
- remove_file(cert_files[CLIENT_KEY], false);
- remove_file(cert_files[OPENSSL_RND], false);
-
- /* Remove existing v3 extension files */
- remove_file(ext_files[CAV3_EXT], false);
- remove_file(ext_files[CERTV3_EXT], false);
-
- /* Create v3 extension files */
- if (x509v3_ext_writer(ext_files[CAV3_EXT], ext_files[CERTV3_EXT]))
- goto end;
-
- /* Generate CA Key and Certificate */
- if ((ret_val= execute_command(x509_key("_Auto_Generated_CA_Certificate",
- cert_files[CA_KEY], cert_files[CA_REQ]),
- "Error generating ca_key.pem and ca_req.pem")))
- goto end;
-
- if ((ret_val= execute_command(x509_cert(cert_files[CA_REQ], cert_files[CA_CERT], 1,
- true, cert_files[CA_KEY], empty_string,
- ext_files[CAV3_EXT]),
- "Error generating ca_cert.pem")))
- goto end;
-
- /* Generate Server Key and Certificate */
- if ((ret_val= execute_command(x509_key("_Auto_Generated_Server_Certificate",
- cert_files[SERVER_KEY], cert_files[SERVER_REQ]),
- "Error generating server_key.pem and server_req.pem")))
- goto end;
-
- if ((ret_val= execute_command(x509_cert(cert_files[SERVER_REQ], cert_files[SERVER_CERT], 2,
- false, cert_files[CA_KEY], cert_files[CA_CERT],
- ext_files[CERTV3_EXT]),
- "Error generating server_cert.pem")))
- goto end;
-
- /* Generate Client Key and Certificate */
- if ((ret_val= execute_command(x509_key("_Auto_Generated_Client_Certificate",
- cert_files[CLIENT_KEY], cert_files[CLIENT_REQ]),
- "Error generating client_key.pem and client_req.pem")))
- goto end;
-
- if ((ret_val= execute_command(x509_cert(cert_files[CLIENT_REQ], cert_files[CLIENT_CERT], 3,
- false, cert_files[CA_KEY], cert_files[CA_CERT],
- ext_files[CERTV3_EXT]),
- "Error generating client_cert.pem")))
- goto end;
-
- /* Verify generated certificates */
- verify.append(cert_files[CA_CERT]);
- verify.append(" ");
- verify.append(cert_files[SERVER_CERT]);
- verify.append(" ");
- verify.append(cert_files[CLIENT_CERT]);
- if ((ret_val= execute_command(verify, "Verification of X509 certificates failed.")))
- goto end;
-
- /* Set permission */
- if ((ret_val= (set_file_pair_permission(cert_files[CA_KEY],
- cert_files[CA_CERT]) |
- set_file_pair_permission(cert_files[SERVER_KEY],
- cert_files[SERVER_CERT]) |
- set_file_pair_permission(cert_files[CLIENT_KEY],
- cert_files[CLIENT_CERT]))))
- goto end;
-
- /* Remove request files : Flag an error if we can't delete them. */
- if ((ret_val= remove_file(cert_files[CA_REQ])))
- goto end;
-
- if ((ret_val= remove_file(cert_files[SERVER_REQ])))
- goto end;
-
- if ((ret_val= remove_file(cert_files[CLIENT_REQ])))
- goto end;
-
- remove_file(cert_files[OPENSSL_RND], false);
-
- /* Remove existing v3 extension files */
- remove_file(ext_files[CAV3_EXT], false);
- remove_file(ext_files[CERTV3_EXT], false);
-
- }
-
- /*
- RSA Key pair generation.
- 1. Check if private_key.pem or public_key.pem are present at
- the directory location provided by --dir.
- 2. If not, generate private_key.pem, public_key.pem and
- set permission after successful generation.
- */
-
- files_exist= file_exists(cert_files[PRIVATE_KEY]) ||
- file_exists(cert_files[PUBLIC_KEY]);
-
- if (files_exist)
- {
- info << "RSA key files are present in given dir. Skipping generation." << endl;
- }
- else
- {
- RSA_priv rsa_priv;
- RSA_pub rsa_pub;
-
- /* Remove existing file if any */
- remove_file(cert_files[OPENSSL_RND], false);
-
- if ((ret_val= execute_command(rsa_priv(cert_files[PRIVATE_KEY]),
- "Error generating private_key.pem")))
- goto end;
-
- if ((ret_val= execute_command(rsa_pub(cert_files[PRIVATE_KEY],
- cert_files[PUBLIC_KEY]),
- "Error generating public_key.pem")))
- goto end;
- /* Set Permission */
- if ((ret_val= set_file_pair_permission(cert_files[PRIVATE_KEY],
- cert_files[PUBLIC_KEY])))
- goto end;
-
- remove_file(cert_files[OPENSSL_RND], false);
- }
-
- if (my_setwd(save_wd, MYF(MY_WME)))
- {
- error << "Error changing working directory" << endl;
- ret_val= 1;
- goto end;
- }
- }
-
- info << "Success!" << endl;
-
- end:
-
- umask(saved_umask);
- free_resources();
-
- DBUG_RETURN(ret_val);
- }
|