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