diff --git a/src/ripple/basics/impl/make_SSLContext.cpp b/src/ripple/basics/impl/make_SSLContext.cpp index 67beb79d3..79a0e9009 100644 --- a/src/ripple/basics/impl/make_SSLContext.cpp +++ b/src/ripple/basics/impl/make_SSLContext.cpp @@ -17,18 +17,55 @@ */ //============================================================================== -#include #include #include -#include -#include -#include +#include #include 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 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 -struct custom_delete; - -template <> -struct custom_delete -{ - explicit custom_delete() = default; - - void - operator()(RSA* rsa) const - { - RSA_free(rsa); - } -}; - -template <> -struct custom_delete -{ - explicit custom_delete() = default; - - void - operator()(EVP_PKEY* evp_pkey) const - { - EVP_PKEY_free(evp_pkey); - } -}; - -template <> -struct custom_delete -{ - explicit custom_delete() = default; - - void - operator()(X509* x509) const - { - X509_free(x509); - } -}; - -template <> -struct custom_delete -{ - explicit custom_delete() = default; - - void - operator()(DH* dh) const - { - DH_free(dh); - } -}; - -template -using custom_delete_unique_ptr = std::unique_ptr>; - -// RSA - -using rsa_ptr = custom_delete_unique_ptr; - -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; - -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; - -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 -get_context(std::string const& cipherList) +get_context(std::string cipherList) { auto c = std::make_shared( 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 = ¶ms[0]; - - custom_delete_unique_ptr 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;