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&)
296  {
297  fclose(f);
298  LogicError("Reading the SSL chain file generated an exception.");
299  }
300  }
301 
302  if (!key_file.empty())
303  {
304  boost::system::error_code ec;
305 
306  context.use_private_key_file(
307  key_file, boost::asio::ssl::context::pem, ec);
308 
309  if (ec)
310  {
311  LogicError(
312  "Problem using the SSL private key file" + fmt_error(ec));
313  }
314  }
315 
316  if (SSL_CTX_check_private_key(ssl) != 1)
317  {
318  LogicError("Invalid key in SSL private key file.");
319  }
320 }
321 
324 {
325  auto c = std::make_shared<boost::asio::ssl::context>(
326  boost::asio::ssl::context::sslv23);
327 
328  c->set_options(
329  boost::asio::ssl::context::default_workarounds |
330  boost::asio::ssl::context::no_sslv2 |
331  boost::asio::ssl::context::no_sslv3 |
332  boost::asio::ssl::context::no_tlsv1 |
333  boost::asio::ssl::context::no_tlsv1_1 |
334  boost::asio::ssl::context::single_dh_use |
335  boost::asio::ssl::context::no_compression);
336 
337  if (cipherList.empty())
338  cipherList = defaultCipherList;
339 
340  if (auto result =
341  SSL_CTX_set_cipher_list(c->native_handle(), cipherList.c_str());
342  result != 1)
343  LogicError("SSL_CTX_set_cipher_list failed");
344 
345  c->use_tmp_dh({std::addressof(detail::defaultDH), sizeof(defaultDH)});
346 
347  // Disable all renegotiation support in TLS v1.2. This can help prevent
348  // exploitation of the bug described in CVE-2021-3499 (for details see
349  // https://www.openssl.org/news/secadv/20210325.txt) when linking
350  // against OpenSSL versions prior to 1.1.1k.
351  SSL_CTX_set_options(c->native_handle(), SSL_OP_NO_RENEGOTIATION);
352 
353  return c;
354 }
355 
356 } // namespace detail
357 } // namespace openssl
358 
359 //------------------------------------------------------------------------------
361 make_SSLContext(std::string const& cipherList)
362 {
363  auto context = openssl::detail::get_context(cipherList);
365  // VFALCO NOTE, It seems the WebSocket context never has
366  // set_verify_mode called, for either setting of WEBSOCKET_SECURE
367  context->set_verify_mode(boost::asio::ssl::verify_none);
368  return context;
369 }
370 
373  std::string const& keyFile,
374  std::string const& certFile,
375  std::string const& chainFile,
376  std::string const& cipherList)
377 {
378  auto context = openssl::detail::get_context(cipherList);
379  openssl::detail::initAuthenticated(*context, keyFile, certFile, chainFile);
380  return context;
381 }
382 
383 } // 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:361
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:323
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:372
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