Improve self-signed certificate generation:

When starting, the code generates a new ephemeral private key and
a corresponding certificate to go along with it. This process can
take time and, while this is unlikely to matter for normal server
operations, it can have a significant impact for unit testing and
development. Profiling data suggests that ~20% of the time needed
for a unit test run can be attributed to this.

This commit does several things:

1. It restructures the code so that a new self-signed certificate
   and its corresponding private key are only initialized once at
   startup; this has minimal impact on the operation of a regular
   server.
2. It provides new default DH parameters. This doesn't impact the
   security of the connection, but those who compile from scratch
   can generate new parameters if they so choose.
3. It properly sets the version number in the certificate, fixing
   issue #4007; thanks to @donovanhide for the report.
4. It uses SHA-256 instead of SHA-1 as the hash algorithm for the
   certificate and adds some X.509 extensions as well as a random
   128-bit serial number.
5. It rounds the certificate's "start of validity" period so that
   the server's precise startup time cannot be easily deduced and
   limits the validity period to two years, down from ten years.
6. It removes some CBC-based ciphers from the default cipher list
   to avoid some potential security issues, such as CVE-2016-2107
   and CVE-2013-0169.
This commit is contained in:
Nik Bougalis
2022-05-30 21:05:10 -07:00
parent e2eed966b0
commit 0ecfc7cb1a

View File

