diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9bab56200..f332af3d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -129,6 +129,7 @@ add_subdirectory(cryptonote_protocol) if(NOT IOS) add_subdirectory(simplewallet) add_subdirectory(gen_multisig) + add_subdirectory(gen_ssl_cert) add_subdirectory(daemonizer) add_subdirectory(daemon) add_subdirectory(blockchain_utilities) diff --git a/src/gen_ssl_cert/CMakeLists.txt b/src/gen_ssl_cert/CMakeLists.txt new file mode 100644 index 000000000..471df021b --- /dev/null +++ b/src/gen_ssl_cert/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (c) 2017-2019, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(gen_ssl_cert_sources + gen_ssl_cert.cpp) + +monero_add_executable(gen_ssl_cert + ${gen_ssl_cert_sources}) +target_link_libraries(gen_ssl_cert + PRIVATE + common + epee + version + ${EPEE_READLINE} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +add_dependencies(gen_ssl_cert + version) +set_property(TARGET gen_ssl_cert + PROPERTY + OUTPUT_NAME "monero-gen-ssl-cert") +install(TARGETS gen_ssl_cert DESTINATION bin) diff --git a/src/gen_ssl_cert/gen_ssl_cert.cpp b/src/gen_ssl_cert/gen_ssl_cert.cpp new file mode 100644 index 000000000..7a9b01700 --- /dev/null +++ b/src/gen_ssl_cert/gen_ssl_cert.cpp @@ -0,0 +1,254 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include "include_base_utils.h" +#include "file_io_utils.h" +#include "net/net_ssl.h" +#include "crypto/crypto.h" +#include "common/util.h" +#include "common/i18n.h" +#include "common/command_line.h" +#include "common/scoped_message_writer.h" +#include "common/password.h" +#include "version.h" + +namespace po = boost::program_options; + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "gen_ssl_cert" + +namespace gencert +{ + const char* tr(const char* str) + { + return i18n_translate(str, "tools::gen_ssl_cert"); + } + +} + +namespace +{ + const command_line::arg_descriptor arg_certificate_filename = {"certificate-filename", gencert::tr("Filename to save the certificate"), ""}; + const command_line::arg_descriptor arg_private_key_filename = {"private-key-filename", gencert::tr("Filename to save the private key"), ""}; + const command_line::arg_descriptor arg_passphrase = {"passphrase", gencert::tr("Passphrase with which to encrypt the private key"), ""}; + const command_line::arg_descriptor arg_passphrase_file = {"passphrase-file", gencert::tr("File containing the passphrase with which to encrypt the private key"), ""}; + const command_line::arg_descriptor arg_prompt_for_passphrase = {"prompt-for-passphrase", gencert::tr("Prompt for a passphrase with which to encrypt the private key"), false}; +} + +// adapted from openssl's apps/x509.c +static std::string get_fingerprint(X509 *cert, const EVP_MD *fdig) +{ + unsigned int j; + unsigned int n; + unsigned char md[EVP_MAX_MD_SIZE]; + std::string fingerprint; + + if (!X509_digest(cert, fdig, md, &n)) + { + tools::fail_msg_writer() << tr("Failed to create fingerprint: ") << ERR_reason_error_string(ERR_get_error()); + return fingerprint; + } + fingerprint.resize(n * 3 - 1); + char *out = &fingerprint[0]; + for (j = 0; j < n; ++j) + { + snprintf(out, 3 + (j + 1 < n), "%02X%s", md[j], (j + 1 == n) ? "" : ":"); + out += 3; + } + return fingerprint; +} + +int main(int argc, char* argv[]) +{ + TRY_ENTRY(); + + tools::on_startup(); + + po::options_description desc_cmd_only("Command line options"); + po::options_description desc_cmd_sett("Command line options and settings options"); + + command_line::add_arg(desc_cmd_sett, arg_certificate_filename); + command_line::add_arg(desc_cmd_sett, arg_private_key_filename); + command_line::add_arg(desc_cmd_sett, arg_passphrase); + command_line::add_arg(desc_cmd_sett, arg_passphrase_file); + command_line::add_arg(desc_cmd_sett, arg_prompt_for_passphrase); + + command_line::add_arg(desc_cmd_only, command_line::arg_help); + command_line::add_arg(desc_cmd_only, command_line::arg_version); + + po::options_description desc_options("Allowed options"); + desc_options.add(desc_cmd_only).add(desc_cmd_sett); + + po::variables_map vm; + bool r = command_line::handle_error_helper(desc_options, [&]() + { + po::store(po::parse_command_line(argc, argv, desc_options), vm); + po::notify(vm); + return true; + }); + if (!r) + return 1; + + if (command_line::get_arg(vm, command_line::arg_help)) + { + std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL << ENDL; + std::cout << desc_options << std::endl; + return 0; + } + if (command_line::get_arg(vm, command_line::arg_version)) + { + std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL; + return 0; + } + + const std::string certificate_filename = command_line::get_arg(vm, arg_certificate_filename); + if (certificate_filename.empty()) + { + tools::fail_msg_writer() << gencert::tr("Argument is needed: ") << "--" << arg_certificate_filename.name; + return 1; + } + const std::string private_key_filename = command_line::get_arg(vm, arg_private_key_filename); + if (private_key_filename.empty()) + { + tools::fail_msg_writer() << gencert::tr("Argument is needed: ") << "--" << arg_private_key_filename.name; + return 1; + } + + epee::wipeable_string private_key_passphrase; + if (command_line::get_arg(vm, arg_prompt_for_passphrase)) + { + auto pwd_container = tools::password_container::prompt(true, "Enter passphrase for the new SSL private key"); + if (!pwd_container) + { + tools::fail_msg_writer() << gencert::tr("Failed to read passphrase"); + return 1; + } + private_key_passphrase = pwd_container->password(); + } + else if (!command_line::is_arg_defaulted(vm, arg_passphrase_file)) + { + std::string passphrase_file = command_line::get_arg(vm, arg_passphrase_file); + if (!passphrase_file.empty()) + { + std::string passphrase; + if (!epee::file_io_utils::load_file_to_string(passphrase_file, passphrase)) + { + MERROR("Failed to load passphrase"); + return 1; + } + + // Remove line breaks the user might have inserted + boost::trim_right_if(passphrase, boost::is_any_of("\r\n")); + private_key_passphrase = passphrase; + memwipe(&passphrase[0], passphrase.size()); + } + } + else + { + private_key_passphrase = command_line::get_arg(vm, arg_passphrase); + } + if (private_key_passphrase.empty()) + tools::msg_writer(epee::console_color_yellow) << (boost::format(tr("Empty passphrase, the private key will be saved to disk unencrypted, use --%s to set a passphrase or --%s to prompt for one")) % arg_passphrase.name % arg_prompt_for_passphrase.name).str(); + + EVP_PKEY *pkey; + X509 *cert; + r = epee::net_utils::create_rsa_ssl_certificate(pkey, cert); + if (!r) + { + tools::fail_msg_writer() << gencert::tr("Failed to create certificate"); + return 1; + } + + // write cert + BIO *bio_cert = BIO_new(BIO_s_mem()); + r = PEM_write_bio_X509(bio_cert, cert); + if (!r) + { + BIO_free(bio_cert); + tools::fail_msg_writer() << gencert::tr("Failed to write certificate: ") << ERR_reason_error_string(ERR_get_error()); + return 1; + } + BUF_MEM *buf = NULL; + BIO_get_mem_ptr(bio_cert, &buf); + if (!buf || !buf->data || !buf->length) + { + BIO_free(bio_cert); + tools::fail_msg_writer() << gencert::tr("Failed to write certificate: ") << ERR_reason_error_string(ERR_get_error()); + return 1; + } + const std::string certificate(std::string(buf->data, buf->length)); + BIO_free(bio_cert); + + // write private key + BIO *bio_pkey = BIO_new(BIO_s_mem()); + r = PEM_write_bio_PKCS8PrivateKey(bio_pkey, pkey, private_key_passphrase.empty() ? NULL : EVP_aes_128_cfb(), private_key_passphrase.data(), private_key_passphrase.size(), NULL, NULL); + if (!r) + { + BIO_free(bio_pkey); + tools::fail_msg_writer() << gencert::tr("Failed to write private key: ") << ERR_reason_error_string(ERR_get_error()); + return 1; + } + buf = NULL; + BIO_get_mem_ptr(bio_pkey, &buf); + if (!buf || !buf->data || !buf->length) + { + BIO_free(bio_pkey); + tools::fail_msg_writer() << gencert::tr("Failed to write private key: ") << ERR_reason_error_string(ERR_get_error()); + return 1; + } + const std::string private_key(std::string(buf->data, buf->length)); + BIO_free(bio_pkey); + + // write files + tools::set_strict_default_file_permissions(true); + r = epee::file_io_utils::save_string_to_file(certificate_filename, certificate); + if (!r) + { + tools::fail_msg_writer() << gencert::tr("Failed to save certificate file"); + return 1; + } + r = epee::file_io_utils::save_string_to_file(private_key_filename, private_key); + if (!r) + { + tools::fail_msg_writer() << gencert::tr("Failed to save private key file"); + return 1; + } + + tools::success_msg_writer() << tr("New certificate created:"); + tools::success_msg_writer() << tr("Certificate: ") << certificate_filename; + tools::success_msg_writer() << tr("SHA-256 Fingerprint: ") << get_fingerprint(cert, EVP_sha256()); + tools::success_msg_writer() << tr("Private key: ") << private_key_filename << " (" << (private_key_passphrase.empty() ? "unencrypted" : "encrypted") << ")"; + + return 0; + CATCH_ENTRY_L0("main", 1); +}