xrpld
Loading...
Searching...
No Matches
make_SSLContext.cpp
1#include <xrpl/basics/make_SSLContext.h>
2
3#include <xrpl/basics/contract.h>
4
5#include <boost/asio/ssl/context.hpp>
6#include <boost/asio/ssl/verify_mode.hpp>
7#include <boost/system/detail/error_code.hpp>
8#include <boost/system/detail/generic_category.hpp>
9
10#include <openssl/asn1.h>
11#include <openssl/bn.h>
12#include <openssl/crypto.h>
13#include <openssl/evp.h>
14#include <openssl/objects.h> // IWYU pragma: keep
15#include <openssl/ossl_typ.h>
16#include <openssl/pem.h>
17#include <openssl/rsa.h>
18#include <openssl/ssl.h>
19#include <openssl/x509.h>
20#include <openssl/x509v3.h>
21
22#include <cerrno>
23#include <cstdio>
24#include <ctime>
25#include <exception>
26#include <memory>
27#include <string>
28
29namespace xrpl {
30
31namespace openssl::detail {
32
51
65static constexpr char kDefaultDh[] =
66 "-----BEGIN DH PARAMETERS-----\n"
67 "MIIBCAKCAQEApKSWfR7LKy0VoZ/SDCObCvJ5HKX2J93RJ+QN8kJwHh+uuA8G+t8Q\n"
68 "MDRjL5HanlV/sKN9HXqBc7eqHmmbqYwIXKUt9MUZTLNheguddxVlc2IjdP5i9Ps8\n"
69 "l7su8tnP0l1JvC6Rfv3epRsEAw/ZW/lC2IwkQPpOmvnENQhQ6TgrUzcGkv4Bn0X6\n"
70 "pxrDSBpZ+45oehGCUAtcbY8b02vu8zPFoxqo6V/+MIszGzldlik5bVqrJpVF6E8C\n"
71 "tRqHjj6KuDbPbjc+pRGvwx/BSO3SULxmYu9J1NOk090MU1CMt6IJY7TpEc9Xrac9\n"
72 "9yqY3xXZID240RRcaJ25+U4lszFPqP+CEwIBAg==\n"
73 "-----END DH PARAMETERS-----";
74
90std::string const kDefaultCipherList = "TLSv1.2:!CBC:!DSS:!PSK:!eNULL:!aNULL";
91
92static void
93initAnonymous(boost::asio::ssl::context& context)
94{
95 using namespace openssl;
96
97 static auto kDefaultRsa = []() {
98 BIGNUM* bn = BN_new();
99 BN_set_word(bn, RSA_F4);
100
101 auto rsa = RSA_new();
102
103 if (!rsa)
104 logicError("RSA_new failed");
105
106 if (RSA_generate_key_ex(rsa, gDefaultRsaKeyBits, bn, nullptr) != 1)
107 logicError("RSA_generate_key_ex failure");
108
109 BN_clear_free(bn);
110
111 return rsa;
112 }();
113
114 static auto kDefaultEphemeralPrivateKey = []() {
115 auto pkey = EVP_PKEY_new();
116
117 if (!pkey)
118 logicError("EVP_PKEY_new failed");
119
120 // We need to up the reference count of here, since we are retaining a
121 // copy of the key for (potential) reuse.
122 if (RSA_up_ref(kDefaultRsa) != 1)
123 logicError("EVP_PKEY_assign_RSA: incrementing reference count failed");
124
125 if (!EVP_PKEY_assign_RSA(pkey, kDefaultRsa))
126 logicError("EVP_PKEY_assign_RSA failed");
127
128 return pkey;
129 }();
130
131 static auto kDefaultCert = []() {
132 auto x509 = X509_new();
133
134 if (x509 == nullptr)
135 logicError("X509_new failed");
136
137 // According to the standards (X.509 et al), the value should be one
138 // less than the actually certificate version we want. Since we want
139 // version 3, we must use a 2.
140 X509_set_version(x509, 2);
141
142 // To avoid leaking information about the precise time that the
143 // server started up, we adjust the validity period:
144 char buf[16] = {0};
145
146 auto const ts = std::time(nullptr) - (25 * 60 * 60);
147
148 int const ret = std::strftime(buf, sizeof(buf) - 1, "%y%m%d000000Z", std::gmtime(&ts));
149
150 buf[ret] = 0;
151
152 if (ASN1_TIME_set_string_X509(X509_get_notBefore(x509), buf) != 1)
153 logicError("Unable to set certificate validity date");
154
155 // And make it valid for two years
156 X509_gmtime_adj(X509_get_notAfter(x509), 2 * 365 * 24 * 60 * 60);
157
158 // Set a serial number
159 if (auto b = BN_new(); b != nullptr)
160 {
161 if (BN_rand(b, 128, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY))
162 {
163 if (auto a = ASN1_INTEGER_new(); a != nullptr)
164 {
165 if (BN_to_ASN1_INTEGER(b, a))
166 X509_set_serialNumber(x509, a);
167
168 ASN1_INTEGER_free(a);
169 }
170 }
171
172 BN_clear_free(b);
173 }
174
175 // Some certificate details
176 {
177 X509V3_CTX ctx;
178
179 X509V3_set_ctx_nodb(&ctx);
180 X509V3_set_ctx(&ctx, x509, x509, nullptr, nullptr, 0);
181
182 if (auto ext =
183 X509V3_EXT_conf_nid(nullptr, &ctx, NID_basic_constraints, "critical,CA:FALSE"))
184 {
185 X509_add_ext(x509, ext, -1);
186 X509_EXTENSION_free(ext);
187 }
188
189 if (auto ext = X509V3_EXT_conf_nid(
190 nullptr, &ctx, NID_ext_key_usage, "critical,serverAuth,clientAuth"))
191 {
192 X509_add_ext(x509, ext, -1);
193 X509_EXTENSION_free(ext);
194 }
195
196 if (auto ext =
197 X509V3_EXT_conf_nid(nullptr, &ctx, NID_key_usage, "critical,digitalSignature"))
198 {
199 X509_add_ext(x509, ext, -1);
200 X509_EXTENSION_free(ext);
201 }
202
203 if (auto ext = X509V3_EXT_conf_nid(nullptr, &ctx, NID_subject_key_identifier, "hash"))
204 {
205 X509_add_ext(x509, ext, -1);
206 X509_EXTENSION_free(ext);
207 }
208 }
209
210 // And a private key
211 X509_set_pubkey(x509, kDefaultEphemeralPrivateKey);
212
213 if (!X509_sign(x509, kDefaultEphemeralPrivateKey, EVP_sha256()))
214 logicError("X509_sign failed");
215
216 return x509;
217 }();
218
219 SSL_CTX* const ctx = context.native_handle();
220
221 if (SSL_CTX_use_certificate(ctx, kDefaultCert) <= 0)
222 logicError("SSL_CTX_use_certificate failed");
223
224 if (SSL_CTX_use_PrivateKey(ctx, kDefaultEphemeralPrivateKey) <= 0)
225 logicError("SSL_CTX_use_PrivateKey failed");
226}
227
228static void
230 boost::asio::ssl::context& context,
231 std::string const& keyFile,
232 std::string const& certFile,
233 std::string const& chainFile)
234{
235 auto fmtError = [](boost::system::error_code ec) -> std::string {
236 return " [" + std::to_string(ec.value()) + ": " + ec.message() + "]";
237 };
238
239 SSL_CTX* const ssl = context.native_handle();
240
241 bool certSet = false;
242
243 if (!certFile.empty())
244 {
245 boost::system::error_code ec;
246
247 // NOLINTNEXTLINE(bugprone-unused-return-value)
248 context.use_certificate_file(certFile, boost::asio::ssl::context::pem, ec);
249
250 if (ec)
251 logicError("Problem with SSL certificate file" + fmtError(ec));
252
253 certSet = true;
254 }
255
256 if (!chainFile.empty())
257 {
258 // VFALCO Replace fopen() with RAII
259 FILE* f = fopen(chainFile.c_str(), "r");
260
261 if (f == nullptr)
262 {
264 "Problem opening SSL chain file" +
265 fmtError(boost::system::error_code(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 (!certSet)
278 {
279 if (SSL_CTX_use_certificate(ssl, x) != 1)
280 {
282 "Problem retrieving SSL certificate from chain "
283 "file.");
284 }
285
286 certSet = true;
287 }
288 else if (SSL_CTX_add_extra_chain_cert(ssl, x) != 1)
289 {
290 X509_free(x);
291 logicError("Problem adding SSL chain certificate.");
292 }
293 }
294
295 fclose(f);
296 }
297 catch (std::exception const& ex)
298 {
299 fclose(f);
301 std::string("Reading the SSL chain file generated an exception: ") + ex.what());
302 }
303 }
304
305 if (!keyFile.empty())
306 {
307 boost::system::error_code ec;
308
309 // NOLINTNEXTLINE(bugprone-unused-return-value)
310 context.use_private_key_file(keyFile, boost::asio::ssl::context::pem, ec);
311
312 if (ec)
313 {
314 logicError("Problem using the SSL private key file" + fmtError(ec));
315 }
316 }
317
318 if (SSL_CTX_check_private_key(ssl) != 1)
319 {
320 logicError("Invalid key in SSL private key file.");
321 }
322}
323
326{
327 auto c = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23);
328
329 c->set_options(
330 boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 |
331 boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::no_tlsv1 |
332 boost::asio::ssl::context::no_tlsv1_1 | boost::asio::ssl::context::single_dh_use |
333 boost::asio::ssl::context::no_compression);
334
335 if (cipherList.empty())
336 cipherList = kDefaultCipherList;
337
338 if (auto result = SSL_CTX_set_cipher_list(c->native_handle(), cipherList.c_str()); result != 1)
339 logicError("SSL_CTX_set_cipher_list failed");
340
341 c->use_tmp_dh({std::addressof(detail::kDefaultDh), sizeof(kDefaultDh)});
342
343 // Disable all renegotiation support in TLS v1.2. This can help prevent
344 // exploitation of the bug described in CVE-2021-3499 (for details see
345 // https://www.openssl.org/news/secadv/20210325.txt) when linking
346 // against OpenSSL versions prior to 1.1.1k.
347 SSL_CTX_set_options(c->native_handle(), SSL_OP_NO_RENEGOTIATION);
348
349 return c;
350}
351
352} // namespace openssl::detail
353
354//------------------------------------------------------------------------------
356makeSslContext(std::string const& cipherList)
357{
358 auto context = openssl::detail::getContext(cipherList);
360 // VFALCO NOTE, It seems the WebSocket context never has
361 // set_verify_mode called, for either setting of WEBSOCKET_SECURE
362 context->set_verify_mode(boost::asio::ssl::verify_none);
363 return context;
364}
365
368 std::string const& keyFile,
369 std::string const& certFile,
370 std::string const& chainFile,
371 std::string const& cipherList)
372{
373 auto context = openssl::detail::getContext(cipherList);
374 openssl::detail::initAuthenticated(*context, keyFile, certFile, chainFile);
375 return context;
376}
377
378} // namespace xrpl
T addressof(T... args)
T c_str(T... args)
T empty(T... args)
T gmtime(T... args)
T make_shared(T... args)
static void initAnonymous(boost::asio::ssl::context &context)
std::shared_ptr< boost::asio::ssl::context > getContext(std::string cipherList)
std::string const kDefaultCipherList
The default list of ciphers we accept over TLS.
static constexpr char kDefaultDh[]
The default DH parameters.
int gDefaultRsaKeyBits
The default strength of self-signed RSA certificates.
static void initAuthenticated(boost::asio::ssl::context &context, std::string const &keyFile, std::string const &certFile, std::string const &chainFile)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
void logicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
std::shared_ptr< boost::asio::ssl::context > makeSslContextAuthed(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.
std::shared_ptr< boost::asio::ssl::context > makeSslContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
T strftime(T... args)
T time(T... args)
T to_string(T... args)
T what(T... args)