Boost.Asio SSL Stream Security

Am I creating a single chain that all my SSL sockets share, or a single line in the SSL context (shared by any related sockets)?

The Boost.Asio SSL documentation claims this, but it does not mention contexts. I guess this means that I should only use one chain for everything, but I think it was written before OpenSSL supported multithreading.

SSL and streams

SSL flow objects do not perform their own locking. Therefore, it is important that all asynchronous SSL operations are performed in an implicit or explicit chain. Please note that this means that synchronization is not required (and thus there is no overhead for blocking) in single-threaded programs.

Most likely, I will have only one SSL context, but I am wondering if it is more appropriate to use it in the context of SSL or the global network service.

I really provided a CRYPTO_set_locking_callback handler in case that matters.

+5
source share
3 answers

UPDATE

The essence of this answer is disputed by David Schwartz, whose authority in this area I highly value.

There is reason to expect that ssl contexts can be shared between threads - at least for some operations, at least to make it easier to resume an SSL session.

I think David has experience with the SSL context that uses OpenSSL. Boost ASIO uses this in turn (at least on all platforms that I know of). Thus, either David writes the answer, sharing his knowledge, or you / I will have to spend some time on the OpenSSL documentation and the Boost Asio source code to find out the effective limitations that apply to using Boost Asio ssl::context .

The following are the limitations that are currently documented.

[text of the old answer follows]

Thread safety


In general, it is safe to use the simultaneous use of individual objects, but unsafe for the simultaneous use of one object. However, types such as io_service provide a higher guarantee that it is safe to use one object at a time.

Logically, since the documentation does not mention thread safety of the ssl_context class in particular, you should conclude that this is not the case.

It doesn’t matter that you know that the basic SSL library supports this if you use specific bindings (as you mentioned). This only says that it would not be easy to make the ssl_context stream stream.

But until you (work with library developers) provide this patch, it is not available.

In short, you get access to each ssl_context from one thread.

+2
source

I think there is confusion about this topic because there are a few things that need to be clarified. Let's start by stating that ::asio::ssl::context == SSL_CTX . These two are one.

Secondly, when using boost::asio::ssl , if you aren’t doing something crazy, when you bypass the internal init objects, you don’t need to manually set up crypto callbacks. This is done for you, as you can see in the sources here .

In fact, you can cause problems by doing this because the destructor for the init object works under the assumption that they did this work internally. Take the last bit with salt, because I did not examine it in detail.

Thirdly, I believe that you mix SSL streams with SSL contexts. For simplicity, think of SSL flow as a socket, think of the SSL context as a separate entity that sockets can use for various SSL functions, such as confirming communication with a specific negotiation key or as a server providing information about your server certificate to a connected client so that you could come to terms with the client.

Mentioning threads comes down to preventing possible simultaneous I / O with respect to one particular stream (socket), rather than contexts. Obviously, the problem of reading to the buffer and writing from the same buffer to the same socket will be a problem. Thus, when you deliver chain-wrapped completion handlers to the various ::asio::async_X , you execute a specific order to prevent the above scenario. You can read more in this answer provided by someone who knows a lot more about this than me.

Now, as far as the contexts go, David Schwartz points out the comments and another answer that he wrote, I need to dig out that the whole purpose of the contexts is to provide information that facilitates the SSL function through several SSL streams. It seems to imply that they should essentially be thread safe, given their intended purpose. I believe maybe he speaks in the context of ::asio::ssl::context , only because ::asio::ssl uses thread safety callbacks correctly, or maybe he just speaks in the context of using openSSL correctly in a multithreaded program.

Regardless of this, in addition to such comments and answers to SO and my own practical experience, it is incredibly difficult to find concrete evidence of this in the documentation or clearly define the boundaries between what is and is not thread safe. boost::asio::ssl:context , as David points out, is just a very thin wrapper around SSL_CTX . I would also like to add that this meant giving a more “C ++ ish” feel of working with a basic structure (s). It was probably also designed with some intention of decoupling ::asio::ssl and the base implementation library, but this is not achieved, the two are closely related. David mentions again that this thin wrapper is poorly documented, and you need to look at the implementation to get an idea.

