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