rippled
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 <ripple/basics/contract.h>
21 #include <ripple/basics/make_SSLContext.h>
22 #include <ctime>
23 #include <stdexcept>
24 
25 namespace ripple {
26 namespace openssl {
27 namespace detail {
28 
45 int defaultRSAKeyBits = 2048;
46 
59 static 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 
83 std::string const defaultCipherList = "TLSv1.2:!CBC:!DSS:!PSK:!eNULL:!aNULL";
84 
85 static void
86 initAnonymous(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)
116  LogicError(
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 
227 static 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  {
262  LogicError(
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)
280  LogicError(
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);
298  LogicError(
299  std::string(
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  {
314  LogicError(
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 //------------------------------------------------------------------------------
364 make_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
ctime
ripple::openssl::detail::defaultDH
static constexpr const char defaultDH[]
The default DH parameters.
Definition: make_SSLContext.cpp:59
std::string
STL class.
std::shared_ptr< boost::asio::ssl::context >
std::exception
STL class.
ripple::openssl::detail::defaultRSAKeyBits
int defaultRSAKeyBits
The default strength of self-signed RSA certifices.
Definition: make_SSLContext.cpp:45
ripple::make_SSLContext
std::shared_ptr< boost::asio::ssl::context > make_SSLContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
Definition: make_SSLContext.cpp:364
ripple::openssl::detail::initAnonymous
static void initAnonymous(boost::asio::ssl::context &context)
Definition: make_SSLContext.cpp:86
ripple::openssl::detail::get_context
std::shared_ptr< boost::asio::ssl::context > get_context(std::string cipherList)
Definition: make_SSLContext.cpp:326
std::gmtime
T gmtime(T... args)
ripple::make_SSLContextAuthed
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.
Definition: make_SSLContext.cpp:375
ripple::openssl::detail::defaultCipherList
const std::string defaultCipherList
The default list of ciphers we accept over TLS.
Definition: make_SSLContext.cpp:83
stdexcept
std::addressof
T addressof(T... args)
std::string::c_str
T c_str(T... args)
std::to_string
T to_string(T... args)
std::strftime
T strftime(T... args)
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::LogicError
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:48
std::string::empty
T empty(T... args)
std::time
T time(T... args)
ripple::openssl::detail::initAuthenticated
static void initAuthenticated(boost::asio::ssl::context &context, std::string const &key_file, std::string const &cert_file, std::string const &chain_file)
Definition: make_SSLContext.cpp:228
std::exception::what
T what(T... args)