If you are starting to delve into the implementation, there is a fairly simple way to find out what is and is not thread safe when it comes to contexts. You can search for CRYPTO_LOCK_SSL_CTX in sources such as ssl_lib.c .

 int SSL_CTX_set_generate_session_id(SSL_CTX *ctx, GEN_SESSION_CB cb) { CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX); ctx->generate_session_id = cb; CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX); return 1; } 

As you can see, CRYPTO_w_lock used, which returns us to the official page about openSSL and threads, here , which says:

OpenSSL can be safely used in multi-threaded applications with at least two callback functions installed, lock_function and threadid_func .

Now we get the full circle in the related source code asio/ssl/detail/impl/openssl_init.ipp in the first paragraph of this answer, where we see:

 do_init() { ::SSL_library_init(); ::SSL_load_error_strings(); ::OpenSSL_add_all_algorithms(); mutexes_.resize(::CRYPTO_num_locks()); for (size_t i = 0; i < mutexes_.size(); ++i) mutexes_[i].reset(new boost::asio::detail::mutex); ::CRYPTO_set_locking_callback(&do_init::openssl_locking_func); ::CRYPTO_set_id_callback(&do_init::openssl_id_func); #if !defined(SSL_OP_NO_COMPRESSION) \ && (OPENSSL_VERSION_NUMBER >= 0x00908000L) null_compression_methods_ = sk_SSL_COMP_new_null(); #endif // !defined(SSL_OP_NO_COMPRESSION) // && (OPENSSL_VERSION_NUMBER >= 0x00908000L) } 

Note:

CRYPTO_set_ locking_callback
CRYPTO_set_ id_callback

So, at least in terms of ::asio::ssl::context , thread safety here has nothing to do with threads, and everything related to openSSL working like openSSL is designed to work when used correctly in a multi-threaded program.

Returning to the first question, now all this is explained, David also gave the answer very simply in the comments:

The most common method is to use one line for each SSL connection.

Take the example of an HTTPS server that serves the contents of example.com . The server has one context configured with information such as a certificate for example.com . The client connects, this context is used for all connected clients to perform handshakes, etc. You transfer the connected client to a new session object where you are processing this client. It is in this session that you will have one direct, implicit or explicit, to protect the socket , not the context.

While I am by no means an expert, and I welcome corrections to this answer, I put everything I know about these subjects into practice in an open-source transparent HTTPS filter. This is a little more than 50% of comments on the code coefficient with more than 17 thousand lines, so everything that I know is written there (whether it is right or wrong)). If you want to see an example of this material in action, you can look at the source TlsCapableHttpBridge.hpp , which acts as a client on the server for each host, for each connection.

Server contexts and certificates are substituted and generated once and are shared for all clients that pan multiple streams. The only manual locking is during storage and context searching. There is one line on the bridge side : one for the real client socket and one for the upstream connection to the server, although they are not even technically necessary because the order of operations creates an implicit chain anyway.

Please note that the project is under development, as I am rewriting a lot of things (assembly instructions have not yet been presented), but everything is functional in terms of SSL MITM code, so you look at a fully functional class and related components.

+5
source

I would say it depends on how your protocol looks. If this is HTTP, there is no need to use a (explicit) string, since you are not reading or writing to your socket in parallel.

In fact, what will cause the problems is the following code:

 void func() { async_write(...); async_read(...); } 

because here, if your io_service () has a POOL of threads associated with it, the actual read and write can be performed in parallel by multiple threads.

If you have only one thread on io_service, there is no need for a chain. The same is true if you use HTTP, for example. In HTTP, you do not read or write to the socket in parallel, due to the protocol layout. You read the request from the client, although this can be done in several asynchronous calls, then you somehow process the request and headers, then you (asynchronously or not) send your response.

Quite a lot of what you can also read in the ASIO documentation.

+1
source

Source: https://habr.com/ru/post/1213336/


All Articles