What is an SSL and SSL_CTX? (Openssl client tutorial, Part 1)
OpenSSL uses a few specially-labelled structs as fundamental building blocks in creating an HTTPS connection. The internals of these structs are usually opaque and change from time to time, but a slew of functions exist to be able to modify them. They almost exhibit behavior similar to objects and classes in C++ (which is why I’ll sometimes refer to them as objects during this tutorial).
It can be difficult to figure out the exact purpose of these structs at first–the openssl.com documentation does a good job describing functions that change how they behave, but it unfortunately omits explaining what they actually do in the process. This usually results in hours of combing through their
long list of functions man-pages in order to piece together the purpose of basic structures. I have found that some of the greatest confusion initially lies in understanding the simple building blocks they use–particularly the uses of and differences between an SSL and an SSL_CTX.
The SSL Object
An SSL is used to hold the settings, buffers and other miscellaneous information for a single HTTPS connection. It acts as a wrapper around a given file descriptor to encrypt and decrypt plaintext data coming in and out of it. Think of it almost as an object you use in place of a regular socket–it has a function to connect via TLS to a peer, and functions to read encrypted data and automatically convert it into plaintext for us.
In fact, SSL objects take in an already-connected file descriptor since Transport Layer Security (TLS) is a protocol that works on top of the TCP protocol. Because of this, the life cycle of a client TLS connection actually involves two
connect() calls: the usual one for the socket, and then a special
SSL_connect() that takes in an SSL object and performs a TLS handshake with the host it’s already connected with.
Other than the added verification steps, the full life cycle of an SSL object mirrors that of a regular file descriptor and is pretty straightforward:
As we can see, the majority of the steps involve preparing the given SSL object for a connection–the actual process of connecting is as simple as calling
SSL_connect() and examining its return value to check for errors (I’ll cover that more in a later post). After that, everything can be done as if regular
write() calls were being performed. For brevity, a few steps that are closely related to the TLS Handshake have been omitted from this life cycle, such as checking to see if a peer’s certificate has been revoked.
It’s important to note that an SSL object should never be used for more than one connection, just like with sockets. In addition (though the distinction may be subtle), an SSL object should not connect to peers multiple times, except for in a few specific cases. The reason behind this is that there is information saved within an SSL object about what cipher and TLS version it ended up using for a connection; a second call to SSL_connect() would attempt to use the exact same settings that the first did, rather than the range of options originally given to it.
Why Have an SSL_CTX?
At some point while looking through OpenSSL man pages or source code you’ll begin to notice that almost all of the functions that apply to an SSL object also apply to SSL_CTX objects as well (albeit with the name distinction *SSL_CTX_** instead of *SSL_**). At first, this seems confusing and redundant: why have two different objects that by and by perform mostly the same functions?
The short answer is that the SSL_CTX is like a factory that produces new SSL objects; it never actually establishes connections, but instead builds SSL objects to do so. The reasoning behind having such a factory becomes clear when we look at creating multiple connections in a client or server.
One use case: Adding TLS to a HTTP server
As a demonstration, let’s say you want to build an HTTPS server. You create a new socket, set it to be listening on a certain port, configure your SSL object with all the nice settings and extra touches it needs, and have everything in place. Then you receive a new connection on said file descriptor. Huzzah! Your first client! You
accept() the connection, assign the newly-created file descriptor to your SSL object, set it to do a TLS handshake, and everything goes smoothly with communicating. But then, all of the sudden, another connection comes in. And another. With each new connection, you have to make an entirely new SSL object and load all the settings and configurations you wanted every single time.
Now, it is true that programmatically there appears to be a simple solution: just put all these configuration and setup actions into a single function. However, several of these configuration actions involve reading and loading up information from files. For instance, a server needs to have a certificate and a private key loaded up into each SSL object in order to communicate with a client; think of the overhead it would be for every single connection to be reading from those files just to begin a TLS handshake.
How the SSL_CTX Takes Care of Overhead
To make things easier in these sorts of situations, the creators of OpenSSL decided to add the SSL_CTX object, which is capable of loading up almost all of the same settings as an SSL object. It can then take those settings and copy them into a new SSL structure every time a new one is desired. The same configuration functions are left in the SSL object so that modifications can also be made on a per-connection basis.
For those building an app that only runs a single client connection, it’s a bit of a nuisance to have the extra SSL_CTX struct lying around that does a seemingly redundant task, but for any server or web browser running multiple connections the benefits are plentiful. Any files that need to be read, such as potentially hundreds of CA certificates (we’ll get to those more in a future post) or public/private key combinations, can simply be read in once on startup and never have to be worried about again.
It should be mentioned that there are additional benefits to this model from the perspective of server-side connections. In particular, session caching and renegotiation become possible if a central SSL_CTX is in place to store and recall such information about past connections spawned by SSL objects. Server Name Indication is also linked to the SSL_CTX in an inexplicable way–but those are all more advanced topics that will be reserved for a future blog post.
By now, you should have a slightly better understanding of how the SSL and the SSL_CTX are going to be used in building our HTTPS client. Of course, this is only the beginning; most of the how-to will come in the later parts of this tutorial. For now, I simply wanted to explain a design choice in OpenSSL and make sure that the purposes of the base building blocks are clear.
The next few posts will dive deeper into how to enable secure verification defaults on an SSL_CTX or a single SSL object once it has been created. For a bigger-picture overview of the kind of steps a client needs to take in order to establish a fully secure TLS connection, check out my previous blog post!
Next up: What ciphers are safe to use in OpenSSL? (Client Tutorial, Part 2)