rippled
Loading...
Searching...
No Matches
make_SSLContext.cpp
1#include <xrpl/basics/contract.h>
2#include <xrpl/basics/make_SSLContext.h>
3
4#include <boost/asio/ssl/context.hpp>
5#include <boost/asio/ssl/verify_mode.hpp>
6#include <boost/system/detail/error_code.hpp>
7#include <boost/system/detail/generic_category.hpp>
8
9#include <openssl/asn1.h>
10#include <openssl/bn.h>
11#include <openssl/evp.h>
12#include <openssl/objects.h>
13#include <openssl/ossl_typ.h>
14#include <openssl/pem.h>
15#include <openssl/rsa.h>
16#include <openssl/ssl.h>
17#include <openssl/x509.h>
18#include <openssl/x509v3.h>
19
20#include <cerrno>
21#include <cstdio>
22#include <ctime>
23#include <exception>
24#include <memory>
25#include <string>
26
27namespace ripple {
28namespace openssl {
29namespace detail {
30
48
61static constexpr char const defaultDH[] =
62 "-----BEGIN DH PARAMETERS-----\n"
63 "MIIBCAKCAQEApKSWfR7LKy0VoZ/SDCObCvJ5HKX2J93RJ+QN8kJwHh+uuA8G+t8Q\n"
64 "MDRjL5HanlV/sKN9HXqBc7eqHmmbqYwIXKUt9MUZTLNheguddxVlc2IjdP5i9Ps8\n"
65 "l7su8tnP0l1JvC6Rfv3epRsEAw/ZW/lC2IwkQPpOmvnENQhQ6TgrUzcGkv4Bn0X6\n"
66 "pxrDSBpZ+45oehGCUAtcbY8b02vu8zPFoxqo6V/+MIszGzldlik5bVqrJpVF6E8C\n"
67 "tRqHjj6KuDbPbjc+pRGvwx/BSO3SULxmYu9J1NOk090MU1CMt6IJY7TpEc9Xrac9\n"
68 "9yqY3xXZID240RRcaJ25+U4lszFPqP+CEwIBAg==\n"
69 "-----END DH PARAMETERS-----";
70
85std::string const defaultCipherList = "TLSv1.2:!CBC:!DSS:!PSK:!eNULL:!aNULL";
86
87static void
88initAnonymous(boost::asio::ssl::context& context)
89{
90 using namespace openssl;
91
92 static auto defaultRSA = []() {
93 BIGNUM* bn = BN_new();
94 BN_set_word(bn, RSA_F4);
95
96 auto rsa = RSA_new();
97
98 if (!rsa)
99 LogicError("RSA_new failed");
100
101 if (RSA_generate_key_ex(rsa, defaultRSAKeyBits, bn, nullptr) != 1)
102 LogicError("RSA_generate_key_ex failure");
103
104 BN_clear_free(bn);
105
106 return rsa;
107 }();
108
109 static auto defaultEphemeralPrivateKey = []() {
110 auto pkey = EVP_PKEY_new();
111
112 if (!pkey)
113 LogicError("EVP_PKEY_new failed");
114
115 // We need to up the reference count of here, since we are retaining a
116 // copy of the key for (potential) reuse.
117 if (RSA_up_ref(defaultRSA) != 1)
119 "EVP_PKEY_assign_RSA: incrementing reference count failed");
120
121 if (!EVP_PKEY_assign_RSA(pkey, defaultRSA))
122 LogicError("EVP_PKEY_assign_RSA failed");
123
124 return pkey;
125 }();
126
127 static auto defaultCert = []() {
128 auto x509 = X509_new();
129
130 if (x509 == nullptr)
131 LogicError("X509_new failed");
132
133 // According to the standards (X.509 et al), the value should be one
134 // less than the actualy certificate version we want. Since we want
135 // version 3, we must use a 2.
136 X509_set_version(x509, 2);
137
138 // To avoid leaking information about the precise time that the
139 // server started up, we adjust the validity period:
140 char buf[16] = {0};
141
142 auto const ts = std::time(nullptr) - (25 * 60 * 60);
143
144 int ret = std::strftime(
145 buf, sizeof(buf) - 1, "%y%m%d000000Z", std::gmtime(&ts));
146
147 buf[ret] = 0;
148
149 if (ASN1_TIME_set_string_X509(X509_get_notBefore(x509), buf) != 1)
150 LogicError("Unable to set certificate validity date");
151
152 // And make it valid for two years
153 X509_gmtime_adj(X509_get_notAfter(x509), 2 * 365 * 24 * 60 * 60);
154
155 // Set a serial number
156 if (auto b = BN_new(); b != nullptr)
157 {
158 if (BN_rand(b, 128, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY))
159 {
160 if (auto a = ASN1_INTEGER_new(); a != nullptr)
161 {
162 if (BN_to_ASN1_INTEGER(b, a))
163 X509_set_serialNumber(x509, a);
164
165 ASN1_INTEGER_free(a);
166 }
167 }
168
169 BN_clear_free(b);
170 }
171
172 // Some certificate details
173 {
174 X509V3_CTX ctx;
175
176 X509V3_set_ctx_nodb(&ctx);
177 X509V3_set_ctx(&ctx, x509, x509, nullptr, nullptr, 0);
178
179 if (auto ext = X509V3_EXT_conf_nid(
180 nullptr, &ctx, NID_basic_constraints, "critical,CA:FALSE"))
181 {
182 X509_add_ext(x509, ext, -1);
183 X509_EXTENSION_free(ext);
184 }
185
186 if (auto ext = X509V3_EXT_conf_nid(
187 nullptr,
188 &ctx,
189 NID_ext_key_usage,
190 "critical,serverAuth,clientAuth"))
191 {
192 X509_add_ext(x509, ext, -1);
193 X509_EXTENSION_free(ext);
194 }
195
196 if (auto ext = X509V3_EXT_conf_nid(
197 nullptr, &ctx, NID_key_usage, "critical,digitalSignature"))
198 {
199 X509_add_ext(x509, ext, -1);
200 X509_EXTENSION_free(ext);
201 }
202
203 if (auto ext = X509V3_EXT_conf_nid(
204 nullptr, &ctx, NID_subject_key_identifier, "hash"))
205 {
206 X509_add_ext(x509, ext, -1);
207 X509_EXTENSION_free(ext);
208 }
209 }
210
211 // And a private key
212 X509_set_pubkey(x509, defaultEphemeralPrivateKey);
213
214 if (!X509_sign(x509, defaultEphemeralPrivateKey, EVP_sha256()))
215 LogicError("X509_sign failed");
216
217 return x509;
218 }();
219
220 SSL_CTX* const ctx = context.native_handle();
221
222 if (SSL_CTX_use_certificate(ctx, defaultCert) <= 0)
223 LogicError("SSL_CTX_use_certificate failed");
224
225 if (SSL_CTX_use_PrivateKey(ctx, defaultEphemeralPrivateKey) <= 0)
226 LogicError("SSL_CTX_use_PrivateKey failed");
227}
228
229static void
231 boost::asio::ssl::context& context,
232 std::string const& key_file,
233 std::string const& cert_file,
234 std::string const& chain_file)
235{
236 auto fmt_error = [](boost::system::error_code ec) -> std::string {
237 return " [" + std::to_string(ec.value()) + ": " + ec.message() + "]";
238 };
239
240 SSL_CTX* const ssl = context.native_handle();
241
242 bool cert_set = false;
243
244 if (!cert_file.empty())
245 {
246 boost::system::error_code ec;
247
248 context.use_certificate_file(
249 cert_file, boost::asio::ssl::context::pem, ec);
250
251 if (ec)
252 LogicError("Problem with SSL certificate file" + fmt_error(ec));
253
254 cert_set = true;
255 }
256
257 if (!chain_file.empty())
258 {
259 // VFALCO Replace fopen() with RAII
260 FILE* f = fopen(chain_file.c_str(), "r");
261
262 if (!f)
263 {
265 "Problem opening SSL chain file" +
266 fmt_error(boost::system::error_code(
267 errno, boost::system::generic_category())));
268 }
269
270 try
271 {
272 for (;;)
273 {
274 X509* const x = PEM_read_X509(f, nullptr, nullptr, nullptr);
275
276 if (x == nullptr)
277 break;
278
279 if (!cert_set)
280 {
281 if (SSL_CTX_use_certificate(ssl, x) != 1)
283 "Problem retrieving SSL certificate from chain "
284 "file.");
285
286 cert_set = true;
287 }
288 else if (SSL_CTX_add_extra_chain_cert(ssl, x) != 1)
289 {
290 X509_free(x);
291 LogicError("Problem adding SSL chain certificate.");
292 }
293 }
294
295 fclose(f);
296 }
297 catch (std::exception const& ex)
298 {
299 fclose(f);
302 "Reading the SSL chain file generated an exception: ") +
303 ex.what());
304 }
305 }
306
307 if (!key_file.empty())
308 {
309 boost::system::error_code ec;
310
311 context.use_private_key_file(
312 key_file, boost::asio::ssl::context::pem, ec);
313
314 if (ec)
315 {
317 "Problem using the SSL private key file" + fmt_error(ec));
318 }
319 }
320
321 if (SSL_CTX_check_private_key(ssl) != 1)
322 {
323 LogicError("Invalid key in SSL private key file.");
324 }
325}
326
329{
331 boost::asio::ssl::context::sslv23);
332
333 c->set_options(
334 boost::asio::ssl::context::default_workarounds |
335 boost::asio::ssl::context::no_sslv2 |
336 boost::asio::ssl::context::no_sslv3 |
337 boost::asio::ssl::context::no_tlsv1 |
338 boost::asio::ssl::context::no_tlsv1_1 |
339 boost::asio::ssl::context::single_dh_use |
340 boost::asio::ssl::context::no_compression);
341
342 if (cipherList.empty())
343 cipherList = defaultCipherList;
344
345 if (auto result =
346 SSL_CTX_set_cipher_list(c->native_handle(), cipherList.c_str());
347 result != 1)
348 LogicError("SSL_CTX_set_cipher_list failed");
349
350 c->use_tmp_dh({std::addressof(detail::defaultDH), sizeof(defaultDH)});
351
352 // Disable all renegotiation support in TLS v1.2. This can help prevent
353 // exploitation of the bug described in CVE-2021-3499 (for details see
354 // https://www.openssl.org/news/secadv/20210325.txt) when linking
355 // against OpenSSL versions prior to 1.1.1k.
356 SSL_CTX_set_options(c->native_handle(), SSL_OP_NO_RENEGOTIATION);
357
358 return c;
359}
360
361} // namespace detail
362} // namespace openssl
363
364//------------------------------------------------------------------------------
366make_SSLContext(std::string const& cipherList)
367{
368 auto context = openssl::detail::get_context(cipherList);
370 // VFALCO NOTE, It seems the WebSocket context never has
371 // set_verify_mode called, for either setting of WEBSOCKET_SECURE
372 context->set_verify_mode(boost::asio::ssl::verify_none);
373 return context;
374}
375
378 std::string const& keyFile,
379 std::string const& certFile,
380 std::string const& chainFile,
381 std::string const& cipherList)
382{
383 auto context = openssl::detail::get_context(cipherList);
384 openssl::detail::initAuthenticated(*context, keyFile, certFile, chainFile);
385 return context;
386}
387
388} // namespace ripple
T addressof(T... args)
T c_str(T... args)
T empty(T... args)
T gmtime(T... args)
T is_same_v
std::shared_ptr< boost::asio::ssl::context > get_context(std::string cipherList)
static void initAnonymous(boost::asio::ssl::context &context)
static void initAuthenticated(boost::asio::ssl::context &context, std::string const &key_file, std::string const &cert_file, std::string const &chain_file)
std::string const defaultCipherList
The default list of ciphers we accept over TLS.
int defaultRSAKeyBits
The default strength of self-signed RSA certifices.
static constexpr char const defaultDH[]
The default DH parameters.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::shared_ptr< boost::asio::ssl::context > make_SSLContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
std::shared_ptr< boost::asio::ssl::context > make_SSLContextAuthed(std::string const &keyFile, std::string const &certFile, std::string const &chainFile, std::string const &cipherList)
Create an authenticated SSL context using the specified files.
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
T strftime(T... args)
T time(T... args)
T to_string(T... args)
T what(T... args)