@@ -17,18 +17,55 @@
*/
//==============================================================================
#include <ripple/basics/chrono.h>
#include <ripple/basics/contract.h>
#include <ripple/basics/make_SSLContext.h>
#include <ripple/beast/container/aged_unordered_set.h>
#include <cstdint>
#include <sstream>
#include <ctime>
#include <stdexcept>
namespace ripple {
namespace openssl {
namespace detail {
/** The default strength of self-signed RSA certifices.
Per NIST Special Publication 800-57 Part 3, 2048-bit RSA is still
considered acceptably secure. Generally, we would want to go above
and beyond such recommendations (e.g. by using 3072 or 4096 bits)
but there is a computational cost associated with that may not
be worth paying, considering that:
- We regenerate a new ephemeral certificate and a securely generated
random private key every time the server is started; and
- There should not be any truly secure information (e.g. seeds or private
keys) that gets relayed to the server anyways over these RPCs.
@note If you increase the number of bits you need to generate new
default DH parameters and update defaultDH accordingly.
* */
int defaultRSAKeyBits = 2048;
/** The default DH parameters.
These were generated using the OpenSSL command: `openssl dhparam 2048`
by Nik Bougalis <nikb@bougalis.net> on May, 29, 2022.
It is safe to use this, but if you want you can generate different
parameters and put them here. There's no easy way to change this
via the config file at this time.
@note If you increase the number of bits you need to update
defaultRSAKeyBits accordingly.
*/
static constexpr char const defaultDH[] =
"-----BEGIN DH PARAMETERS-----\n"
"MIIBCAKCAQEApKSWfR7LKy0VoZ/SDCObCvJ5HKX2J93RJ+QN8kJwHh+uuA8G+t8Q\n"
"MDRjL5HanlV/sKN9HXqBc7eqHmmbqYwIXKUt9MUZTLNheguddxVlc2IjdP5i9Ps8\n"
"l7su8tnP0l1JvC6Rfv3epRsEAw/ZW/lC2IwkQPpOmvnENQhQ6TgrUzcGkv4Bn0X6\n"
"pxrDSBpZ+45oehGCUAtcbY8b02vu8zPFoxqo6V/+MIszGzldlik5bVqrJpVF6E8C\n"
"tRqHjj6KuDbPbjc+pRGvwx/BSO3SULxmYu9J1NOk090MU1CMt6IJY7TpEc9Xrac9\n"
"9yqY3xXZID240RRcaJ25+U4lszFPqP+CEwIBAg==\n"
"-----END DH PARAMETERS-----";
/** The default list of ciphers we accept over TLS.
Generally we include cipher suites that are part of TLS v1.2, but
@@ -43,188 +80,148 @@ namespace detail {
global or per-port basis, using the `ssl_ciphers` directive in the
config file.
*/
std::string const defaultCipherList = "TLSv1.2:!DSS:!PSK:!eNULL:!aNULL";
template <class>
struct custom_delete;
template <>
struct custom_delete<RSA>
{
explicit custom_delete() = default;
void
operator()(RSA* rsa) const
{
RSA_free(rsa);
}
};
template <>
struct custom_delete<EVP_PKEY>
{
explicit custom_delete() = default;
void
operator()(EVP_PKEY* evp_pkey) const
{
EVP_PKEY_free(evp_pkey);
}
};
template <>
struct custom_delete<X509>
{
explicit custom_delete() = default;
void
operator()(X509* x509) const
{
X509_free(x509);
}
};
template <>
struct custom_delete<DH>
{
explicit custom_delete() = default;
void
operator()(DH* dh) const
{
DH_free(dh);
}
};
template <class T>
using custom_delete_unique_ptr = std::unique_ptr<T, custom_delete<T>>;
// RSA
using rsa_ptr = custom_delete_unique_ptr<RSA>;
static rsa_ptr
rsa_generate_key(int n_bits)
{
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
BIGNUM* bn = BN_new();
BN_set_word(bn, RSA_F4);
RSA* rsa = RSA_new();
if (RSA_generate_key_ex(rsa, n_bits, bn, nullptr) != 1)
{
RSA_free(rsa);
rsa = nullptr;
}
BN_free(bn);
#else
RSA* rsa = RSA_generate_key(n_bits, RSA_F4, nullptr, nullptr);
#endif
if (rsa == nullptr)
LogicError("RSA_generate_key failed");
return rsa_ptr(rsa);
}
// EVP_PKEY
using evp_pkey_ptr = custom_delete_unique_ptr<EVP_PKEY>;
static evp_pkey_ptr
evp_pkey_new()
{
EVP_PKEY* evp_pkey = EVP_PKEY_new();
if (evp_pkey == nullptr)
LogicError("EVP_PKEY_new failed");
return evp_pkey_ptr(evp_pkey);
}
static void
evp_pkey_assign_rsa(EVP_PKEY* evp_pkey, rsa_ptr rsa)
{
if (!EVP_PKEY_assign_RSA(evp_pkey, rsa.get()))
LogicError("EVP_PKEY_assign_RSA failed");
rsa.release();
}
// X509
using x509_ptr = custom_delete_unique_ptr<X509>;
static x509_ptr
x509_new()
{
X509* x509 = X509_new();
if (x509 == nullptr)
LogicError("X509_new failed");
X509_set_version(x509, NID_X509);
int const margin = 60 * 60; // 3600, one hour
int const length = 10 * 365.25 * 24 * 60 * 60; // 315576000, ten years
X509_gmtime_adj(X509_get_notBefore(x509), -margin);
X509_gmtime_adj(X509_get_notAfter(x509), length);
return x509_ptr(x509);
}
static void
x509_set_pubkey(X509* x509, EVP_PKEY* evp_pkey)
{
X509_set_pubkey(x509, evp_pkey);
}
static void
x509_sign(X509* x509, EVP_PKEY* evp_pkey)
{
if (!X509_sign(x509, evp_pkey, EVP_sha1()))
LogicError("X509_sign failed");
}
static void
ssl_ctx_use_certificate(SSL_CTX* const ctx, x509_ptr cert)
{
if (SSL_CTX_use_certificate(ctx, cert.get()) <= 0)
LogicError("SSL_CTX_use_certificate failed");
}
static void
ssl_ctx_use_privatekey(SSL_CTX* const ctx, evp_pkey_ptr key)
{
if (SSL_CTX_use_PrivateKey(ctx, key.get()) <= 0)
LogicError("SSL_CTX_use_PrivateKey failed");
}
static std::string
error_message(std::string const& what, boost::system::error_code const& ec)
{
std::stringstream ss;
ss << what << ": " << ec.message() << " (" << ec.value() << ")";
return ss.str();
}
std::string const defaultCipherList = "TLSv1.2:!CBC:!DSS:!PSK:!eNULL:!aNULL";
static void
initAnonymous(boost::asio::ssl::context& context)
{
using namespace openssl;
evp_pkey_ptr pkey = evp_pkey_new();
evp_pkey_assign_rsa(pkey.get(), rsa_generate_key(2048));
static auto defaultRSA = []() {
BIGNUM* bn = BN_new();
BN_set_word(bn, RSA_F4);
x509_ptr cert = x509_new();
x509_set_pubkey(cert.get(), pkey.get());
x509_sign(cert.get(), pkey.get());
auto rsa = RSA_new();
if (!rsa)
LogicError("RSA_new failed");
if (RSA_generate_key_ex(rsa, defaultRSAKeyBits, bn, nullptr) != 1)
LogicError("RSA_generate_key_ex failure");
BN_clear_free(bn);
return rsa;
}();
static auto defaultEphemeralPrivateKey = []() {
auto pkey = EVP_PKEY_new();
if (!pkey)
LogicError("EVP_PKEY_new failed");
// We need to up the reference count of here, since we are retaining a
// copy of the key for (potential) reuse.
if (RSA_up_ref(defaultRSA) != 1)
LogicError(
"EVP_PKEY_assign_RSA: incrementing reference count failed");
if (!EVP_PKEY_assign_RSA(pkey, defaultRSA))
LogicError("EVP_PKEY_assign_RSA failed");
return pkey;
}();
static auto defaultCert = []() {
auto x509 = X509_new();
if (x509 == nullptr)
LogicError("X509_new failed");
// According to the standards (X.509 et al), the value should be one
// less than the actualy certificate version we want. Since we want
// version 3, we must use a 2.
X509_set_version(x509, 2);
// To avoid leaking information about the precise time that the
// server started up, we adjust the validity period:
char buf[16] = {0};
auto const ts = std::time(nullptr) - (25 * 60 * 60);
int ret = std::strftime(
buf, sizeof(buf) - 1, "%y%m%d000000Z", std::gmtime(&ts));
buf[ret] = 0;
if (ASN1_TIME_set_string_X509(X509_get_notBefore(x509), buf) != 1)
LogicError("Unable to set certificate validity date");
// And make it valid for two years
X509_gmtime_adj(X509_get_notAfter(x509), 2 * 365 * 24 * 60 * 60);
// Set a serial number
if (auto b = BN_new(); b != nullptr)
{
if (BN_rand(b, 128, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY))
{
if (auto a = ASN1_INTEGER_new(); a != nullptr)
{
if (BN_to_ASN1_INTEGER(b, a))
X509_set_serialNumber(x509, a);
ASN1_INTEGER_free(a);
}
}
BN_clear_free(b);
}
// Some certificate details
{
X509V3_CTX ctx;
X509V3_set_ctx_nodb(&ctx);
X509V3_set_ctx(&ctx, x509, x509, nullptr, nullptr, 0);
if (auto ext = X509V3_EXT_conf_nid(
nullptr, &ctx, NID_basic_constraints, "critical,CA:FALSE"))
{
X509_add_ext(x509, ext, -1);
X509_EXTENSION_free(ext);
}
if (auto ext = X509V3_EXT_conf_nid(
nullptr,
&ctx,
NID_ext_key_usage,
"critical,serverAuth,clientAuth"))
{
X509_add_ext(x509, ext, -1);
X509_EXTENSION_free(ext);
}
if (auto ext = X509V3_EXT_conf_nid(
nullptr, &ctx, NID_key_usage, "critical,digitalSignature"))
{
X509_add_ext(x509, ext, -1);
X509_EXTENSION_free(ext);
}
if (auto ext = X509V3_EXT_conf_nid(
nullptr, &ctx, NID_subject_key_identifier, "hash"))
{
X509_add_ext(x509, ext, -1);
X509_EXTENSION_free(ext);
}
}
// And a private key
X509_set_pubkey(x509, defaultEphemeralPrivateKey);
if (!X509_sign(x509, defaultEphemeralPrivateKey, EVP_sha256()))
LogicError("X509_sign failed");
return x509;
}();
SSL_CTX* const ctx = context.native_handle();
ssl_ctx_use_certificate(ctx, std::move(cert));
ssl_ctx_use_privatekey(ctx, std::move(pkey));
if (SSL_CTX_use_certificate(ctx, defaultCert) <= 0)
LogicError("SSL_CTX_use_certificate failed");
if (SSL_CTX_use_PrivateKey(ctx, defaultEphemeralPrivateKey) <= 0)
LogicError("SSL_CTX_use_PrivateKey failed");
}
static void
@@ -234,6 +231,10 @@ initAuthenticated(
std::string const& cert_file,
std::string const& chain_file)
{
auto fmt_error = [](boost::system::error_code ec) -> std::string {
return " [" + std::to_string(ec.value()) + ": " + ec.message() + "]";
};
SSL_CTX* const ssl = context.native_handle();
bool cert_set = false;
@@ -246,10 +247,7 @@ initAuthenticated(
cert_file, boost::asio::ssl::context::pem, ec);
if (ec)
{
LogicError(error_message("Problem with SSL certificate file.", ec)
.c_str());
}
LogicError("Problem with SSL certificate file" + fmt_error(ec));
cert_set = true;
}
@@ -261,11 +259,10 @@ initAuthenticated(
if (!f)
{
LogicError(error_message(
"Problem opening SSL chain file.",
boost::system::error_code(
errno, boost::system::generic_category()))
.c_str());
LogicError(
"Problem opening SSL chain file" +
fmt_error(boost::system::error_code(
errno, boost::system::generic_category())));
}
try
@@ -312,8 +309,7 @@ initAuthenticated(
if (ec)
{
LogicError(
error_message("Problem using the SSL private key file.", ec)
.c_str());
"Problem using the SSL private key file" + fmt_error(ec));
}
}
@@ -324,7 +320,7 @@ initAuthenticated(
}
std::shared_ptr<boost::asio::ssl::context>
get_context(std::string const& cipherList)
get_context(std::string cipherList)
{
auto c = std::make_shared<boost::asio::ssl::context>(
boost::asio::ssl::context::sslv23);
@@ -338,55 +334,20 @@ get_context(std::string const& cipherList)
boost::asio::ssl::context::single_dh_use |
boost::asio::ssl::context::no_compression);
{
auto const& l = !cipherList.empty() ? cipherList : defaultCipherList;
auto result = SSL_CTX_set_cipher_list(c->native_handle(), l.c_str());
if (result != 1)
LogicError("SSL_CTX_set_cipher_list failed");
}
if (cipherList.empty())
cipherList = defaultCipherList;
// These are the raw DH parameters that Ripple Labs has
// chosen for Ripple, in the binary format needed by
// d2i_DHparams.
//
unsigned char const params[] = {
0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0x8f, 0xca, 0x66,
0x85, 0x33, 0xcb, 0xcf, 0x36, 0x27, 0xb2, 0x4c, 0xb8, 0x50, 0xb8, 0xf9,
0x53, 0xf8, 0xb9, 0x2d, 0x1c, 0xa2, 0xad, 0x86, 0x58, 0x29, 0x3b, 0x88,
0x3e, 0xf5, 0x65, 0xb8, 0xda, 0x22, 0xf4, 0x8b, 0x21, 0x12, 0x18, 0xf7,
0x16, 0xcd, 0x7c, 0xc7, 0x3a, 0x2d, 0x61, 0xb7, 0x11, 0xf6, 0xb0, 0x65,
0xa0, 0x5b, 0xa4, 0x06, 0x95, 0x28, 0xa4, 0x4f, 0x76, 0xc0, 0xeb, 0xfa,
0x95, 0xdf, 0xbf, 0x19, 0x90, 0x64, 0x8f, 0x60, 0xd5, 0x36, 0xba, 0xab,
0x0d, 0x5a, 0x5c, 0x94, 0xd5, 0xf7, 0x32, 0xd6, 0x2a, 0x76, 0x77, 0x83,
0x10, 0xc4, 0x2f, 0x10, 0x96, 0x3e, 0x37, 0x84, 0x45, 0x9c, 0xef, 0x33,
0xf6, 0xd0, 0x2a, 0xa7, 0xce, 0x0a, 0xce, 0x0d, 0xa1, 0xa7, 0x44, 0x5d,
0x18, 0x3f, 0x4f, 0xa4, 0x23, 0x9c, 0x5d, 0x74, 0x4f, 0xee, 0xdf, 0xaa,
0x0d, 0x0a, 0x52, 0x57, 0x73, 0xb1, 0xe4, 0xc5, 0x72, 0x93, 0x9d, 0x03,
0xe9, 0xf5, 0x48, 0x8c, 0xd1, 0xe6, 0x7c, 0x21, 0x65, 0x4e, 0x16, 0x51,
0xa3, 0x16, 0x51, 0x10, 0x75, 0x60, 0x37, 0x93, 0xb8, 0x15, 0xd6, 0x14,
0x41, 0x4a, 0x61, 0xc9, 0x1a, 0x4e, 0x9f, 0x38, 0xd8, 0x2c, 0xa5, 0x31,
0xe1, 0x87, 0xda, 0x1f, 0xa4, 0x31, 0xa2, 0xa4, 0x42, 0x1e, 0xe0, 0x30,
0xea, 0x2f, 0x9b, 0x77, 0x91, 0x59, 0x3e, 0xd5, 0xd0, 0xc5, 0x84, 0x45,
0x17, 0x19, 0x74, 0x8b, 0x18, 0xb0, 0xc1, 0xe0, 0xfc, 0x1c, 0xaf, 0xe6,
0x2a, 0xef, 0x4e, 0x0e, 0x8a, 0x5c, 0xc2, 0x91, 0xb9, 0x2b, 0xf8, 0x17,
0x8d, 0xed, 0x44, 0xaa, 0x47, 0xaa, 0x52, 0xa2, 0xdb, 0xb6, 0xf5, 0xa1,
0x88, 0x85, 0xa1, 0xd5, 0x87, 0xb8, 0x07, 0xd3, 0x97, 0xbe, 0x37, 0x74,
0x72, 0xf1, 0xa8, 0x29, 0xf1, 0xa7, 0x7d, 0x19, 0xc3, 0x27, 0x09, 0xcf,
0x23, 0x02, 0x01, 0x02};
if (auto result =
SSL_CTX_set_cipher_list(c->native_handle(), cipherList.c_str());
result != 1)
LogicError("SSL_CTX_set_cipher_list failed");
unsigned char const* data = &params[0];
custom_delete_unique_ptr<DH> const dh{
d2i_DHparams(nullptr, &data, sizeof(params))};
if (!dh)
LogicError("d2i_DHparams returned nullptr.");
SSL_CTX_set_tmp_dh(c->native_handle(), dh.get());
c->use_tmp_dh({std::addressof(detail::defaultDH), sizeof(defaultDH)});
// Disable all renegotiation support in TLS v1.2. This can help prevent
// exploitation of the bug described in CVE-2021-3499 (for details see
// https://www.openssl.org/news/secadv/20210325.txt) when linking against
// OpenSSL versions prior to 1.1.1k.
// https://www.openssl.org/news/secadv/20210325.txt) when linking
// against OpenSSL versions prior to 1.1.1k.
SSL_CTX_set_options(c->native_handle(), SSL_OP_NO_RENEGOTIATION);
return c;