Electroneum
Loading...
Searching...
No Matches
net_ssl.cpp
Go to the documentation of this file.
1// Copyright (c) 2018, The Monero Project
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification, are
6// permitted provided that the following conditions are met:
7//
8// 1. Redistributions of source code must retain the above copyright notice, this list of
9// conditions and the following disclaimer.
10//
11// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12// of conditions and the following disclaimer in the documentation and/or other
13// materials provided with the distribution.
14//
15// 3. Neither the name of the copyright holder nor the names of its contributors may be
16// used to endorse or promote products derived from this software without specific
17// prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29#include <string.h>
30#include <boost/asio/ssl.hpp>
31#include <openssl/ssl.h>
32#include <openssl/pem.h>
33#include "misc_log_ex.h"
34#include "net/net_ssl.h"
35
36#undef ELECTRONEUM_DEFAULT_LOG_CATEGORY
37#define ELECTRONEUM_DEFAULT_LOG_CATEGORY "net.ssl"
38
39// openssl genrsa -out /tmp/KEY 4096
40// openssl req -new -key /tmp/KEY -out /tmp/REQ
41// openssl x509 -req -days 999999 -sha256 -in /tmp/REQ -signkey /tmp/KEY -out /tmp/CERT
42
43namespace
44{
45 struct openssl_bio_free
46 {
47 void operator()(BIO* ptr) const noexcept
48 {
49 BIO_free(ptr);
50 }
51 };
52 using openssl_bio = std::unique_ptr<BIO, openssl_bio_free>;
53
54 struct openssl_pkey_free
55 {
56 void operator()(EVP_PKEY* ptr) const noexcept
57 {
58 EVP_PKEY_free(ptr);
59 }
60 };
61 using openssl_pkey = std::unique_ptr<EVP_PKEY, openssl_pkey_free>;
62
63 struct openssl_rsa_free
64 {
65 void operator()(RSA* ptr) const noexcept
66 {
67 RSA_free(ptr);
68 }
69 };
70 using openssl_rsa = std::unique_ptr<RSA, openssl_rsa_free>;
71
72 struct openssl_bignum_free
73 {
74 void operator()(BIGNUM* ptr) const noexcept
75 {
76 BN_free(ptr);
77 }
78 };
79 using openssl_bignum = std::unique_ptr<BIGNUM, openssl_bignum_free>;
80
81 struct openssl_ec_key_free
82 {
83 void operator()(EC_KEY* ptr) const noexcept
84 {
85 EC_KEY_free(ptr);
86 }
87 };
88 using openssl_ec_key = std::unique_ptr<EC_KEY, openssl_ec_key_free>;
89
90 struct openssl_group_free
91 {
92 void operator()(EC_GROUP* ptr) const noexcept
93 {
94 EC_GROUP_free(ptr);
95 }
96 };
97 using openssl_group = std::unique_ptr<EC_GROUP, openssl_group_free>;
98
99 boost::system::error_code load_ca_file(boost::asio::ssl::context& ctx, const std::string& path)
100 {
101 SSL_CTX* const ssl_ctx = ctx.native_handle(); // could be moved from context
102 if (ssl_ctx == nullptr)
103 return {boost::asio::error::invalid_argument};
104
105 if (!SSL_CTX_load_verify_locations(ssl_ctx, path.c_str(), nullptr))
106 {
107 return boost::system::error_code{
108 int(::ERR_get_error()), boost::asio::error::get_ssl_category()
109 };
110 }
111 return boost::system::error_code{};
112 }
113}
114
115namespace epee
116{
117namespace net_utils
118{
119
120
121// https://stackoverflow.com/questions/256405/programmatically-create-x509-certificate-using-openssl
122bool create_rsa_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert)
123{
124 MGINFO("Generating SSL certificate");
125 pkey = EVP_PKEY_new();
126 if (!pkey)
127 {
128 MERROR("Failed to create new private key");
129 return false;
130 }
131
132 openssl_pkey pkey_deleter{pkey};
133 openssl_rsa rsa{RSA_new()};
134 if (!rsa)
135 {
136 MERROR("Error allocating RSA private key");
137 return false;
138 }
139
140 openssl_bignum exponent{BN_new()};
141 if (!exponent)
142 {
143 MERROR("Error allocating exponent");
144 return false;
145 }
146
147 BN_set_word(exponent.get(), RSA_F4);
148
149 if (RSA_generate_key_ex(rsa.get(), 4096, exponent.get(), nullptr) != 1)
150 {
151 MERROR("Error generating RSA private key");
152 return false;
153 }
154
155 if (EVP_PKEY_assign_RSA(pkey, rsa.get()) <= 0)
156 {
157 MERROR("Error assigning RSA private key");
158 return false;
159 }
160
161 // the RSA key is now managed by the EVP_PKEY structure
162 (void)rsa.release();
163
164 cert = X509_new();
165 if (!cert)
166 {
167 MERROR("Failed to create new X509 certificate");
168 return false;
169 }
170 ASN1_INTEGER_set(X509_get_serialNumber(cert), 1);
171 X509_gmtime_adj(X509_get_notBefore(cert), 0);
172 X509_gmtime_adj(X509_get_notAfter(cert), 3600 * 24 * 182); // half a year
173 if (!X509_set_pubkey(cert, pkey))
174 {
175 MERROR("Error setting pubkey on certificate");
176 X509_free(cert);
177 return false;
178 }
179 X509_NAME *name = X509_get_subject_name(cert);
180 X509_set_issuer_name(cert, name);
181
182 if (X509_sign(cert, pkey, EVP_sha256()) == 0)
183 {
184 MERROR("Error signing certificate");
185 X509_free(cert);
186 return false;
187 }
188 (void)pkey_deleter.release();
189 return true;
190}
191
192bool create_ec_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert, int type)
193{
194 MGINFO("Generating SSL certificate");
195 pkey = EVP_PKEY_new();
196 if (!pkey)
197 {
198 MERROR("Failed to create new private key");
199 return false;
200 }
201
202 openssl_pkey pkey_deleter{pkey};
203 openssl_ec_key ec_key{EC_KEY_new()};
204 if (!ec_key)
205 {
206 MERROR("Error allocating EC private key");
207 return false;
208 }
209
210 EC_GROUP *group = EC_GROUP_new_by_curve_name(type);
211 if (!group)
212 {
213 MERROR("Error getting EC group " << type);
214 return false;
215 }
216 openssl_group group_deleter{group};
217
218 EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE);
219 EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED);
220
221 if (!EC_GROUP_check(group, NULL))
222 {
223 MERROR("Group failed check: " << ERR_reason_error_string(ERR_get_error()));
224 return false;
225 }
226 if (EC_KEY_set_group(ec_key.get(), group) != 1)
227 {
228 MERROR("Error setting EC group");
229 return false;
230 }
231 if (EC_KEY_generate_key(ec_key.get()) != 1)
232 {
233 MERROR("Error generating EC private key");
234 return false;
235 }
236 if (EVP_PKEY_assign_EC_KEY(pkey, ec_key.get()) <= 0)
237 {
238 MERROR("Error assigning EC private key");
239 return false;
240 }
241
242 // the key is now managed by the EVP_PKEY structure
243 (void)ec_key.release();
244
245 cert = X509_new();
246 if (!cert)
247 {
248 MERROR("Failed to create new X509 certificate");
249 return false;
250 }
251 ASN1_INTEGER_set(X509_get_serialNumber(cert), 1);
252 X509_gmtime_adj(X509_get_notBefore(cert), 0);
253 X509_gmtime_adj(X509_get_notAfter(cert), 3600 * 24 * 182); // half a year
254 if (!X509_set_pubkey(cert, pkey))
255 {
256 MERROR("Error setting pubkey on certificate");
257 X509_free(cert);
258 return false;
259 }
260 X509_NAME *name = X509_get_subject_name(cert);
261 X509_set_issuer_name(cert, name);
262
263 if (X509_sign(cert, pkey, EVP_sha256()) == 0)
264 {
265 MERROR("Error signing certificate");
266 X509_free(cert);
267 return false;
268 }
269 (void)pkey_deleter.release();
270 return true;
271}
272
273ssl_options_t::ssl_options_t(std::vector<std::vector<std::uint8_t>> fingerprints, std::string ca_path)
274 : fingerprints_(std::move(fingerprints)),
275 ca_path(std::move(ca_path)),
276 auth(),
279{
280 std::sort(fingerprints_.begin(), fingerprints_.end());
281}
282
283boost::asio::ssl::context ssl_options_t::create_context() const
284{
285 boost::asio::ssl::context ssl_context{boost::asio::ssl::context::tlsv12};
286 if (!bool(*this))
287 return ssl_context;
288
289 // only allow tls v1.2 and up
290 ssl_context.set_options(boost::asio::ssl::context::default_workarounds);
291 ssl_context.set_options(boost::asio::ssl::context::no_sslv2);
292 ssl_context.set_options(boost::asio::ssl::context::no_sslv3);
293 ssl_context.set_options(boost::asio::ssl::context::no_tlsv1);
294 ssl_context.set_options(boost::asio::ssl::context::no_tlsv1_1);
295
296 // only allow a select handful of tls v1.3 and v1.2 ciphers to be used
297 SSL_CTX_set_cipher_list(ssl_context.native_handle(), "ECDHE-ECDSA-CHACHA20-POLY1305-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256");
298
299 // set options on the SSL context for added security
300 SSL_CTX *ctx = ssl_context.native_handle();
301 CHECK_AND_ASSERT_THROW_MES(ctx, "Failed to get SSL context");
302 SSL_CTX_clear_options(ctx, SSL_OP_LEGACY_SERVER_CONNECT); // SSL_CTX_SET_OPTIONS(3)
303 SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); // https://stackoverflow.com/questions/22378442
304#ifdef SSL_OP_NO_TICKET
305 SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET); // https://stackoverflow.com/questions/22378442
306#endif
307#ifdef SSL_OP_NO_RENEGOTIATION
308 SSL_CTX_set_options(ctx, SSL_OP_NO_RENEGOTIATION);
309#endif
310#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
311 SSL_CTX_set_options(ctx, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
312#endif
313#ifdef SSL_OP_NO_COMPRESSION
314 SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
315#endif
316#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
317 SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
318#endif
319 SSL_CTX_set_ecdh_auto(ctx, 1);
320
321 switch (verification)
322 {
324 ssl_context.set_default_verify_paths();
325 break;
327 ssl_context.set_verify_depth(0);
328 /* fallthrough */
330 if (!ca_path.empty())
331 {
332 const boost::system::error_code err = load_ca_file(ssl_context, ca_path);
333 if (err)
334 throw boost::system::system_error{err, "Failed to load user CA file at " + ca_path};
335 }
336 break;
337 default:
338 break;
339 }
340
341 CHECK_AND_ASSERT_THROW_MES(auth.private_key_path.empty() == auth.certificate_path.empty(), "private key and certificate must be either both given or both empty");
342 if (auth.private_key_path.empty())
343 {
344 EVP_PKEY *pkey;
345 X509 *cert;
346 bool ok = false;
347
348#ifdef USE_EXTRA_EC_CERT
349 CHECK_AND_ASSERT_THROW_MES(create_ec_ssl_certificate(pkey, cert, NID_secp256k1), "Failed to create certificate");
350 CHECK_AND_ASSERT_THROW_MES(SSL_CTX_use_certificate(ctx, cert), "Failed to use generated certificate");
351 if (!SSL_CTX_use_PrivateKey(ctx, pkey))
352 MERROR("Failed to use generated EC private key for " << NID_secp256k1);
353 else
354 ok = true;
355 X509_free(cert);
356 EVP_PKEY_free(pkey);
357#endif
358
359 CHECK_AND_ASSERT_THROW_MES(create_rsa_ssl_certificate(pkey, cert), "Failed to create certificate");
360 CHECK_AND_ASSERT_THROW_MES(SSL_CTX_use_certificate(ctx, cert), "Failed to use generated certificate");
361 if (!SSL_CTX_use_PrivateKey(ctx, pkey))
362 MERROR("Failed to use generated RSA private key for RSA");
363 else
364 ok = true;
365 X509_free(cert);
366 EVP_PKEY_free(pkey);
367
368 CHECK_AND_ASSERT_THROW_MES(ok, "Failed to use any generated certificate");
369 }
370 else
371 auth.use_ssl_certificate(ssl_context);
372
373 return ssl_context;
374}
375
376void ssl_authentication_t::use_ssl_certificate(boost::asio::ssl::context &ssl_context) const
377{
378 ssl_context.use_private_key_file(private_key_path, boost::asio::ssl::context::pem);
379 ssl_context.use_certificate_chain_file(certificate_path);
380}
381
382bool is_ssl(const unsigned char *data, size_t len)
383{
384 if (len < get_ssl_magic_size())
385 return false;
386
387 // https://security.stackexchange.com/questions/34780/checking-client-hello-for-https-classification
388 MDEBUG("SSL detection buffer, " << len << " bytes: "
389 << (unsigned)(unsigned char)data[0] << " " << (unsigned)(unsigned char)data[1] << " "
390 << (unsigned)(unsigned char)data[2] << " " << (unsigned)(unsigned char)data[3] << " "
391 << (unsigned)(unsigned char)data[4] << " " << (unsigned)(unsigned char)data[5] << " "
392 << (unsigned)(unsigned char)data[6] << " " << (unsigned)(unsigned char)data[7] << " "
393 << (unsigned)(unsigned char)data[8]);
394 if (data[0] == 0x16) // record
395 if (data[1] == 3) // major version
396 if (data[5] == 1) // ClientHello
397 if (data[6] == 0 && data[3]*256 + data[4] == data[7]*256 + data[8] + 4) // length check
398 return true;
399 return false;
400}
401
402bool ssl_options_t::has_strong_verification(boost::string_ref host) const noexcept
403{
404 // onion and i2p addresses contain information about the server cert
405 // which both authenticates and encrypts
406 if (host.ends_with(".onion") || host.ends_with(".i2p"))
407 return true;
408 switch (verification)
409 {
410 default:
413 return false;
416 break;
417 }
418 return true;
419}
420
421bool ssl_options_t::has_fingerprint(boost::asio::ssl::verify_context &ctx) const
422{
423 // can we check the certificate against a list of fingerprints?
424 if (!fingerprints_.empty()) {
425 X509_STORE_CTX *sctx = ctx.native_handle();
426 if (!sctx)
427 {
428 MERROR("Error getting verify_context handle");
429 return false;
430 }
431
432 X509* cert = nullptr;
433 const STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(sctx);
434 if (!chain || sk_X509_num(chain) < 1 || !(cert = sk_X509_value(chain, 0)))
435 {
436 MERROR("No certificate found in verify_context");
437 return false;
438 }
439
440 // buffer for the certificate digest and the size of the result
441 std::vector<uint8_t> digest(EVP_MAX_MD_SIZE);
442 unsigned int size{ 0 };
443
444 // create the digest from the certificate
445 if (!X509_digest(cert, EVP_sha256(), digest.data(), &size)) {
446 MERROR("Failed to create certificate fingerprint");
447 return false;
448 }
449
450 // strip unnecessary bytes from the digest
451 digest.resize(size);
452
453 return std::binary_search(fingerprints_.begin(), fingerprints_.end(), digest);
454 }
455
456 return false;
457}
458
459bool ssl_options_t::handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, boost::asio::ssl::stream_base::handshake_type type, const std::string& host) const
460{
461 socket.next_layer().set_option(boost::asio::ip::tcp::no_delay(true));
462
463 /* Using system-wide CA store for client verification is funky - there is
464 no expected hostname for server to verify against. If server doesn't have
465 specific whitelisted certificates for client, don't require client to
466 send certificate at all. */
467 const bool no_verification = verification == ssl_verification_t::none ||
468 (type == boost::asio::ssl::stream_base::server && fingerprints_.empty() && ca_path.empty());
469
470 /* According to OpenSSL documentation (and SSL specifications), server must
471 always send certificate unless "anonymous" cipher mode is used which are
472 disabled by default. Either way, the certificate is never inspected. */
473 if (no_verification)
474 socket.set_verify_mode(boost::asio::ssl::verify_none);
475 else
476 {
477 socket.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert);
478
479 // in case server is doing "virtual" domains, set hostname
480 SSL* const ssl_ctx = socket.native_handle();
481 if (type == boost::asio::ssl::stream_base::client && !host.empty() && ssl_ctx)
482 SSL_set_tlsext_host_name(ssl_ctx, host.c_str());
483
484 socket.set_verify_callback([&](const bool preverified, boost::asio::ssl::verify_context &ctx)
485 {
486 // preverified means it passed system or user CA check. System CA is never loaded
487 // when fingerprints are whitelisted.
488 const bool verified = preverified &&
489 (verification != ssl_verification_t::system_ca || host.empty() || boost::asio::ssl::rfc2818_verification(host)(preverified, ctx));
490
491 if (!verified && !has_fingerprint(ctx))
492 {
493 // autodetect will reconnect without SSL - warn and keep connection encrypted
495 {
496 MERROR("SSL certificate is not in the allowed list, connection droppped");
497 return false;
498 }
499 MWARNING("SSL peer has not been verified");
500 }
501 return true;
502 });
503 }
504
505 boost::system::error_code ec;
506 socket.handshake(type, ec);
507 if (ec)
508 {
509 MERROR("SSL handshake failed, connection dropped: " << ec.message());
510 return false;
511 }
512 MDEBUG("SSL handshake success");
513 return true;
514}
515
516bool ssl_support_from_string(ssl_support_t &ssl, boost::string_ref s)
517{
518 if (s == "enabled")
520 else if (s == "disabled")
522 else if (s == "autodetect")
524 else
525 return false;
526 return true;
527}
528
529} // namespace
530} // namespace
531
boost::asio::ssl::context create_context() const
Definition net_ssl.cpp:283
ssl_verification_t verification
Definition net_ssl.h:82
ssl_options_t(ssl_support_t support)
Verification is set to system ca unless SSL is disabled.
Definition net_ssl.h:85
bool has_strong_verification(boost::string_ref host) const noexcept
\retrurn True if host can be verified using this configuration WITHOUT system "root" CAs.
Definition net_ssl.cpp:402
ssl_authentication_t auth
Definition net_ssl.h:80
bool handshake(boost::asio::ssl::stream< boost::asio::ip::tcp::socket > &socket, boost::asio::ssl::stream_base::handshake_type type, const std::string &host={}) const
Definition net_ssl.cpp:459
bool has_fingerprint(boost::asio::ssl::verify_context &ctx) const
Search against internal fingerprints. Always false if behavior() != user_certificate_check.
Definition net_ssl.cpp:421
#define MERROR(x)
Definition misc_log_ex.h:73
#define MWARNING(x)
Definition misc_log_ex.h:74
#define MDEBUG(x)
Definition misc_log_ex.h:76
#define MGINFO(x)
Definition misc_log_ex.h:80
#define CHECK_AND_ASSERT_THROW_MES(expr, message)
@ user_certificates
Verify peer via specific (non-chain) certificate(s) only.
Definition net_ssl.h:56
@ none
Do not verify peer.
Definition net_ssl.h:54
@ system_ca
Verify peer via system ca only (do not inspect user certificates).
Definition net_ssl.h:55
@ user_ca
Verify peer via specific (possibly chain) certificate(s) only.
Definition net_ssl.h:57
bool is_ssl(const unsigned char *data, size_t len)
Definition net_ssl.cpp:382
constexpr size_t get_ssl_magic_size()
Definition net_ssl.h:135
bool ssl_support_from_string(ssl_support_t &ssl, boost::string_ref s)
Definition net_ssl.cpp:516
bool create_rsa_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert)
Definition net_ssl.cpp:122
bool create_ec_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert)
STL namespace.
std::string certificate_path
Certificate used for authentication to peer.
Definition net_ssl.h:63
std::string private_key_path
Private key used for authentication.
Definition net_ssl.h:62
void use_ssl_certificate(boost::asio::ssl::context &ssl_context) const
Load private_key_path and certificate_path into ssl_context.
Definition net_ssl.cpp:376