rippled
Loading...
Searching...
No Matches
make_SSLContext.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpl/basics/contract.h>
21#include <xrpl/basics/make_SSLContext.h>
22#include <ctime>
23#include <stdexcept>
24
25namespace ripple {
26namespace openssl {
27namespace detail {
28
46
59static constexpr char const defaultDH[] =
60 "-----BEGIN DH PARAMETERS-----\n"
61 "MIIBCAKCAQEApKSWfR7LKy0VoZ/SDCObCvJ5HKX2J93RJ+QN8kJwHh+uuA8G+t8Q\n"
62 "MDRjL5HanlV/sKN9HXqBc7eqHmmbqYwIXKUt9MUZTLNheguddxVlc2IjdP5i9Ps8\n"
63 "l7su8tnP0l1JvC6Rfv3epRsEAw/ZW/lC2IwkQPpOmvnENQhQ6TgrUzcGkv4Bn0X6\n"
64 "pxrDSBpZ+45oehGCUAtcbY8b02vu8zPFoxqo6V/+MIszGzldlik5bVqrJpVF6E8C\n"
65 "tRqHjj6KuDbPbjc+pRGvwx/BSO3SULxmYu9J1NOk090MU1CMt6IJY7TpEc9Xrac9\n"
66 "9yqY3xXZID240RRcaJ25+U4lszFPqP+CEwIBAg==\n"
67 "-----END DH PARAMETERS-----";
68
83std::string const defaultCipherList = "TLSv1.2:!CBC:!DSS:!PSK:!eNULL:!aNULL";
84
85static void
86initAnonymous(boost::asio::ssl::context& context)
87{
88 using namespace openssl;
89
90 static auto defaultRSA = []() {
91 BIGNUM* bn = BN_new();
92 BN_set_word(bn, RSA_F4);
93
94 auto rsa = RSA_new();
95
96 if (!rsa)
97 LogicError("RSA_new failed");
98
99 if (RSA_generate_key_ex(rsa, defaultRSAKeyBits, bn, nullptr) != 1)
100 LogicError("RSA_generate_key_ex failure");
101
102 BN_clear_free(bn);
103
104 return rsa;
105 }();
106
107 static auto defaultEphemeralPrivateKey = []() {
108 auto pkey = EVP_PKEY_new();
109
110 if (!pkey)
111 LogicError("EVP_PKEY_new failed");
112
113 // We need to up the reference count of here, since we are retaining a
114 // copy of the key for (potential) reuse.
115 if (RSA_up_ref(defaultRSA) != 1)
117 "EVP_PKEY_assign_RSA: incrementing reference count failed");
118
119 if (!EVP_PKEY_assign_RSA(pkey, defaultRSA))
120 LogicError("EVP_PKEY_assign_RSA failed");
121
122 return pkey;
123 }();
124
125 static auto defaultCert = []() {
126 auto x509 = X509_new();
127
128 if (x509 == nullptr)
129 LogicError("X509_new failed");
130
131 // According to the standards (X.509 et al), the value should be one
132 // less than the actualy certificate version we want. Since we want
133 // version 3, we must use a 2.
134 X509_set_version(x509, 2);
135
136 // To avoid leaking information about the precise time that the
137 // server started up, we adjust the validity period:
138 char buf[16] = {0};
139
140 auto const ts = std::time(nullptr) - (25 * 60 * 60);
141
142 int ret = std::strftime(
143 buf, sizeof(buf) - 1, "%y%m%d000000Z", std::gmtime(&ts));
144
145 buf[ret] = 0;
146
147 if (ASN1_TIME_set_string_X509(X509_get_notBefore(x509), buf) != 1)
148 LogicError("Unable to set certificate validity date");
149
150 // And make it valid for two years
151 X509_gmtime_adj(X509_get_notAfter(x509), 2 * 365 * 24 * 60 * 60);
152
153 // Set a serial number
154 if (auto b = BN_new(); b != nullptr)
155 {
156 if (BN_rand(b, 128, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY))
157 {
158 if (auto a = ASN1_INTEGER_new(); a != nullptr)
159 {
160 if (BN_to_ASN1_INTEGER(b, a))
161 X509_set_serialNumber(x509, a);
162
163 ASN1_INTEGER_free(a);
164 }
165 }
166
167 BN_clear_free(b);
168 }
169
170 // Some certificate details
171 {
172 X509V3_CTX ctx;
173
174 X509V3_set_ctx_nodb(&ctx);
175 X509V3_set_ctx(&ctx, x509, x509, nullptr, nullptr, 0);
176
177 if (auto ext = X509V3_EXT_conf_nid(
178 nullptr, &ctx, NID_basic_constraints, "critical,CA:FALSE"))
179 {
180 X509_add_ext(x509, ext, -1);
181 X509_EXTENSION_free(ext);
182 }
183
184 if (auto ext = X509V3_EXT_conf_nid(
185 nullptr,
186 &ctx,
187 NID_ext_key_usage,
188 "critical,serverAuth,clientAuth"))
189 {
190 X509_add_ext(x509, ext, -1);
191 X509_EXTENSION_free(ext);
192 }
193
194 if (auto ext = X509V3_EXT_conf_nid(
195 nullptr, &ctx, NID_key_usage, "critical,digitalSignature"))
196 {
197 X509_add_ext(x509, ext, -1);
198 X509_EXTENSION_free(ext);
199 }
200
201 if (auto ext = X509V3_EXT_conf_nid(
202 nullptr, &ctx, NID_subject_key_identifier, "hash"))
203 {
204 X509_add_ext(x509, ext, -1);
205 X509_EXTENSION_free(ext);
206 }
207 }
208
209 // And a private key
210 X509_set_pubkey(x509, defaultEphemeralPrivateKey);
211
212 if (!X509_sign(x509, defaultEphemeralPrivateKey, EVP_sha256()))
213 LogicError("X509_sign failed");
214
215 return x509;
216 }();
217
218 SSL_CTX* const ctx = context.native_handle();
219
220 if (SSL_CTX_use_certificate(ctx, defaultCert) <= 0)
221 LogicError("SSL_CTX_use_certificate failed");
222
223 if (SSL_CTX_use_PrivateKey(ctx, defaultEphemeralPrivateKey) <= 0)
224 LogicError("SSL_CTX_use_PrivateKey failed");
225}
226
227static void
229 boost::asio::ssl::context& context,
230 std::string const& key_file,
231 std::string const& cert_file,
232 std::string const& chain_file)
233{
234 auto fmt_error = [](boost::system::error_code ec) -> std::string {
235 return " [" + std::to_string(ec.value()) + ": " + ec.message() + "]";
236 };
237
238 SSL_CTX* const ssl = context.native_handle();
239
240 bool cert_set = false;
241
242 if (!cert_file.empty())
243 {
244 boost::system::error_code ec;
245
246 context.use_certificate_file(
247 cert_file, boost::asio::ssl::context::pem, ec);
248
249 if (ec)
250 LogicError("Problem with SSL certificate file" + fmt_error(ec));
251
252 cert_set = true;
253 }
254
255 if (!chain_file.empty())
256 {
257 // VFALCO Replace fopen() with RAII
258 FILE* f = fopen(chain_file.c_str(), "r");
259
260 if (!f)
261 {
263 "Problem opening SSL chain file" +
264 fmt_error(boost::system::error_code(
265 errno, boost::system::generic_category())));
266 }
267
268 try
269 {
270 for (;;)
271 {
272 X509* const x = PEM_read_X509(f, nullptr, nullptr, nullptr);
273
274 if (x == nullptr)
275 break;
276
277 if (!cert_set)
278 {
279 if (SSL_CTX_use_certificate(ssl, x) != 1)
281 "Problem retrieving SSL certificate from chain "
282 "file.");
283
284 cert_set = true;
285 }
286 else if (SSL_CTX_add_extra_chain_cert(ssl, x) != 1)
287 {
288 X509_free(x);
289 LogicError("Problem adding SSL chain certificate.");
290 }
291 }
292
293 fclose(f);
294 }
295 catch (std::exception const& ex)
296 {
297 fclose(f);
300 "Reading the SSL chain file generated an exception: ") +
301 ex.what());
302 }
303 }
304
305 if (!key_file.empty())
306 {
307 boost::system::error_code ec;
308
309 context.use_private_key_file(
310 key_file, boost::asio::ssl::context::pem, ec);
311
312 if (ec)
313 {
315 "Problem using the SSL private key file" + fmt_error(ec));
316 }
317 }
318
319 if (SSL_CTX_check_private_key(ssl) != 1)
320 {
321 LogicError("Invalid key in SSL private key file.");
322 }
323}
324
327{
328 auto c = std::make_shared<boost::asio::ssl::context>(
329 boost::asio::ssl::context::sslv23);
330
331 c->set_options(
332 boost::asio::ssl::context::default_workarounds |
333 boost::asio::ssl::context::no_sslv2 |
334 boost::asio::ssl::context::no_sslv3 |
335 boost::asio::ssl::context::no_tlsv1 |
336 boost::asio::ssl::context::no_tlsv1_1 |
337 boost::asio::ssl::context::single_dh_use |
338 boost::asio::ssl::context::no_compression);
339
340 if (cipherList.empty())
341 cipherList = defaultCipherList;
342
343 if (auto result =
344 SSL_CTX_set_cipher_list(c->native_handle(), cipherList.c_str());
345 result != 1)
346 LogicError("SSL_CTX_set_cipher_list failed");
347
348 c->use_tmp_dh({std::addressof(detail::defaultDH), sizeof(defaultDH)});
349
350 // Disable all renegotiation support in TLS v1.2. This can help prevent
351 // exploitation of the bug described in CVE-2021-3499 (for details see
352 // https://www.openssl.org/news/secadv/20210325.txt) when linking
353 // against OpenSSL versions prior to 1.1.1k.
354 SSL_CTX_set_options(c->native_handle(), SSL_OP_NO_RENEGOTIATION);
355
356 return c;
357}
358
359} // namespace detail
360} // namespace openssl
361
362//------------------------------------------------------------------------------
364make_SSLContext(std::string const& cipherList)
365{
366 auto context = openssl::detail::get_context(cipherList);
368 // VFALCO NOTE, It seems the WebSocket context never has
369 // set_verify_mode called, for either setting of WEBSOCKET_SECURE
370 context->set_verify_mode(boost::asio::ssl::verify_none);
371 return context;
372}
373
376 std::string const& keyFile,
377 std::string const& certFile,
378 std::string const& chainFile,
379 std::string const& cipherList)
380{
381 auto context = openssl::detail::get_context(cipherList);
382 openssl::detail::initAuthenticated(*context, keyFile, certFile, chainFile);
383 return context;
384}
385
386} // namespace ripple
T addressof(T... args)
T c_str(T... args)
T empty(T... args)
T gmtime(T... args)
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:26
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.
Definition: contract.cpp:48
T strftime(T... args)
T time(T... args)
T to_string(T... args)
T what(T... args)