Chapter 5. SSL/TLS Programming

The main feature of the OpenSSL library is its implementations of the Secure Sockets Layer (SSL) and Transport Layer Security (TLS) protocols. Originally developed by Netscape for secure web transactions, the protocol has grown into a general solution for secure stream-based communications. Netscape’s first public version of SSL is what we now call SSL Version 2. From that point, security experts began working to improve upon some of the flaws in SSLv2, and that gave birth to SSL Version 3. Development of a standard for transport layer security based on SSL was being done concurrently, which resulted in TLS Version 1. Because of the security flaws with SSLv2, modern applications should not support it. In this chapter, we’ll discuss only programming with the SSLv3 and TLSv1 protocols in OpenSSL. Unless otherwise noted, when we refer to SSL, we refer to both SSLv3 and TLSv1.

From a design perspective, we need to know more than just that we want to use SSL in our application. The correct implementation of an SSL-enabled program can be difficult due to complexities in protocol setup, the large size of the API, and developer inexperience with the library. OpenSSL’s SSL support was originally designed to mimic the Unix socket interface; however, the likenesses quickly fade as we get into the subtleties of the API. In order to make the process of becoming acquainted with the massive library easier, we take a small example client and server through a step-by-step process of making it SSL-enabled and secure. To aid understanding, we start with some simplifying assumptions that may not be practical for real-world applications.

From that point, we’ll bridge the gap to the more advanced OpenSSL features. The goal of this chapter is to compartmentalize the features of the library into smaller groups to establish a sense of process. We hope that this process will serve as a template for developers when it comes to implementing SSL in their own applications. In adding SSL to an application, the application’s unique requirements must be considered, and the best decision must be made for both security and functionality.

Programming with SSL

OpenSSL’s API for SSL is large and can be daunting to inexperienced programmers. Additionally, as discussed in Chapter 1, SSL can be ineffective at accomplishing its security goals if implemented incorrectly. These factors compound to leave the developer with a difficult task. In hopes of disentangling the mystery of implementing secure programs, we attack the problem in three steps. At each step, the developer must provide some application-specific knowledge to make sure SSL does its job. For example, the choices made by a developer of a highly compatible web browser will be different from those made by a developer of a highly secure server application.

The steps below provide a template for developers to follow when implementing an SSL client or server. We will start with a small example and build upon it. This example will not be secure to our satisfaction until all of the steps have been thought through. In each step, we will introduce a small dose of the API; after all the steps, the developer should be able to think through the design of an SSL-enabled application much more clearly. Completing these steps is not the end of the road, however. In order to address the requirements of many applications, we need to go further and look into the advanced features of the API.

The Application(s) to Secure

We will be using two very simple applications: a client and server in which the server simply echoes data from the client to the console. Our goal is to augment our two applications so that they can perform their tasks in a hostile environment. In other words, we’ll implement each program to strictly authenticate connecting peers. As we walk the path to creating SSL-enabled versions of our programs, we discuss the choices that a developer must make at each stage.

Before moving forward, let’s look at our sample applications. There a total of four files: common.h, common.c, client.c, and server.c. The code for each is presented in Example 5-1 through Example 5-4. Also, we use the code presented in Example 4-2 so we can use multiple threads. For Unix systems, we’ll continue to use POSIX threads.

Starting with common.h in Example 5-1, Lines 1-5 include relevant headers from OpenSSL. Right now, we don’t make use of anything from a few of these headers, but we soon will, so they are included. Lines 22-24 define the strings for the client and server machines as well as the server’s listening port. In addition, the header contains some definitions for convenient error handling and for threading in a platform-independent manner similar to the definitions set forth in Chapter 4’s threading examples.

Example 5-1. common.h
  1  #include <openssl/bio.h>
  2  #include <openssl/err.h>
  3  #include <openssl/rand.h>
  4  #include <openssl/ssl.h>
  5  #include <openssl/x509v3.h>
  6  
  7  #ifndef WIN32
  8  #include <pthread.h>
  9  #define THREAD_CC
 10  #define THREAD_TYPE                    pthread_t
 11  #define THREAD_CREATE(tid, entry, arg) pthread_create(&(tid), NULL, \
 12                                                        (entry), (arg))
 13  #else
 14  #include <windows.h>
 15  #define THREAD_CC                      _  _cdecl
 16  #define THREAD_TYPE                    DWORD
 17  #define THREAD_CREATE(tid, entry, arg) do { _beginthread((entry), 0, (arg));\
 18                                              (tid) = GetCurrentThreadId(  );   \
 19                                         } while (0)
 20  #endif
 21   
 22  #define PORT            "6001"
 23  #define SERVER          "splat.zork.org"
 24  #define CLIENT          "shell.zork.org"
 25   
 26  #define int_error(msg)  handle_error(__FILE__, __LINE_  _, msg)
 27  void handle_error(const char *file, int lineno, const char *msg);
 28   
 29  void init_OpenSSL(void);

Example 5-2, the file common.c , defines our error reporting function handle_error . The error handling in our example applications is a bit draconian, and you’ll most likely want to handle errors in your own applications in a much more user-friendly manner. In general, it’s not appropriate to handle all possible errors by bringing the application to such an abrupt halt.

The file common.c also defines a function that will perform common initialization such as setting up OpenSSL for multithreading, initializing the library, and loading error strings. The call to SSL_load_error_strings loads the associated data for error codes so that when errors occur and we print the error stack, we get human-readable information about what went wrong. Since loading these diagnostic strings does take memory, there are circumstances in which we would not want to make this call, such as when we’re developing applications for embedded systems or other machines with limited memory. Generally, it’s a good idea to load the strings since it makes the job of decoding error messages much easier.

As we build in SSL support, the file common.c will hold the implementations of functions used by both our client and server, and common.h will receive the prototypes.

Example 5-2. common.c
  1  #include "common.h"
  2   
  3  void handle_error(const char *file, int lineno, const char *msg)
  4  {
  5      fprintf(stderr, "** %s:%i %s\n", file, lineno, msg);
  6      ERR_print_errors_fp(stderr);
  7      exit(-1);
  8  }
  9   
 10  void init_OpenSSL(void)
 11  {
 12      if (!THREAD_setup() || !SSL_init_library(  ))
 13      {
 14          fprintf(stderr, "** OpenSSL initialization failed!\n");
 15          exit(-1);
 16      }
 17      SSL_load_error_strings(  );
 18  }

The bulk of our client application is in Example 5-3, client.c. At a high level, it creates a connection to the server on port 6001, as specified in common.h. Once a connection is established, it reads data from stdin until EOF is reached. As data is read and an internal buffer is filled, the data is sent over the connection to the server. Note that at this point, although we’re using OpenSSL for socket communications, we have not yet enabled the use of the SSL protocol.

Lines 27-29 create a new BIO object with a BIO_METHOD returned from BIO_s_connect ; the call to BIO_new_connect is a simplified function to accomplish this task. As long as no error occurs, lines 31-32 do the work of actually making the TCP connection and checking for errors. When a connection is successfully established, do_client_loop is called, which continually reads data from stdin and writes that data out to the socket. If an error while writing occurs or an EOF is received while reading from the console, the function exits and the program terminates.

Example 5-3. client.c
  1  #include "common.h"
  2   
  3  void do_client_loop(BIO *conn)
  4  {
  5      int  err, nwritten;
  6      char buf[80];
  7   
  8      for (;;)
  9      {
 10          if (!fgets(buf, sizeof(buf), stdin))
 11              break;
 12          for (nwritten = 0;  nwritten < sizeof(buf);  nwritten += err)
 13          {
 14              err = BIO_write(conn, buf + nwritten, strlen(buf) - nwritten);
 15              if (err <= 0)
 16                  return;
 17          }
 18      }
 19  }
 20   
 21  int main(int argc, char *argv[])
 22  {
 23      BIO  *conn;
 24   
 25      init_OpenSSL(  );
 26   
 27      conn = BIO_new_connect(SERVER ":" PORT);
 28      if (!conn)
 29          int_error("Error creating connection BIO");
 30   
 31      if (BIO_do_connect(conn) <= 0)
 32          int_error("Error connecting to remote machine");
 33   
 34      fprintf(stderr, "Connection opened\n");
 35      do_client_loop(conn);
 36      fprintf(stderr, "Connection closed\n");
 37   
 38      BIO_free(conn);
 39      return 0;
 40  }

The server application in Example 5-4, server.c, differs from the client program in a few ways. After making the call to our common initialization function (line 44), it creates a different kind of BIO, one based on the BIO_METHOD returned from BIO_s_accept . This type of BIO creates a server socket that can accept remote connections. In lines 50-51, a call to BIO_do_accept binds the socket to port 6001; subsequent calls to BIO_do_accept will block and wait for a remote connection. The loop in lines 53-60 blocks until a connection is made. When a connection is made, a new thread to handle the new connection is spawned, which then calls do_server_loop with the connected socket’s BIO. The function do_server_loop simply reads data from the socket and writes it back out to stdout. If any errors occur here, the function returns and the thread is terminated. As a note, we call ERR_remove_state on line 33 to make sure any memory used by the error queue for the thread is freed.

Example 5-4. The server application
  1  #include "common.h"
  2   
  3  void do_server_loop(BIO *conn)
  4  {
  5      int  done, err, nread;
  6      char buf[80];
  7   
  8      do
  9      {
 10          for (nread = 0;  nread < sizeof(buf);  nread += err)
 11          {
 12               err = BIO_read(conn, buf + nread, sizeof(buf) - nread);
 13               if (err <= 0)
 14                   break;
 15          }
 16          fwrite(buf, 1, nread, stdout);
 17      }
 18      while (err > 0);
 19  }
 20   
 21  void THREAD_CC server_thread(void *arg)
 22  {
 23      BIO *client = (BIO *)arg;
 24   
 25  #ifndef WIN32
 26      pthread_detach(pthread_self(  ));
 27  #endif
 28      fprintf(stderr, "Connection opened.\n");
 29      do_server_loop(client);
 30      fprintf(stderr, "Connection closed.\n");
 31  
 32      BIO_free(client);
 33      ERR_remove_state(0);
 34  #ifdef WIN32
 35      _endthread(  );
 36  #endif
 37  }
 38   
 39  int main(int argc, char *argv[])
 40  {
 41      BIO         *acc, *client;
 42      THREAD_TYPE tid;
 43   
 44      init_OpenSSL(  );
 45   
 46      acc = BIO_new_accept(PORT);
 47      if (!acc)
 48          int_error("Error creating server socket");
 49    
 50      if (BIO_do_accept(acc) <= 0)
 51          int_error("Error binding server socket");
 52    
 53      for (;;)
 54      {
 55          if (BIO_do_accept(acc) <= 0)
 56              int_error("Error accepting connection");
 57   
 58          client = BIO_pop(acc);
 59          THREAD_CREATE(tid, server_thread, client);
 60      }
 61   
 62      BIO_free(acc);
 63      return 0;
 64  }

Now that we understand the sample application, we’re ready to take the steps necessary to secure the communications with SSL.

Step 1: SSL Version Selection and Certificate Preparation

In order for SSL connections to be secure, we must select a secure version of the protocol and provide accurate certificate information for the peer to validate. Since this is our first introduction to the SSL API, we will cover the background information about the structures and functions we need to accomplish our task.

Background

We need to examine three relevant object types: SSL_METHOD, SSL_CTX, and SSL. An SSL_METHOD represents an implementation of SSL functionality. In other words, it specifies a protocol version. OpenSSL provides populated SSL_METHOD objects and some accessor methods for them. They are listed in Table 5-1. The extent of our interaction with this type of object will be to select the protocol version we wish to support by making a function call from the table.

Table 5-1. Functions to retrieve pointers to SSL_METHOD objects

Function

Comments

SSLv2_method

Returns a pointer to SSL_METHOD for generic SSL Version 2

SSLv2_client_method

Returns a pointer to SSL_METHOD for an SSL Version 2 client

SSLv2_server_method

Returns a pointer to SSL_METHOD for an SSL Version 2 server

SSLv3_method

Returns a pointer to SSL_METHOD for generic SSL Version 3

SSLv3_client_method

Returns a pointer to SSL_METHOD for an SSL Version 3 client

SSLv3_server_method

Returns a pointer to SSL_METHOD for an SSL Version 3 server

TLSv1_method

Returns a pointer to SSL_METHOD for generic TLS Version 1

TLSv1_client_method

Returns a pointer to SSL_METHOD for a TLS Version 1 client

TLSv1_server_method

Returns a pointer to SSL_METHOD for a TLS Version 1 server

SSLv23_method

Returns a pointer to SSL_METHOD for generic SSL/TLS

SSLv23_client_method

Returns a pointer to SSL_METHOD for an SSL/TLS client

SSLv23_server_method

Returns a pointer to SSL_METHOD for an SSL/TLS server

OpenSSL provides implementations for SSL Version 2, SSL Version 3, and TLS Version 1. Also, some SSLv23 functions don’t indicate a specific protocol version but rather a compatibility mode. In such a mode, a connection will report that it can handle any of the three SSL/TLS protocol versions. To reiterate, applications should not use SSLv2, since this protocol is known to have security flaws. Using an SSL_METHOD object retrieved by one of the functions in Table 5-1, we create an SSL_CTX object.

Tip

How would we create an application that supports both SSLv3 and TLSv1? If we are to create a server that needs to communicate with both SSLv3 and TLSv1 clients, using either SSLv3_method or TLSv1_method will prevent one kind of client from connecting properly. Since we do not want to use SSL Version 2 (it is insecure), it would seem that the compatibility implementation SSLv23_method is also not an option. This isn’t actually true. We can use the compatibility mode and set an option in the SSL_CTX object to have it remove SSLv2 from the acceptable protocols. The function to do this is SSL_CTX_set_options , and the relevant details for doing this are in Step 3.

An SSL_CTX object will be a factory for producing SSL connection objects. This context allows us to set connection configuration parameters before the connection is made, such as protocol version, certificate information, and verification requirements. It is easiest to think of SSL_CTX objects as the containers for default values for the SSL connections to be made by a program. Objects of this type are created with the function SSL_CTX_new. This function takes only one argument, generally supplied from the return value of one of the functions in Table 5-1.

In general, an application will create just one SSL_CTX object for all of the connections it makes. From this SSL_CTX object, an SSL type object can be created with the SSL_new function. This function causes the newly created SSL object to inherit all of the parameters set forth in the context. Even though most of the settings are copied to the SSL object on invocation of SSL_new, the order in which calls are made to OpenSSL functions can cause unexpected behavior if we’re not careful.

Warning

Applications should set up an SSL_CTX completely, with all connection invariant settings, before creating SSL objects from it. In other words, after calling SSL_new with a particular context object, no more calls operating on that SSL_CTX object should be made until all produced SSL objects are no longer in use. The reason is simple. Modifying a context can sometimes affect the SSL connections that have already been created (i.e.., a function we examine later, SSL_CTX_set_default_passwd_cb, changes the callback in the context and in all connections that were already created from this context). To avoid any unpredictability, never modify the context after connection creation has begun. If there are any connection-specific parameters that we do need to set, most SSL_CTX functions have SSL counterparts that act on SSL-type objects.

Certificate preparation

The SSL protocol usually requires the server to present a certificate. The certificate contains credentials that the client may look at to determine if the server is authentic and can be trusted. As we know, a peer validates a certificate through verification of its chain of signers. Thus, to implement an SSL server correctly, we must provide certificate and chain information to the peer. The SSL protocol also allows the client to optionally present certificate information so that the server may authenticate it.

Tip

There are, in fact, ways of using SSL to create anonymous connections in which neither the server nor client presents a certificate. These are done by using the Diffie-Hellman key-agreement protocol and setting the SSL cipher suite to include the anonymous DH algorithm. This is discussed further Section 5.1.4.3 below.

In general, server applications should always provide certificates to peers, and clients can do so optionally. The purpose and the desired security of the application should dictate whether client certificates are used. For instance, a server may request a client certificate, and if our client does not have one to present, we may not be able to establish a secure connection. Thus, it’s a good idea to implement client certificates if it makes sense to do so. On the other hand, server certificates are usually required, and, unless our goal is to create a completely nonauthenticated connection, we should implement them.

OpenSSL presents the client certificate to the server during handshakes, as long as we assign a certificate to the client and the server asks for it. This is actually a small violation of the TLS protocol. The protocol calls for the server to present a list of valid CAs, and the client should send a certificate only if it matches. In practice, this infraction of the standard should not affect anything, but the behavior may be fixed in future versions of OpenSSL.

The SSL API has several ways to incorporate certificate information into an SSL_CTX object. The function to use is SSL_CTX_use_certificate_chain_file . It loads the chain of certificates from the filename specified by the second argument. The file should contain the certificate chain in order, starting with the certificate for the application and ending with the root CA certificate. Each of these entries must be in PEM format.

In addition to loading the certificate chain, the SSL_CTX object must have the application’s private key. This key must correspond to the public key embedded within the certificate. The easiest way that we can supply this key to the context is through the SSL_CTX_use_PrivateKey_file function. The second argument specifies the filename, and the third specifies the type of encoding. The type is specified by using a defined name—either SSL_FILETYPE_PEM or SSL_FILETYPE_ASN1. It bears mentioning that this private key must be kept secret for the application to remain secure. Therefore, using an encrypted PEM format for on-disk storage is recommended; using triple DES in CBC mode is a good choice. The SSL_CTX will fail to incorporate an encrypted private key correctly unless the correct passphrase is supplied.

OpenSSL collects passphrases through a callback function. The default callback prompts the user on the terminal. For some applications, the default will not be acceptable. The function SSL_CTX_set_default_passwd_cb allows us to set the callback to something more appropriate for the application. The assigned function is invoked during the call to SSL_CTX_use_PrivateKey_file if the indicated file contains an encrypted key. Therefore, the callback should be set before making that call. In fact, the certificates in our chain could be encrypted even though there is nothing secret about them, and our callback function would be invoked to gather the passphrase. More accurately, we could state that the passphrase function is called any time a piece of encrypted information is loaded as a parameter to the SSL_CTX.

The callback function’s obligation is to copy the passphrase into the buffer that is supplied to it when it is called. The callback function is called with four arguments.

int passwd_cb(char * buf, int size, int flag, void *userdata);
buf

The buffer that the passphrase should be copied into. The buffer must be NULL terminated.

size

The size of the buffer in bytes; includes space for the NULL terminating character.

flag

Passed as either zero or nonzero. When the flag is nonzero, this passphrase will be used to perform encryption; otherwise, it will be used to perform decryption.

userdata

Application-specific data; SSL_CTX_set_default_passwd_cb_userdata is used to set this data. Whatever data is set by the application is passed to the callback function untouched by OpenSSL.

There are two approaches to implementing the passphrase callback. The first method is simply to have the callback prompt the user, copy the collected passphrase to the buffer, and return. This method is viable for applications that need to decrypt a key only once, commonly on application startup. The second way to implement the callback is for the application to prompt the user for a passphrase on startup and store the collected information in a buffer. The passphrase can be added to the SSL_CTX as user data via the function SSL_CTX_set_default_passwd_cb_userdata . With this method, the callback itself only needs to copy the data from the fourth parameter to the first. This method is viable for applications that need to decrypt keys during normal operation in which constant user prompting is a nuisance.

Tip

An unlimited number of PEM-encoded items can be stored in a file, but only one DER item may be stored in a file. Also, different types of PEM items may be stored within a single file. As a result, if the private key is kept in PEM encoding, it can be appended to the certificate chain file, and the same filename can be used for the calls to SSL_CTX_use_certificate_chain_file and SSL_CTX_use_PrivateKey_file . This trick is used in Example 5-5. PEM and DER encodings are discussed in Chapter 8.

At this stage, we will limit our discussion to the process of providing certificate information to the peer, rather than discussing the processes of validation. The validation problem will be discussed in Step 2.

Our example extended

Using the same example applications that we’ve already provided, let’s modify them with what we’ve learned about making SSL connections. Keep in mind that this example is not yet secure. It does not validate anything about the peer to which it connects; it merely provides the authentication information. The new version of our client in client1.c is shown in Example 5-5. The bold lines are those that we have added or changed.

Example 5-5. client1.c
  1  #include "common.h"
  2   
  3  #define CERTFILE "client.pem"
  4  SSL_CTX *setup_client_ctx(void)
  5  {
  6      SSL_CTX *ctx;
  7  
  8      ctx = SSL_CTX_new(SSLv23_method(  ));
  9      if (SSL_CTX_use_certificate_chain_file(ctx, CERTFILE) != 1)
 10          int_error("Error loading certificate from file");
 11      if (SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM) != 1)
 12          int_error("Error loading private key from file");  
 13      return ctx;
 14  }
 15  
 16  int do_client_loop(SSL *ssl)
 17  {
 18      int  err, nwritten;
 19      char buf[80];
 20   
 21      for (;;)
 22      {
 23          if (!fgets(buf, sizeof(buf), stdin))
 24              break;
 25          for (nwritten = 0;  nwritten < sizeof(buf);  nwritten += err)
 26          {
 27              err = SSL_write(ssl, buf + nwritten, strlen(buf) - nwritten);
 28              if (err <= 0)
 29                  return 0;
 30          }
 31      }
 32      return 1;
 33  }
 34   
 35  int main(int argc, char *argv[])
 36  {
 37      BIO     *conn;
 38      SSL     *ssl;
 39      SSL_CTX *ctx;
 40  
 41      init_OpenSSL(  );
 42      seed_prng(  );
 43  
 44      ctx = setup_client_ctx(  );
 45  
 46      conn = BIO_new_connect(SERVER ":" PORT);
 47      if (!conn)
 48          int_error("Error creating connection BIO");
 49   
 50      if (BIO_do_connect(conn) <= 0)
 51          int_error("Error connecting to remote machine");
 52   
 53      if (!(ssl = SSL_new(ctx)))
 54          int_error("Error creating an SSL context");
 55      SSL_set_bio(ssl, conn, conn);
 56      if (SSL_connect(ssl) <= 0)
 57          int_error("Error connecting SSL object");
 58  
 59      fprintf(stderr, "SSL Connection opened\n");
 60      if (do_client_loop(ssl))
 61          SSL_shutdown(ssl);
 62      else
 63          SSL_clear(ssl);
 64      fprintf(stderr, "SSL Connection closed\n");
 65  
 66      SSL_free(ssl);
 67      SSL_CTX_free(ctx);
 68      return 0;
 69  }

In this example, we make a call to the function seed_prng . As its name suggests, this function seeds the OpenSSL PRNG. Its implementation is left out; see Chapter 4 for details on an appropriate implementation. It is very important to maintaining the security of SSL for the PRNG to be properly seeded, so this function should never be left out in real-world applications.

The function setup_client_ctx performs the actions as discussed earlier to provide certificate data to the server properly. In this setup process, we leave out any calls to SSL_CTX_set_default_passwd_cb since the default OpenSSL passphrase callback is acceptable for our purposes. The only other point of interest is the error checking performed. This example prints errors and exits if anything goes wrong; a more robust error handling technique was left out for clarity. The sidebar contains more information about the contents of the file client.pem.

Lines 54-58 create the SSL object and connect it. There are some functions used here that we have not yet discussed. The call to SSL_new creates our SSL object and copies the settings we’ve already placed in the SSL_CTX to the newly created object. At this point, the SSL object is still in a generic state. In other words, it could play the role of the server or the client in an SSL handshake.

Another factor left unspecified is the path of communications for the SSL object. Since SSL objects are flexible in the sense that they can perform SSL functions on top of many different types of I/O methods, we must specify a BIO for our object to use. Line 56 does this through a call to SSL_set_bio . This function is passed our connection BIO twice since SSL objects are robust enough to operate on two one-way I/O types instead of requiring a single full-duplex I/O method. Basically, we must specify the BIO to use for writing separately from the BIO used for reading. In this case, they are the same object, since sockets allow two-way communication.

The last unfamiliar function used here is SSL_connect . This function causes the SSL object to initiate the protocol using the underlying I/O. In other words, it begins the SSL handshake with the application on the other end of the underlying BIO. This function will return an error for problems such as incompatible protocol versions.

The do_client_loop function is almost identical to that of our non-SSL client. We’ve simply changed the parameter to an SSL object instead of a BIO, and the BIO_write becomes an SSL_write. In addition, we’ve added a return value to this function. If no errors occur, we can call SSL_shutdown to stop the SSL connection; otherwise, we call SSL_clear. This is done to force OpenSSL to remove any session with errors from the session cache. We will look at session caching in more detail later in this chapter, but for now, it is worth noting that session caching is effectively disabled in the examples we’ve provided so far.

The last point to make about this example is that we removed the call to BIO_free. This is done because SSL_free automatically frees the SSL object’s underlying BIOs for us.

Example 5-6 has the contents of server1.c, the file containing the implementation of our SSL-enabled server. Again, it isn’t yet secure since it validates nothing about the peer; it simply provides its certificate information to the client.

Example 5-6. server1.c
  1  #include "common.h"
  2   
  3  #define CERTFILE "server.pem"
  4  SSL_CTX *setup_server_ctx(void)
  5  {
  6      SSL_CTX *ctx;
  7  
  8      ctx = SSL_CTX_new(SSLv23_method(  ));
  9      if (SSL_CTX_use_certificate_chain_file(ctx, CERTFILE) != 1)
 10          int_error("Error loading certificate from file");
 11      if (SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM) != 1)
 12          int_error("Error loading private key from file");
 13      return ctx;
 14  }
 15  
 16  int do_server_loop(SSL *ssl)
 17  {
 18      int  err, nread;
 19      char buf[80];
 20   
 21      do
 22      {
 23          for (nread = 0;  nread < sizeof(buf);  nread += err)
 24          {
 25              err = SSL_read(ssl, buf + nread, sizeof(buf) - nread);
 26              if (err <= 0)
 27                  break;
 28          }
 29          fwrite(buf, 1, nread, stdout);
 30      }
 31      while (err > 0);
 32      return (SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN) ? 1 : 0;
 33  }
 34   
 35  void THREAD_CC server_thread(void *arg)
 36  {
 37      SSL *ssl = (SSL *)arg;
 38  
 39  #ifndef WIN32
 40      pthread_detach(pthread_self(  ));
 41  #endif
 42      if (SSL_accept(ssl) <= 0)
 43          int_error("Error accepting SSL connection");
 44      fprintf(stderr, "SSL Connection opened\n");
 45      if (do_server_loop(ssl))
 46          SSL_shutdown(ssl);
 47      else
 48          SSL_clear(ssl);
 49      fprintf(stderr, "SSL Connection closed\n");
 50      SSL_free(ssl);
 51  
 52  ERR_remove_state(0);
 53  
 54  #ifdef WIN32
 55      _endthread(  );
 56  #endif
 57  }
 58   
 59  int main(int argc, char *argv[])
 60  {
 61      BIO         *acc, *client;
 62      SSL         *ssl;
 63      SSL_CTX     *ctx;
 64      THREAD_TYPE tid;
 65   
 66      init_OpenSSL(  );
 67      seed_prng(  );
 68  
 69      ctx = setup_server_ctx(  );
 70  
 71      acc = BIO_new_accept(PORT);
 72      if (!acc)
 73          int_error("Error creating server socket");
 74   
 75      if (BIO_do_accept(acc) <= 0)
 76          int_error("Error binding server socket");
 77   
 78      for (;;)
 79      {
 80          if (BIO_do_accept(acc) <= 0)
 81              int_error("Error accepting connection");
 82   
 83          client = BIO_pop(acc);
 84          if (!(ssl = SSL_new(ctx)))
 85              int_error("Error creating SSL context");
 86  
 87          SSL_set_bio(ssl, client, client);
 88          THREAD_create(tid, server_thread, ssl);
 89      }
 90      
 91      SSL_CTX_free(ctx);
 92      BIO_free(acc);
 93      return 0;
 94  }

After looking at the new client program, the modifications to the server should be clear. Since this is the server side of the SSL negotiation, a different function call is made: SSL_accept . The SSL_accept function initiates communication on the underlying I/O layer to perform the SSL handshake.

In the do_server_loop function, we use a call to SSL_get_shutdown to check into the error status of the SSL object. This essentially allows us to differentiate normal client terminations from actual errors. If the SSL_RECEIVED_SHUTDOWN flag is set, we know the session hasn’t had an error and it’s safe to cache. In other words, we can call SSL_shutdown rather than simply clear the connection. The remainder of the modifications to the server program parallel those made to the client.

In summary, we’ve taken our applications and built them to the point of creating the objects necessary for SSL connections. Each application does provide its certificate data to the peers to which it connects, but they do not verify the certificates they receive. We will build further in the next step and learn how to validate peer certificates.

Step 2: Peer Authentication

The security of an SSL-enabled program is compromised by failure to verify peer certificates properly. In this step, we’ll look into the various API calls that deal with trusted certificates, certificate chain verification, CRL usage, and post-connection verification.

Background

Certificate verification can often be confusing, so we’ll discuss the theory before delving into the details. As we already know, a certificate is a set of credentials that has been cryptographically signed by a CA. Each certificate, including a CA certificate, contains a public key with a private key counterpart held in secret by the certificate owner. Moving forward, the process of signing a certificate involves using the private key of a CA to sign the public key in the new certificate. Thus, it should be clear that the process of verification will involve using the public key in a CA certificate to verify the signature on a certificate.

Aside from using a CA to create an entity certificate, a CA may also sign a certificate and give it permissions, via X.509v3 extensions, to act as a CA itself. Generally, this procedure allows for a CA to permit another certificate to act as a CA for a specialized purpose. Through this mechanism, we become aware of certificate hierarchies, i.e., a certificate tree. Understanding this, we can see that a single entity certificate may have a list of signing certificates leading up to the original, self-signed root certificate. This list of certificates, each signed by the next, is called a certificate chain .

Jumping back to a simple example of a root CA signing a single entity certificate, any party may verify the entity certificate by checking the signature on it, presuming it trusts the root CA. The process of validating an entity certificate is that simple. Extending this to a certificate chain, we must validate each subsequent signature in the list until we reach a trusted CA certificate or until we reach the end of the list. If we hit a CA we trust and the signatures are all valid, our entity certificate is verified; if we find an invalid signature or reach the end of the chain without reaching a trusted certificate, the entity certificate is not verified.

Incorporating trusted certificates

As we previously discussed, verifying a certificate’s authenticity requires that the verifying agent have a list of CAs that it trusts. Therefore, we must provide our application with such a list in order for it to verify the peer. We will start our discussion of peer verification by first concentrating on accomplishing this task.

Loading trusted CA certificates into our application is manifested as additional setup to the SSL_CTX object. The function SSL_CTX_load_verify_locations performs this task. This function will load certificates from files, directories, or both.

int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile,
                                    const char *CApath);
ctx

The SSL context object that trusted CA certificates will be loaded into.

CAfile

The name of a file containing CA certificates in PEM format. More than one CA certificate may be present in the file.

CApath

The name of a directory containing CA certificates. Each file in the directory must contain only a single CA certificate, and the files must be named by the subject name’s hash and an extension of “.0”.

We can call SSL_CTX_load_verify_locations with either the second or the third arguments specified as NULL, but not both. The behavior of this function is to perform the loading for the non-NULL arguments. An important difference between file and directory storage is the time when the certificates get loaded. With a flat file, the file is parsed and certificates loaded during the call to SSL_CTX_load_verify_locations. However, with a directory, certificates are read only when needed, i.e., during the verification phase that occurs during the SSL handshake.

OpenSSL also has default CA certificate locations. When building the library, these paths are hardcoded into the library based on the parameters that are used to build it. In general, there is an OpenSSL directory (commonly /usr/local/openssl on Unix systems). The default certificate file is named cert.pem , and it lives in this OpenSSL directory. Likewise, the default certificate directory is named certs , and it too lives in the OpenSSL directory. These default locations provide a convenient place to store system-wide CA certificates that all of the OpenSSL-based applications running on the machine require. Using the default files, we will not need to keep separate copies of common certificates for each application. The function SSL_CTX_set_default_verify_paths loads these default locations into our SSL_CTX object. For calls to this function, the same rules for determining when the CA certificates actually get loaded apply as with a call to SSL_CTX_load_verify_locations.

Warning

When we load a certificate location into an SSL_CTX object, we are making the statement that we trust those certificates. It is important to understand that if our application runs on a multiuser system, any user with permissions to write to the certificate locations that we load can subvert the security of our application. This is especially important when electing to load the default verify locations. For instance, if our application loads these defaults, a user with the correct permissions could slip in a new CA certificate, thus connecting our application with peers presenting certificates signed by this rogue CA certificate.

Using these two functions, we can load trusted CA certificates into our SSL_CTX. Even though they are loaded, the certificates are still not used to verify the peer. We will explore the details of enabling this in the next section.

Certificate verification

Certificate verification entails checking the cryptographic signatures on a certificate to be sure an entity we trust has signed that certificate. It also involves checking the certificate’s notBefore and notAfter dates, trust settings, purpose, and revocation status. Verification of a certificate takes place during the SSL handshake (during the call to SSL_connect or SSL_accept, depending on whether the SSL object is a client or a server).

Once we’ve properly loaded trusted certificates into the SSL_CTX object, OpenSSL has a built-in function to verify the peer’s certificate chain automatically. The routine used to verify the certificate chain could be changed from the default via a call to SSL_CTX_set_cert_verify_callback , but under almost all circumstances, this is undesirable since the default routine for signature verification is amply complete and robust. Instead, the developer can specify a different callback that filters the return status of the default verification and returns the new verification status. The function to perform this task is SSL_CTX_set_verify .

Aside from assigning a filter verify callback, this function’s primary purpose is to assign the type of verification our SSL_CTX object’s connections will perform. More accurately, we can use it to control how certificates and requests are handled during a handshake. The second argument to this function is a set of flags that determine this. Four flags are defined with names that can be combined with a logical OR operation. Depending on whether the context is being used in client mode or server mode, these flags can have different meanings.

SSL_VERIFY_NONE

When the context is being used in server mode, no request for a certificate will be sent to the client, and the client should not send a certificate. When the context is being used in client mode, any certificate received from the server will be verified, but failure will not terminate the handshake. Do not combine this flag with any others; the others will take precedence over this one. This flag should only be used by itself.

SSL_VERIFY_PEER

When the context is being used in server mode, a request for a certificate will be sent to the client. The client may opt to ignore the request, but if a certificate is sent back, it will be verified. If the verification fails, the handshake will be terminated immediately.

When the context is being used in client mode, if the server sends a certificate, it will be verified. If the verification fails, the handshake will be terminated immediately. The only time that a server would not send a certificate is when an anonymous cipher is in use. Anonymous ciphers are disabled by default. Any other flags combined with this one in client mode are ignored.

SSL_VERIFY_FAIL_IF_NO_PEER_CERT

If the context is not being used in server mode or if SSL_VERIFY_PEER is not set, this flag is ignored. Use of this flag will cause the handshake to terminate immediately if no certificate is provided by the client.

SSL_VERIFY_CLIENT_ONCE

If the context is not being used in server mode or if SSL_VERIFY_PEER is not set, this flag is ignored. Use of this flag will prevent the server from requesting a certificate from the client in the case of a renegotiation. A certificate will still be requested during the initial handshake.

The third argument to SSL_CTX_set_verify is a pointer to the verification filter callback. Since the internal verification routine is called for each level of the peer certificate chain, our filter routine will be called just after each step. This function’s first argument is nonzero if the verification succeeded, and zero otherwise. The second argument is an X509_STORE_CTX object. This type of object contains the information necessary to verify a certificate. The object holds the current certificate being verified and the verification result. The return value from the function should be either zero or nonzero to indicate whether the certificate should be considered valid or not.

As in most of the callbacks used with OpenSSL, there is a default supplied that simply returns the value of the first argument. When implementing our own version of this function, we need to maintain this behavior. If we return nonzero when the first parameter is actually zero, an unverified client certificate will be accepted as a verified one. Likewise, the reverse would cause a valid certificate to fail verification. At this point, it may seem as if there is no real purpose to implementing our own version, but this isn’t accurate. The reason we should supply our own is so that more detailed information about the results of verification can be obtained, especially when verification fails. For instance, if a peer presents an expired certificate and we do not implement a verify callback to check status, then we find out only that the call to SSL_connect or SSL_accept failed because of “handshake failure.” Example 5-7 shows an implementation for this callback that we will use in our example applications. To use it in our examples, it should be implemented in common.c and prototyped in common.h.

Example 5-7. A verify callback (implemented in common.c and prototyped in common.h)
int verify_callback(int ok, X509_STORE_CTX *store)
{
    char data[256];
 
    if (!ok)
    {
        X509 *cert = X509_STORE_CTX_get_current_cert(store);
        int  depth = X509_STORE_CTX_get_error_depth(store);
        int  err = X509_STORE_CTX_get_error(store);
 
        fprintf(stderr, "-Error with certificate at depth: %i\n", depth);
        X509_NAME_oneline(X509_get_issuer_name(cert), data, 256);
        fprintf(stderr, "  issuer   = %s\n", data);
        X509_NAME_oneline(X509_get_subject_name(cert), data, 256);
        fprintf(stderr, "  subject  = %s\n", data);
        fprintf(stderr, "  err %i:%s\n", err, X509_verify_cert_error_string(err));
    }
 
    return ok;
}

This callback employs several functions from the X509 family of functions to report the detailed error information.

The call to SSL_CTX_set_verify is done before any SSL objects are created from the context. We should also make a call to SSL_CTX_set_verify_depth . This function sets the maximum allowable depth for peer certificates. In other words, it limits the number of certificates that we are willing to verify in order to ensure the chain is trusted. For example, if the depth was set to four and six certificates are present in the chain to reach the trusted certificate, the verification would fail because the required depth would be too great. For nearly all applications, the default depth of nine is more than high enough to ensure that the peer certificate will not fail due to too large of a certificate chain. On the other hand, if we know that our application will be used only with peers presenting certificates of some smaller chain length, it is a good idea to set the value to exclude certificates composed of longer chains from being verified successfully. Setting the depth to zero allows chains of unlimited length to be used.

Warning

There is a known security vulnerability in SSL_CTX_set_verify_depth in versions of OpenSSL prior to 0.9.6. The problem stemmed from the fact that the internal verification routine did not properly check extensions on peer certificate chains; it approved certificate chains that contained non-CA certificates as long as they led to a trusted root CA. Thus, using any verification depth greater than one left the application susceptible to attack from anyone signed by the trusted root CA. Since this problem has been fixed in newer versions of OpenSSL by checking the X509v3 fields regarding CA authorization, this vulnerability should be of only academic interest.

Incorporating certificate revocation lists

A large problem with SSL security is the availability and usage of certificate revocation lists. Since certificates can be revoked by the issuing CA, we must somehow account for this in our SSL implementation. To do this, an application must load CRL files in order for the internal verification process to ensure each certificate it verifies is not revoked. Unfortunately, OpenSSL’s CRL functionality is incomplete in Version 0.9.6. The features necessary to utilize CRL information will be complete in new versions starting with 0.9.7.

Because this functionality is not available at the time of writing, CRL usage will not be incorporated in this example. However, we can tell you what you will need to do once newer releases are made. Remember, it is paramount to include CRL checking when verifying certificates. If any new version of OpenSSL is used when building applications, this step is required for security.

The SSL interface itself does not support CRL incorporation directly; instead, we must use the underlying X509 interface.

The function SSL_CTX_get_cert_store retrieves the internal X509_STORE object from the SSL_CTX object. Operations on this store object allow us to perform a variety of tweaks on the verification process. In fact, the functions SSL_CTX_load_verify_locations and SSL_CTX_set_default_paths call functions against this same X509_STORE object to perform their respective operations.

X509_STORE *SSL_CTX_get_cert_store(SSL_CTX *ctx);

All of the details for interacting with certificate stores to set further verification parameters or incorporate CRL data are discussed in Chapter 10 with the verification of certificate chains. We strongly recommend that developers implementing applications consult the verification process in Chapter 10 that uses X509_STORE objects to learn the proper method of SSL certificate verification against CRLs. The process involves adding the CRL files to the X509_STORE via a file lookup method and then setting the store’s flags to check certificates against the CRLs.

Post-connection assertions

Essentially, SSL_CTX_set_verify and SSL_CTX_set_verify_depth are all we need to use in order for OpenSSL to verify the peer certificate chain upon connection. There is more, however. After connecting the SSL object, we need to assert that some assumed properties about the connection are indeed true. OpenSSL provides several functions that allow us to create a post-connection verification routine to make sure that we haven’t been fooled by a malicious peer. This post-connection verification routine is very important because it allows for much finer grained control over the certificate that is presented by the peer, beyond the certificate verification that is required by the SSL protocol proper.

The function SSL_get_peer_certificate will return a pointer to an X509 object that contains the peer’s certificate. While the handshake is complete and, presumably, the verification completed correctly, we must still use this function. Consider the case in which the peer presents no certificate when one is requested but not required. The certificate verification routines—both the built-in and the filter—will not return errors since there was nothing wrong with the NULL certificate. Thus, to prevent this condition, we must call this function and check that the return value is not NULL. If this function returns a non-NULL value, the reference count of the return object is increased. In order to prevent memory leaks, we must call X509_free to decrement the count after we’re done using the object.

Our application will be vulnerable if we do not check the peer certificate beyond verification of the chain. For example, let’s say that we’re making a web browsing application. To keep it simple, we’ll allow just one trusted CA. When we do this, any SSL peer with a certificate signed by the same CA will be verified correctly. This isn’t secure. Nothing prevents an attacker from getting his own certificate signed by the CA and then hijacking all your sessions. We thwart this kind of masquerade by tying the certificate to some piece of information unique to the machine. For purposes of SSL, this piece of information is the entity’s fully qualified domain name (FQDN), also called the DNS name.

The common practice with X.509v1 certificates was to put the FQDN in the certificate’s commonName field of the subjectName field. This practice is no longer recommended for new applications since X.509v3 allows certificate extensions to hold the FQDN as well as other identifying information, such as IP address. The proper place for the FQDN is in the dNSName field of the subjectAltName extension.

We use the function post_connection_check to perform these checks for us. We recommend always checking for the dNSName field first, and if it isn’t present, we can check the commonName field. Checking the commonName field is strictly for backward compatibility, so if this isn’t a concern, it can safely be omitted. Our example function will check for the extension first and then fall back to the commonName. One feature our example does omit is the optional wildcard expansion. RFC 2818 specifies a paradigm for allowing FQDNs in certificates to contain wildcards. Implementing this functionality is simply a text-processing issue and is thus omitted for clarity.

SSL_get_verify_result is another API function that we will employ in our post-connection check. This function returns the error code last generated by the verification routines. If no error has occurred, X509_V_OK is returned. We should call this function and make sure the returned value equals X509_V_OK. When browsing the example application, it is obvious that robust error handling has been left out for clarity. For example, the programs simply exit when an error occurs. In most cases, we will want to do something better to handle errors in some application-specific way. Checking the verify result is always a good idea. It makes an assertion that no matter what error handling occurred up to this point, if the result isn’t OK now, we should disconnect.

Example 5-8 shows a function that performs the checks that we’ve just described. In the example, we’ll check to be sure that the certificate contains the FQDN of the peer to which we expect to be connecting. For the client, we’ll want to make sure that the server presents a certificate that contains the FQDN of the server’s address. Likewise, for the server, we’ll want to make sure that the client presents a certificate that contains the FQDN of the client’s address. In this case, our checking of the client certificate will be very strict because we’ll be expecting the client to be using a specific FQDN, and we’ll allow only that one. For the purposes of our example client and server, this function should appear in common.c and be prototyped in common.h.

Example 5-8. A function to do post-connection assertions (implemented in common.c and prototyped in common.h)
long post_connection_check(SSL *ssl, char *host)
{
    X509      *cert;
    X509_NAME *subj;
    char      data[256];
    int       extcount;
    int       ok = 0;
 
    /* Checking the return from SSL_get_peer_certificate here is not strictly
     * necessary.  With our example programs, it is not possible for it to return
     * NULL.  However, it is good form to check the return since it can return NULL
     * if the examples are modified to enable anonymous ciphers or for the server
     * to not require a client certificate.
     */
    if (!(cert = SSL_get_peer_certificate(ssl)) || !host)
        goto err_occured;
    if ((extcount = X509_get_ext_count(cert)) > 0)
    {
        int i;
 
        for (i = 0;  i < extcount;  i++)
        {
            char              *extstr;
            X509_EXTENSION    *ext;
 
            ext = X509_get_ext(cert, i);
            extstr = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));
 
            if (!strcmp(extstr, "subjectAltName"))
            {
                int                  j;
                unsigned char        *data;
                STACK_OF(CONF_VALUE) *val;
                CONF_VALUE           *nval;
                X509V3_EXT_METHOD    *meth;
 
                if (!(meth = X509V3_EXT_get(ext)))
                    break;
                data = ext->value->data;
 
                val = meth->i2v(meth, 
                                meth->d2i(NULL, &data, ext->value->length),
                                NULL);
                for (j = 0;  j < sk_CONF_VALUE_num(val);  j++)
                {
                    nval = sk_CONF_VALUE_value(val, j);
                    if (!strcmp(nval->name, "DNS") && !strcmp(nval->value, host))
                    {
                        ok = 1;
                        break;
                    }
                }
            }
            if (ok)
                break;
        }
    }
 
    if (!ok && (subj = X509_get_subject_name(cert)) &&
        X509_NAME_get_text_by_NID(subj, NID_commonName, data, 256) > 0)
    {
        data[255] = 0;
        if (strcasecmp(data, host) != 0)
            goto err_occured;
    }
 
    X509_free(cert);
    return SSL_get_verify_result(ssl);
 
err_occured:
    if (cert)
        X509_free(cert);
    return X509_V_ERR_APPLICATION_VERIFICATION;
}

At a high level, the function post_connection_check is implemented as a wrapper around SSL_get_verify_result, which performs our extra peer certificate checks. It uses the reserved error code X509_V_ERR_APPLICATION_VERIFICATION to indicate errors where there is no peer certificate present or the certificate presented does not match the expected FQDN. This function will return an error in the following circumstances:

  • If no peer certificate is found

  • If it is called with a NULL second argument, i.e., if no FQDN is specified to compare against

  • If the dNSName fields found (if any) do not match the host argument and the commonName also doesn’t match the host argument (if found)

  • Any time the SSL_get_verify_result routine returns an error

As long as none of the above errors occurs, the value X509_V_OK will be returned. Our example programs are further extended below to employ this function.

Unfortunately, the code to check the dNSName is not very clear. We use the X509 functions to access the extensions. We then iterate through the extensions and use the extension-specific parsing routines to find all extensions that are subjectAltName fields. Since a subjectAltName field may itself contain several fields, we must then iterate through those fields to find any dNSName fields (they are tagged with the short name DNS). Since it is rather confusing, we will step through this function to explain its behavior. Having a stronger understanding of some of the more advanced programming techniques presented in Chapter 10 will help demystify the implementation of this function.

At the onset, we simply get the peer certificate from the SSL object. If the function X509_get_ext_count returns a positive number, we know there are some X.509v3 extensions present in the peer’s certificate. Given this, we iterate over the extensions to look for a subjectAltName. The function X509_get_ext will retrieve an extension for us based on the counter. We also use the variable extstr to hold the extracted short name of the extension. Unfortunately, we must use three functions to perform this task: the innermost extracts the ASN1_OBJECT, the next fetches the NID, and the outermost function gets the short name as a const char * from the NID.

Next, we check if the extension is what we’re looking for by comparing the short name against the string constant subjectAltName. Once we’re sure it’s the right extension, we need to extract the X509V3_EXT_METHOD object from the extension. This object is a container of extension-specific functions for manipulating the data within the extension. We access the data we wish to manipulate directly through the value member of the X509_EXTENSION structure. The d2i and i2v functions serve the purpose of converting the raw data in the subjectAltName to a stack of CONF_VALUE objects. This is necessary to make it simple to iterate over the several kinds of fields in the subjectAltName so that we may find the dNSName field(s). We check each member of this CONF_VALUE stack to see if we have a match for the host string in a dNSName field. Keep in mind that the dNSName field is named DNS in the extension itself, since it’s referenced by its short name. As soon as we find a match, we stop the iterations over all the extensions.

We only pursue checking the commonName of the certificate if no match is found in a dNSName field. If we fail to find an FQDN that matches the host argument in either the dNSName field or the commonName, we return the code for an application-specific error. In most real-world cases, matching one specific FQDN is not desirable. Most often, a server would have a list (known as a whitelist) that contains all of the acceptable FQDNs for connecting clients. If the client’s certificate contains an FQDN that appears on this list, the certificate is accepted; otherwise, it is rejected.

Further extension of the examples

Employing what we know about verifying the authenticity of the peer, we can extend our example applications to make them one step closer to being secure. For these examples, we’ve added the functions verify_callback and post_connection_check to common.c and their prototypes to common.h.

The code for our revised client application, client2.c, is provided in Example 5-9. The lines that differ from client1.c are marked. Line 3 defines the file we use to store trusted certificates. We define CADIR to be NULL on line 4 since we will use a flat file instead. Nothing prevents us from specifying both a file and a directory; but in this case, we do not need a directory.

Example 5-9. client2.c
  1  #include "common.h"
  2   
  3  #define CAFILE "root.pem"
  4  #define CADIR NULL
  5  #define CERTFILE "client.pem"
  6  SSL_CTX *setup_client_ctx(void)
  7  {
  8      SSL_CTX *ctx;
  9   
 10      ctx = SSL_CTX_new(SSLv23_method(  ));
 11      if (SSL_CTX_load_verify_locations(ctx, CAFILE, CADIR) != 1)
 12          int_error("Error loading CA file and/or directory");
 13      if (SSL_CTX_set_default_verify_paths(ctx) != 1)
 14          int_error("Error loading default CA file and/or directory");
 15      if (SSL_CTX_use_certificate_chain_file(ctx, CERTFILE) != 1)
 16          int_error("Error loading certificate from file");
 17      if (SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM) != 1)
 18          int_error("Error loading private key from file");
 19      SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
 20      SSL_CTX_set_verify_depth(ctx, 4);
 21      return ctx;
 22  }
 23   
 24  int do_client_loop(SSL *ssl)
 25  {
 26      int  err, nwritten;
 27      char buf[80];
 28   
 29      for (;;)
 30      {
 31          if (!fgets(buf, sizeof(buf), stdin))
 32              break;
 33          for (nwritten = 0;  nwritten < sizeof(buf);  nwritten += err)
 34          {
 35              err = SSL_write(ssl, buf + nwritten, strlen(buf) - nwritten);
 36              if (err <= 0)
 37                  return 0;
 38          }
 39      }
 40      return 1;
 41  }
 42   
 43  int main(int argc, char *argv[])
 44  {
 45      BIO     *conn;
 46      SSL     *ssl;
 47      SSL_CTX *ctx;
 48      long    err;
 49  
 50      init_OpenSSL(  );
 51      seed_prng(  );
 52   
 53      ctx = setup_client_ctx(  );
 54   
 55      conn = BIO_new_connect(SERVER ":" PORT);
 56      if (!conn)
 57          int_error("Error creating connection BIO");
 58   
 59      if (BIO_do_connect(conn) <= 0)
 60          int_error("Error connecting to remote machine");
 61   
 62      ssl = SSL_new(ctx);
 63      SSL_set_bio(ssl, conn, conn);
 64      if (SSL_connect(ssl) <= 0)
 65          int_error("Error connecting SSL object");
 66      if ((err = post_connection_check(ssl, SERVER)) != X509_V_OK)
 67      {
 68          fprintf(stderr, "-Error: peer certificate: %s\n",
 69                  X509_verify_cert_error_string(err));
 70          int_error("Error checking SSL object after connection");
 71      }
 72      fprintf(stderr, "SSL Connection opened\n");
 73      if (do_client_loop(ssl))
 74          SSL_shutdown(ssl);
 75      else
 76          SSL_clear(ssl);
 77      fprintf(stderr, "SSL Connection closed\n");
 78   
 79      SSL_free(ssl);
 80      SSL_CTX_free(ctx);
 81      return 0;
 82  }

To load the trusted certificates from root.pem, we call SSL_CTX_load_verify_locations on line 11 and check for errors. Since we trust the users of the system on which this client will run, we also call SSL_CTX_set_default_verify_paths to load the built-in certificate stores. Our example does not explicitly require the loading of the default locations; it is included for illustration. It is good design practice to load these defaults only when the application will run on a trusted system and when the application itself needs to incorporate these extra certificates.

After loading the trusted certificates, we set the verification mode to SSL_VERIFY_PEER and assign the callback (line 19). When implementing SSL clients, the verification mode should always include SSL_VERIFY_PEER. Without this, we could never tell if the server we connect with is properly authenticated. As we discussed in the section above, the verify_callback function simply reports errors in more detail and does not change the behavior of the internal verification process. The following line, line 20, sets the maximum depth for verification to four. For this client example, four levels of verification ought to be plenty because our certificate hierarchy is not too complex. Given the details from the previous sidebar describing our example PKI, the minimum depth we can assign to our client is two, since the server’s certificate is signed by the server CA, which, in turn, is signed by the root CA that we trust.

The last major change to this version of the client is in lines 66-71. These lines use the post_connection_check function that we developed in Example 5-8. This call asserts that the server we are connected with did present a certificate and the certificate it provided has “splat.zork.org” as the FQDN. If any errors occur, we can call X509_verify_cert_error_string to convert our error code into a string to print to the console.

Example 5-10 shows the contents of server2.c, our example server program. The changes made to it are congruent with the changes made to the client application.

Example 5-10. server2.c
  1  #include "common.h"
  2   
  3  #define CAFILE "root.pem"
  4  #define CADIR NULL
  5  #define CERTFILE "server.pem"
  6  SSL_CTX *setup_server_ctx(void)
  7  {
  8      SSL_CTX *ctx;
  9   
 10      ctx = SSL_CTX_new(SSLv23_method(  ));
 11      if (SSL_CTX_load_verify_locations(ctx, CAFILE, CADIR) != 1)
 12          int_error("Error loading CA file and/or directory");
 13      if (SSL_CTX_set_default_verify_paths(ctx) != 1)
 14          int_error("Error loading default CA file and/or directory");
 15      if (SSL_CTX_use_certificate_chain_file(ctx, CERTFILE) != 1)
 16          int_error("Error loading certificate from file");
 17      if (SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM) != 1)
 18          int_error("Error loading private key from file");
 19      SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
 20                         verify_callback);
 21      SSL_CTX_set_verify_depth(ctx, 4);
 22      return ctx;
 23  }
 24   
 25  int do_server_loop(SSL *ssl)
 26  {
 27      int  err, nread;
 28      char buf[80];
 29   
 30      for (;;)
 31      {
 32          for (nread = 0;  nread < sizeof(buf);  nread += err)
 33          {
 34              err = SSL_read(ssl, buf + nread, sizeof(buf) - nread);
 35              if (err <= 0)
 36                  break;
 37          }
 38          fwrite(buf, 1, nread, stdout);
 39      }
 40      return (SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN) ? 1 : 0;
 41  }
 42   
 43  void THREAD_CC server_thread(void *arg)
 44  {
 45      SSL *ssl = (SSL *)arg;
 46  long err;
 47   
 48  #ifndef WIN32
 49      pthread_detach(pthread_self(  ));
 50  #endif
 51      if (SSL_accept(ssl) <= 0)
 52          int_error("Error accepting SSL connection");
 53      if ((err = post_connection_check(ssl, CLIENT)) != X509_V_OK)
 54      {
 55          fprintf(stderr, "-Error: peer certificate: %s\n",
 56                  X509_verify_cert_error_string(err));
 57          int_error("Error checking SSL object after connection");
 58      }
 59      fprintf(stderr, "SSL Connection opened\n");
 60      if (do_server_loop(ssl))
 61          SSL_shutdown(ssl);
 62      else
 63          SSL_clear(ssl);
 64      fprintf(stderr, "SSL Connection closed\n");
 65      SSL_free(ssl);
 66  ERR_remove_state(0)
 67  #ifdef WIN32
 68      _endthread(  );
 69  #endif
 70  }
 71   
 72  int main(int argc, char *argv[])
 73  {
 74      BIO     *acc, *client;
 75      SSL     *ssl;
 76      SSL_CTX *ctx;
 77   THREAD_TYPE tid;
 78  
 79      init_OpenSSL(  );
 80      seed_prng(  );
 81   
 82      ctx = setup_server_ctx(  );
 83   
 84      acc = BIO_new_accept(PORT);
 85      if (!acc)
 86          int_error("Error creating server socket");
 87   
 88      if (BIO_do_accept(acc) <= 0)
 89          int_error("Error binding server socket");
 90   
 91      for (;;)
 92      {
 93          if (BIO_do_accept(acc) <= 0)
 94              int_error("Error accepting connection");
 95   
 96          client = BIO_pop(acc);
 97          if (!(ssl = SSL_new(ctx)))
 98          int_error("Error creating SSL context");
 99          SSL_set_accept_state(ssl);
100          SSL_set_bio(ssl, client, client);
101          THREAD_create(tid, server_thread, ssl);
102      }
103   
104      SSL_CTX_free(ctx);
105      BIO_free(acc);
106      return 0;
107  }

One way the changes to the server stray from the changes to the client is in the verification mode. In server applications, the behavior of SSL_VERIFY_PEER is slightly different; it causes the server to solicit a certificate from the client. The other verification flag used is SSL_VERIFY_FAIL_IF_NO_PEER_CERT. This flag instructs the server to fail on the handshake if the client presents no certificate.

Tip

The choice of whether to require a client to supply a certificate depends on the type of server application you’re building. In many cases, requiring a client certificate may not be strictly necessary, but it’s generally a bad idea to request a certificate without requiring one because it may often cause a client to present the user with a prompt to select the certificate to use. Particularly in a web-based environment, this is not desirable. If you need to require a certificate only for specific commands or options, it’s better to force a renegotiation instead.

The client’s usage of the SERVER-defined value and the server’s usage of CLIENT is an oversimplification. In many cases, especially with that of a server, the string containing the peer’s FQDN will not be so readily available. Instead, we may need to look at the IP address to which we are connected and use it to discover the FQDN. For purposes of clarity, this process was omitted, and the names were hardcoded. Additionally, when verifying peer certificates, the owner will often not be a machine with a FQDN but rather a person, organization, etc. For these cases, it’s important to change the post_connection_check routine accordingly to allow for successful connections.

Using the tricks we learned in this step, we have now developed a viable framework for verifying a peer and assuring authentication is done properly. The majority of the battle to SSL-enable our application is over; however, we still have one more step.

Step 3: SSL Options and Cipher Suites

We have not yet discussed a few important points about SSL connections. For instance, we mentioned that SSLv2 shouldn’t be used, yet our example was created with the SSLv23_method that still allows for this insecure protocol version. Beyond protocol limitation, OpenSSL provides for many workarounds for known bugs in other SSL implementations. While these bugs don’t affect security, our application may lose interoperability if we do not account for them.

Additionally, we need to delve into the selection of cipher suites . A cipher suite is a combination of lower-level algorithms that an SSL connection uses to do authentication, key exchange, and stream encryption. Suite selection is important because OpenSSL supports some algorithms for compatibility that we want to exclude for security reasons. Similarly, some of the cipher suites that are secure require an application to provide callbacks in order to be utilized. Learning how to do these things properly and extending our example for this final step will be the topic of this section.

Setting SSL options

The SSL_CTX_set_options function provides the developer with finer-grained control over the SSL connections spawned from the context. Using this function, we can enable the bug workarounds built into the OpenSSL library. For instance, a particular version of a Netscape product (Netscape-Commerce 1.12) will truncate the material used for key generation. In order for our SSL programs to establish a connection to a peer with such a bug, we need to enable the workaround. These fixes are useful only to programs that will communicate with a peer known to have bugs, but enabling the workarounds does not hurt anything as a rule. These bug fixes can be enabled individually, but instead we should set the SSL_OP_ALL flag, which will enable all of the workaround code.

Like the function SSL_CTX_set_verify, the second parameter to this function is a set of flags. Again, the flags can be combined with the logical OR operation. An important fact about this call is that once an option is set, it can’t be cleared: this function only adds the options presented by the second argument to the options set contained in the SSL_CTX object. The new set of options is returned by this function.

In addition to the workarounds for buggy SSL peers, this function allows us to tighten the security of our SSL connections. By setting the option SSL_OP_NO_SSLv2, we prevent the SSLv2 protocol from being used. As we noted in Step 1, this is a very useful feature. Using this option, we can create an SSL_CTX object based on the compatibility method, SSLv23_method, and the context will not allow SSLv2 peers. This is useful since electing to base our context upon either SSLv3_method or TLSv1_method would prevent the other from connecting correctly.

Two server-side-only options that bear consideration are SSL_OP_EPHEMERAL_RSA and SSL_OP_SINGLE_DH_USE. The former causes our context object to attempt to use a temporary RSA key for the key exchange. The details of this process are discussed below, but generally, this option should never be used, since it violates the SSL/TLS protocol specification. We discuss the SSL_OP_SINGLE_DH_USE flag in the next section.

Ephemeral keying

In our examples thus far, both the server and the client certificates have been based on RSA key pairs. Because the RSA algorithm can be used for most signing and encrypting, SSL uses it to perform the key agreement necessary to create the shared key under which the data in the stream is encrypted. This technique, such as when the key exchange is conducted through a persistent key, is called static keying . Building on this definition, ephemeral keying is defined as key exchange through a temporary key. At first, it may seem that temporary keys may not allow for proper authentication—not true. Generally, with ephemeral keying, the authentication is accomplished through signature verification with persistent keys, and the temporary keys are used only for key agreement. There are two main advantages of ephemeral keying over static keying from a security perspective.

We’ve said that our example uses RSA keys in the certificates, but consider a case in which the certificates are based upon DSA keys. The DSA algorithm provides a mechanism for signing but not for encrypting. Thus, having only DSA keys on either side of an SSL connection leaves the protocol unable to perform key exchange. It follows that static keying is not even an option for DSA-based certificates; we must supplement them with ephemeral keys.

The second advantage of using temporary keys is that they provide forward secrecy . At a high level, forward secrecy means that if the private key is obtained by a third party, that third party will not be able to decode previous sessions conducted with that key or even future sessions conducted by someone else with the compromised key. This is due to the ephemeral keys used for the secrecy of the sessions. When using static keys, there is no forward secrecy since the secrecy of the key exchange, and hence the following transmission stream, is based on the private key. With an ephemeral key, the data on which the key exchange was performed no longer exists after the key exchange, and thus the session remains private regardless of private key compromise. Thus, forward secrecy means that private key compromise will only allow an attacker to masquerade as the key owner, but not access the key owner’s private data.

These two points make the benefits of using ephemeral keying clear. In the case of DSA certificates, it’s necessary for the protocol to succeed, and in the case of RSA certificates, it affords us forward secrecy. In terms of SSL, using ephemeral keys essentially mandates that the keys embedded in the certificates shall be used only for signatures and not for encryption. Understanding this, it seems as though we’ve left a hole in the protocol since we do not have a method for key exchange. OpenSSL provides two options: temporary RSA keys or Diffie-Hellman (DH) key agreement. Of these two choices, DH is better because temporary RSA keys violate the SSL/TLS protocols. The RSA keying was originally implemented to make sure export restrictions on cryptography were not violated.[1] Today, this issue is not a primary concern; thus, ephemeral RSA keys tend not to be used. Additionally, generation of these temporary RSA keys is much slower than using DH, presuming the DH parameters are pre-generated.

In order to allow OpenSSL to use ephemeral Diffie-Hellman (EDH), we must set up the server-side SSL_CTX object properly. Providing DH parameters directly or, alternatively, a callback that returns the DH parameters accomplishes this goal. The function SSL_CTX_set_tmp_dh sets the DH parameters for a context, while the function SSL_CTX_set_tmp_dh_callback sets the callback. Since the callback mechanism subsumes the functionality of the former method, applications should provide only the callback. The callback function has the following signature:

DH *tmp_dh_callback(SSL *ssl, int is_export, int keylength);

The first argument to the callback is the SSL object representing the connection on which the parameters will be used. The second argument indicates whether an export-restricted cipher is being used; the argument value is nonzero, and zero otherwise. The main advantage of the callback is its ability to provide different functionality based on the third parameter, the key size. The DH parameters returned should have a key size equal to the last argument’s value. Below, our server application is extended with a fully functional callback. The server application in its final form shows an implementation of this callback.

We’ve deferred discussion of the SSL option SSL_OP_SINGLE_DH_USE to this point. Some of the details from Chapter 8 will be helpful in understanding the impact of this option. Essentially, DH parameters are public information. A private key is generated and used for the key exchange from these parameters. Setting this option causes the server to generate a new private key for each new connection. In the end, setting this option provides better security at the cost of more computational power to make new connections. Unless there are special processor usage considerations, we should enable this option.

Cipher suite selection

A cipher suite is a set of algorithms that SSL uses to secure a connection. In order to make a suite, we need to provide algorithms for four functions: signing/authentication, key exchange, cryptographic hashing, and encrypting/decrypting. Keep in mind that some algorithms can serve multiple purposes. For example, RSA can be used for signing and for key exchange.

OpenSSL implements a variety of algorithms and cipher suites when it comes to SSL connections. When designing secure applications, it is essential that algorithms having known security vulnerabilities not be allowed.

The SSL_CTX_set_cipher_list function allows us to set the list of cipher suites that we authorize our SSL objects to use. The list of ciphers is specified by a specially formatted string. This string is a colon-delimited list of algorithms. Given the number of possible combinations, specifying all the acceptable ones explicitly would be quite cumbersome. OpenSSL allows for several keywords in the list, which are shortcuts for sets of ciphers. For instance, “ALL” is a shortcut for every available combination. Additionally, we can precede a keyword with the “!” operator to remove all ciphers associated with the keyword from the list. Using this, we will create a string to define our custom cipher list. There are other operators such as “+” or “-”, but they are not essential for specifying a secure list. For applications that need a custom definition, the ciphers manpage is a good reference on string formation.

SSL allows the use of anonymous ciphers . Anonymous ciphers allow the SSL connection to succeed without proper authentication of the peer by using the DH algorithm. In almost all circumstances, we want to block these ciphers; they are identified by the "ADH” keyword. In addition to suites that do not allow us to authenticate properly, we want to block low-security algorithms. The "LOW” keyword refers to ciphers that use a key of 64 bits or 56 bits without export crippling. Accordingly, the "EXP” keyword marks the ciphers that are export-crippled to 56 or 40 bits. Finally, we should block algorithms that have known weaknesses, e.g., "MD5”.

We can also use the special keyword " @STRENGTH”. Using this indicates that the list of cipher suites should be sorted by their strength (their key size) in order of highest to lowest. Employing this keyword causes our SSL connections to attempt to select the most secure suite possible, and if necessary, back off to the next most secure, and so on down the list. This keyword should be specified last on the list.

The final product

Using our knowledge of SSL options, ephemeral keying, and cipher suite selection, we will implement the last step necessary to make our examples fully SSL-enabled, secure applications. After looking at the code for the client and server, we will discuss some of the simplifications that our applications employ.

Example 5-11 contains the code for client3.c, the final client application. Because the only changes we’re making are to the setup_client_ctx function, we truncated the example to only the contents of the source file up to and including that function.

Example 5-11. client3.c
  1  include "common.h"
  2   
  3  #define CIPHER_LIST "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"
  4  #define CAFILE "root.pem"
  5  #define CADIR NULL
  6  #define CERTFILE "client.pem"
  7  SSL_CTX *setup_client_ctx(void)
  8  {
  9      SSL_CTX *ctx;
 10   
 11      ctx = SSL_CTX_new(SSLv23_method(  ));
 12      if (SSL_CTX_load_verify_locations(ctx, CAFILE, CADIR) != 1)
 13          int_error("Error loading CA file and/or directory");
 14      if (SSL_CTX_set_default_verify_paths(ctx) != 1)
 15          int_error("Error loading default CA file and/or directory");
 16      if (SSL_CTX_use_certificate_chain_file(ctx, CERTFILE) != 1)
 17          int_error("Error loading certificate from file");
 18      if (SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM) != 1)
 19          int_error("Error loading private key from file");
 20      SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
 21      SSL_CTX_set_verify_depth(ctx, 4);
 22      SSL_CTX_set_options(ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2);
 23      if (SSL_CTX_set_cipher_list(ctx, CIPHER_LIST) != 1)
 24          int_error("Error setting cipher list (no valid ciphers)");
 25      return ctx;
 26  }

Line 3 contains a definition of the cipher list we discussed in the previous section. Translated to plain terms, the list is composed of all cipher suites in order of strength except those containing anonymous DH ciphers, low bit-size ciphers, export-crippled ciphers, or the MD5 hash algorithm.

As discussed at the beginning of this step, we enable all bug workarounds and disable SSL Version 2 with the call on line 22. Finally, lines 23-24 actually load our cipher list into the SSL_CTX object.

The server application appears in Example 5-12. We’ve made significantly more changes than in the client, but most of our changes are the addition of new functions that are used only by the SSL context setup function. We’ve similarly truncated the source listing for the server example.

Example 5-12. server3.c
  1  #include "common.h"
  2   
  3  DH *dh512 = NULL;
  4  DH *dh1024 = NULL;
  5  
  6  void init_dhparams(void)
  7  {
  8      BIO *bio;
  9  
 10      bio = BIO_new_file("dh512.pem", "r");
 11      if (!bio)
 12          int_error("Error opening file dh512.pem");
 13      dh512 = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
 14      if (!dh512)
 15          int_error("Error reading DH parameters from dh512.pem");
 16      BIO_free(bio);
 17  
 18      bio = BIO_new_file("dh1024.pem", "r");
 19      if (!bio)
 20          int_error("Error opening file dh1024.pem");
 21      dh1024 = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
 22      if (!dh1024)
 23          int_error("Error reading DH parameters from dh1024.pem");
 24      BIO_free(bio);
 25  }
 26  
 27  DH *tmp_dh_callback(SSL *ssl, int is_export, int keylength)
 28  {
 29      DH *ret;
 30  
 31      if (!dh512 || !dh1024)
 32          init_dhparams(  );
 33  
 34      switch (keylength)
 35      {
 36          case 512:
 37              ret = dh512;
 38              break;
 39          case 1024:
 40          default: /* generating DH params is too costly to do on the fly */
 41              ret = dh1024;
 42              break;
 43      }
 44      return ret;
 45  }
 46  
 47  #define CIPHER_LIST "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"
 48  #define CAFILE "root.pem"
 49  #define CADIR NULL
 50  #define CERTFILE "server.pem"
 51  SSL_CTX *setup_server_ctx(void)
 52  {
 53      SSL_CTX *ctx;
 54   
 55      ctx = SSL_CTX_new(SSLv23_method(  ));
 56      if (SSL_CTX_load_verify_locations(ctx, CAFILE, CADIR) != 1)
 57          int_error("Error loading CA file and/or directory");
 58      if (SSL_CTX_set_default_verify_paths(ctx) != 1)
 59          int_error("Error loading default CA file and/or directory");
 60      if (SSL_CTX_use_certificate_chain_file(ctx, CERTFILE) != 1)
 61          int_error("Error loading certificate from file");
 62      if (SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM) != 1)
 63          int_error("Error loading private key from file");
 64      SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
 65                         verify_callback);
 66      SSL_CTX_set_verify_depth(ctx, 4);
 67      SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 |
 68                               SSL_OP_SINGLE_DH_USE);
 69      SSL_CTX_set_tmp_dh_callback(ctx, tmp_dh_callback);
 70      if (SSL_CTX_set_cipher_list(ctx, CIPHER_LIST) != 1)
 71          int_error("Error setting cipher list (no valid ciphers)");
 72      return ctx;
 73  }

The most obvious change to this file is the addition of the functions init_dhparams and tmp_dh_callback . The initializing function reads the DH parameters from the files dh512.pem and dh1024.pem and loads them into the global parameters. The callback function simply switches on the required key size and returns either a 512-bit DH parameter set or a 1,024-bit set. This function intentionally does not try to perform any on-the-fly generation of parameters because it is simply too computationally expensive to be worthwhile.

The only other changes to the server not done to the client, aside from the call on line 60 to set the callback, is the inclusion of the SSL option SSL_OP_SINGLE_DH_USE . As discussed earlier, this causes the private part of the DH key exchange to be recomputed for each client connecting.

Beyond the example

For purposes of clarity in the example code, we have avoided several serious considerations for real applications. The most obvious is error handling. Our examples simply exit if errors of any kind occur. For most applications, extension of the example to more robustly handle errors is application-specific. While specific functions may return different error codes, OpenSSL functions and macros will generally return 1 on success; thus, implementing better error handling should be straightforward.

In addition, the examples we’ve built do two-way authentication. When making a new client and server application, this should always be done. However, when making applications that are meant to connect with other SSL peers, such as a web server, we should take into account that the stringent security requirements of our example are not always desirable. For instance, to entirely remove client authentication from a server, we simply need to remove the calls that load the verify locations, the call to set the verify mode, and the call to the post-connection verification routine. This isn’t the best approach, though. When making such compatibility-first applications, we need to try to incorporate as much security as possible. For instance, instead of removing all of the verification calls, we could still load the verification locations and request a peer certificate with the SSL option SSL_VERIFY_PEER . We can, however, omit the SSL_VERIFY_FAIL_IF_NO_PEER_CERT option and modify the post-connection verification so that if the client does present a certificate, we go on with high security. If the client does not present the server with a certificate, the condition and associated information can be logged so that we can keep track of unauthenticated connections.

Another point we avoided was the password callback for the encrypted private key for the certificate. For most server applications, common practice is to leave the private key in a file that isn’t encrypted so that the application can start up and stop without user input. This convention, born of simplicity of implementation, can easily be subverted. When doing something like this, it is essential that we make sure users on the machine do not have read permission on the private key file; ideally, the file will also be owned by the root or administrator user so that the likelihood of compromise is further reduced. For most client applications, tying the encryption passphrase to the user should not be a problem.

DSA parameters can be converted to DH parameters. This method is often utilized since the computational power necessary to generate the DSA parameters is smaller. Often, parameters generated in this fashion are used for ephemeral DH parameters. Without having to get into the mathematics behind the algorithms, the SSL option SSL_OP_SINGLE_DH_USE should always be used in these cases. Without it, applications are susceptible to a subtle attack.

A large flaw of our example programs is their handling of I/O on SSL connections. The examples rely on blocking I/O operations. For most real applications, this is unacceptable. In the following section, we broach the important topic of non-blocking I/O on SSL connections. We have also neglected to consider renegotiations (requesting that a handshake be performed during an already established connection) on SSL connections and their impact on I/O. While this should occur automatically when the peer requests it, the I/O routines must be robust enough to handle its subtle impacts. In the next section, we begin by addressing server efficiency with respect to session caching and then move into a more in-depth look at I/O paradigms.

Advanced Programming with SSL

OpenSSL provides many more routines than those we’ve discussed up to this point. In fact, most of the SSL_CTX routines have SSL counterparts that perform the same function except on an SSL object instead of the context that creates it. Aside from this small point, we will discuss techniques for caching SSL sessions, using renegotiations, and properly reading and writing on SSL connections—including during renegotiation.

SSL Session Caching

An SSL session is different from an SSL connection. A session refers to the set of parameters and encryption keys created by performing a handshake, whereas a connection is an active conversation that uses a session. In other words, the connection refers to the process of communication, and the session refers to the parameters by which that communication was set up. Understanding this, we can delve into some of the OpenSSL routines that provide for SSL session caching.

Since the majority of the computational expense associated with an SSL server is in setting up connections with clients and not in maintaining them, session caching can be a lifesaver when it comes to server load reduction. OpenSSL implements sessions as SSL_SESSION objects. While the majority of the work in implementing session caching falls on the server side, the client must also keep its previous sessions in order for the efficiency benefit to be realized.

From the server’s perspective, once it has established a connection, it merely needs to label the data and cache the session. This label, called the session ID context , is described in Section 5.2.1.2. After establishing a session, the server assigns a timeout value to it, which is simply a time after which the session will be discarded and another will need to be negotiated. The default behavior of a session caching server is to flush expired sessions automatically. From the client side, an SSL_SESSION object is received from the server. The client can then save this session and reuse it if it needs to make another connection. For instance, a web browser may need to make several connections to display all of the information presented from an SSL-enabled web server. By keeping this session information around, the client connects quickly, and the load on the server is reduced, since it need not negotiate a new session for each connection.

As we’ve said, the server determines the valid session ID context. When a client attempts to connect to a caching server, it has only one attempt at presenting the correct value. If incorrect, a normal handshake occurs, and a new session is created. If the client saves this newly created session, it will then have the correct session ID context for future connections.

The details associated with sessions, such as enabling a client and server, dealing with timeouts, and flushing old sessions, are discussed in the following sections. In addition, we will also outline a mechanism for server-side, on-disk storage of sessions.

Client-side SSL sessions

When a connection is established, the function SSL_get_session returns the SSL_SESSION object representing the parameters used on the SSL connection. This function returns NULL if there is no session established on the SSL connection; otherwise, it returns the object. Actually, this function is called as either SSL_get0_session or SSL_get1_session . These two variants are used to ensure that the reference counting on the SSL_SESSION object is updated correctly. The former does not change the reference count, and the latter increments it. In general, we want to use the latter function since SSL_SESSION objects can timeout asynchronously. If this occurs, our object disappears and we are holding an invalid reference to it. Using SSL_get1_session requires that we call SSL_SESSION_free on the object when we’re done using it, in order to prevent memory leaks.

After saving a reference to the SSL_SESSION object, we can close down the SSL connection and its underlying transport (normally a socket). For most clients, there will not be a large number of SSL sessions established at one time, so caching them in memory is adequate. If this isn’t the case, we can always write the sessions out to disk using PEM_write_bio_SSL_SESSION or PEM_write_SSL_SESSION , and reread them later using PEM_read_bio_SSL_SESSION or PEM_read_SSL_SESSION . The syntax of these functions is the same as the functions used for reading and writing public-key objects. They are discussed in Chapter 8 in Section 8.6.2.

To reuse a saved session, we need to call SSL_set_session before calling SSL_connect. The reference count of the SSL_SESSION object will be incremented automatically so we should follow the call with SSL_SESSION_free. After reusing a session, it is a good idea to call SSL_get1_session before disconnecting to replace the SSL_SESSION object that we’ve cached. The reason is that renegotiations may occur during the connection. Renegotiations cause the creation of a new SSL_SESSION, so we should keep only the most recent (renegotiation is discussed in more detail later).

Now that we understand the basics of enabling session caching, we’ll take a brief look at incorporating it into a client application. Example 5-13 shows pseudocode for our client-side session caching. Now that we’ve established an implementation for a client, we’ll elaborate upon some of the details of session caching as we explore the implementation necessary for server-side caching.

Example 5-13. Pseudocode for client-side caching
ssl = SSL_new(ctx)
... setup underlying communications layer for ssl ...
... connect to host:port ...
if (saved session for host:port in cache)
  SSL_set_session(ssl, saved session)
  SSL_SESSION_free(saved session)
SSL_connect(ssl)
call post_connection_check(ssl, host) and check return value
... normal application code here ...
saved session = SSL_get1_session(ssl)
if (saved session != NULL)
  enter saved session into cache under host:port
SSL_shutdown(ssl)
SSL_free(ssl)

Server-side SSL sessions

All sessions must have a session ID context. For the server, session caching is disabled by default unless a call to SSL_CTX_set_session_id_context is made. The purpose of the session ID context is to make sure the session is being reused for the same purpose for which it was created. For instance, a session created for an SSL web server should not be automatically allowed for an SSL FTP server. Along the same lines, we can use session ID contexts to exercise a finer-grained control over sessions within our application. For example, authenticated clients could have a different session ID context than unauthenticated ones. The context itself can be any data we choose. We set the context through a call to the above function, passing in our context data as the second argument and its length as the third.

After setting the session ID context, session caching is enabled on the server side; however, it isn’t configured completely yet. Sessions have a limited lifetime. The default value for session timeout with OpenSSL is 300 seconds. If we want to change this lifetime, a call to SSL_CTX_set_timeout is necessary. Although the default server automatically flushes expired sessions, we may still want to call SSL_CTX_flush_sessions manually, e.g., when we disable automatic session flushing.

One important function that allows us to tweak the behavior of a server with respect to caching is SSL_CTX_set_session_cache_mode . Like several other mode-setting calls in OpenSSL, the mode is set using flags that are joined by a logical OR operation. One such flag is SSL_SESS_CACHE_NO_AUTO_CLEAR. This disables the automatic flushing of expired sessions. It is useful for servers with tighter processor usage constraints. The automatic behavior can cause unexpected delays; thus, disabling it and manually calling the flush routine when free cycles are available can improve performance. Another flag that can be set is SSL_SESS_CACHE_NO_INTERNAL_LOOKUP. Up to this point, we’ve relied solely on the internal lookup, but in the next section, we’ll outline an on-disk caching method that can replace the internal lookup methods.

Session caching adds subtleties when we also use connection renegotiations. Before implementing a caching server, we should be aware of the potential pitfalls. The section below on renegotiation explores some of these problems after explaining a little more about what renegotiation entails.

An on-disk, session caching framework

OpenSSL’s session caching features include API calls to set three callbacks for synchronization of sessions with external caches. Like other OpenSSL callbacks, three functions are used to set a pointer to the callback function. For each one, the first argument is an SSL_CTX object, and the second argument is a pointer to the callback function.

The callback set by SSL_CTX_sess_set_new_cb is invoked whenever a new SSL_SESSION is created by the SSL_CTX object. This callback enables us to add the new session to our external container. If the callback returns zero, the session object will not be cached. A nonzero return allows the session to be cached.

int new_session_cb(SSL *ctx, SSL_SESSION *session);
ctx

The SSL connection’s connection object.

session

A newly created session object.

The callback set by SSL_CTX_sess_set_remove_cb is invoked whenever an SSL_SESSION is destroyed. It is called just before the session object is destroyed because it is invalid or has expired.

void remove_session_cb(SSL_CTX *ctx, SSL_SESSION *session);
ctx

The SSL_CTX object that is destroying the session.

session

The session object that is about to be destroyed because it’s invalid or expiring.

The SSL_CTX_sess_set_get_cb function is used to set a cache retrieval callback. The assigned function is called whenever the internal cache cannot find a hit for the requested session resumption. In other words, this callback should query our external cache in hopes of finding a match.

SSL_SESSION *get_session_cb(SSL *ctx, unsigned char *id, int len, int *ref);
ctx

The SSL connection’s connection object.

id

The session ID that’s being requested by the peer. It should be noted that the session ID is distinctly different from the session ID context. The context is an application specific classification on session groups, whereas the session ID is an identifier for a peer.

len

The length of the session ID. Since the session ID can be any arbitrary string of characters, it may not necessarily be NULL terminated. Thus, the length of the session ID must also be specified.

ref

An output from the callback. It is used to allow the callback to specify whether the reference count on the returned session object should be incremented or not. It returns as nonzero if the object’s reference count should be incremented; otherwise, zero is returned.

Some of the features required by the caching mechanism in Example 5-14 are easily implemented. Since we are using files to store the sessions, we can use the filesystem’s built-in locking mechanisms. In order to write the keys to disk, we can use the macro PEM_write_bio_SSL_SESSION, but it doesn’t allow for encryption. Remember, SSL_SESSION objects hold the shared secrets that were negotiated; thus, their contents shoud be protected when serialized. Instead, we can call the underlying function PEM_ASN1_write_bio directly. Alternatively, it may be sufficient for some applications to simply use a secure directory and write the sessions unencrypted. In general, it is far safer to use encryption with an in-memory key.

Example 5-14. A framework for external session caching
new_session_cb(  )
{
  acquire a lock for the file named according to the session id
  open file named according to session id
  encrypt and write the SSL_SESSION object to the file
  release the lock
}

remove_session_cb(  )
{
  acquire a lock for the file named according to the session id
  remove the file named according to the session id
  release the lock
}

get_session_cb(  )
{
  acquire a lock for the file named according to the session id in the 2nd arg
  read and decrypt contents of file and create a new SSL_SESSION object
  release the lock
  set the integer referenced by the fourth parameter to 0
  return the new session object
}

This framework, when implemented, provides a powerful mechanism for session caching. By using the filesystem, the cache isn’t constrained by memory restrictions. Additionally, it is easier to use than an in-memory caching scheme since the on-disk scheme allows multiple processes to access the session cache.

I/O on SSL Connections

The motivation behind creating an SSL connection is to transfer data back and forth securely. Thus far, we’ve concentrated on making this connection very secure, but we’ve avoided the details on how data is transferred. In the earlier examples, we cheated on the I/O processing, since the client only writes to the SSL connection, and the server only reads from it. Most real-world applications do not operate on such a simple model. Since the calls we used to perform the reading and writing (SSL_read and SSL_write) closely mirror the system calls read and write, it seems as though I/O would not be significantly different from I/O in non-SSL applications. Unfortunately, this is far from the truth.

There are many subtleties in performing I/O correctly with OpenSSL. We will first look at SSL_read and SSL_write in more detail. Once we understand them, we will get into the detail of the differences between performing blocking and non-blocking I/O on SSL connections. By the end of this chapter, we will have built up our knowledge of the pitfalls with I/O in OpenSSL. This will enable us to avoid them when implementing real-world applications that require I/O paradigms that are more complex than the ones provided in our examples.

Reading and writing functions

In the examples, we used the I/O functions SSL_read and SSL_write for SSL connections, but we didn’t discuss them in any detail. The arguments for these calls are similar to the system calls read and write. The way these calls differ from their system call counterparts is in their return values. Table 5-2 provides the details of the possible return values.

Table 5-2. Return values of SSL_read and SSL_write

Return value

Description

> 0

Success. The data requested was read or written, and the return value is the number of bytes.

0

Failure. Either an error with the SSL connection occurred or the call failed because the underlying I/O could not perform the operation at the time of calling.

< 0

Failure. Either an error with the SSL connection occurred or the application is required to perform a different function before retrying the failed call.

Knowing these return values helps, but we still can’t tell if an error actually occured without some more information. OpenSSL provides the function SSL_get_error , which accepts the return value of an SSL I/O function and returns different values based on what actually happened.

This function inspects the I/O routine’s return value, the SSL object, and the current thread’s error queue to determine what the effect of the call was. Because the internal errors for OpenSSL are stored on a per-thread basis, the call to check the errors must be made from the same thread as the I/O call. Robust applications should always check the detailed error status of all I/O operations with SSL_get_error. Remember that functions like SSL_connect and SSL_accept are also I/O operations.

The return value of SSL_get_error can be many different values. Our application needs to be able to handle a subset of these conditions and provide a reasonable default behavior of shutting down the SSL connection. Table 5-3 provides the descriptions of the values we should always be able to handle.

Table 5-3. Some common return values of SSL_get_error

Return value

Description

SSL_ERROR_NONE

No error occurred with the I/O call.

SSL_ERROR_ZERO_RETURN

The operation failed due to the SSL session being closed. The underlying connection medium may still be open.

SSL_ERROR_WANT_READ

The operation couldn’t be completed. The underlying medium could not fulfill the read requested. This error code means the call should be retried.

SSL_ERROR_WANT_WRITE

The operation couldn’t be completed. The underlying medium could not fulfill the write requested. This error code means the call should be retried.

The handling of the first error code in the table, SSL_ERROR_NONE, is straightforward. SSL_ERROR_ZERO_RETURN should be handled in an application-specific way, knowing that the SSL connection has been closed. In order to handle SSL_ERROR_WANT_READ and SSL_ERROR_WANT_WRITE, we need to retry the I/O operation; this is discussed in more detail below. Any of the other possible return values of SSL_get_error should be considered errors.

In order to implement a call to an I/O function correctly, we should check for these different return values. A sample piece of code is given in Example 5-15. The handlers for SSL_ERROR_WANT_READ and SSL_ERROR_WANT_WRITE have been omitted; the details on handling them properly are discussed below, since the correct actions vary based on whether the application is using blocking or non-blocking I/O. This example’s purpose is to provide a template of what a robust I/O call should look like. Using the switch statement, we handle all the conditions for which we have interest and error on any others. As stated above, there are other return values possible, and we may wish to handle them with specific error messages, for instance.

Example 5-15. A sample I/O call template
code = SSL_read(ssl, buf + offset, size - offset);
switch (SSL_get_error(ssl, code))
{
    case SSL_ERROR_NONE:
        /* update the offset value */
        offset += code;
        break;
    case SSL_ERROR_ZERO_RETURN:
        /* react to the SSL connection being closed */
        do_cleanup(ssl);
        break;
    case SSL_ERROR_WANT_READ:
        /* handle this in an application specific way to retry the SSL_read */
        break;
    case SSL_ERROR_WANT_WRITE:
        /* handle this in an application specific way to retry the SSL_read */
        break;
    default:
        /* an error occurred. shutdown the connection */
        shutdown_connection(ssl);
}

Typically, using blocking I/O alleviates the complications of retrying failed calls. With SSL, this is not the case. There are times when a call to SSL_read or SSL_write on a blocking connection requires a retry.

Blocking I/O

Blocking I/O means that an operation will wait until it can be completed or until an error occurs. Thus, with blocking I/O in general, we shouldn’t have to retry an I/O call, since a single call’s failure indicates an error with the communication channel, such that the channel is no longer usable; with blocking I/O using OpenSSL, this is not true. The SSL connection is based upon an underlying I/O layer that handles the actual transfer of data from one side to the other. The blocking property of an SSL connection is based solely on the underlying communication layer. For instance, if we create an SSL connection from a socket, the SSL connection will be blocking if and only if the socket is blocking. It is also possible to change this property in a connection we’ve already established; we change the property on the underlying layer and then handle all SSL I/O functions appropriate for the new paradigm.

Conceptually, SSL_read and SSL_write read and write data from the peer. Actually, a call to SSL_read may write data to the underlying I/O layer, and a call to SSL_write may read. This usually occurs during a renegotiation. Since a renegotiation may occur at any time, this behavior can cause unexpected results when using a blocking I/O layer; namely, an I/O call may require a retry. Thus, our implementation must handle this.

In order to handle a blocking call correctly, we need to retry the SSL I/O operation if we receive SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE . Don’t let the names of these errors confuse you. Even though they tell us the SSL connection needs to wait until we can read or write, respectively, we just need to retry whatever operation caused the condition. For instance, if an SSL_read caused the SSL_ERROR_WANT_WRITE error, we must retry the SSL_read rather than making the call to SSL_write. It is worth taking a moment to understand the potential errors for a single I/O call. Though nonintuitive, a call to SSL_read may indeed return the error SSL_ERROR_WANT_WRITE due to the possibility of renegotiations at any point.

In many ways, implementing a blocking call is similar to implementing a non-blocking one. It’s similar in the sense that we must loop for retries, but it differs in that we don’t need to check for I/O availability on the underlying layer, as with poll or select. In the end, we will not show an example of looping to accomplish the blocking call; there is an easier way.

Using the SSL_CTX_set_mode function or its counterpart for SSL objects, SSL_set_mode, we can set some I/O behaviors of SSL connections. The second parameter is a set of defined properties joined with the logical OR operator. SSL_MODE_AUTO_RETRY is one such mode. Setting this on a blocking SSL object (or on the context that will create a object) will cause all I/O operations to automatically retry all reads and complete all negotiations before returning.

Using this option allows us to implement I/O as simply as normal blocking I/O with the system calls read and write. In general, we should set this option on the SSL_CTX object before creating the SSL object. We can set the option later on the SSL object itself, but it’s best to do so before any calls to I/O routines on that object.

If we elect not to use this option and instead implement our blocking I/O with our own loops, we might fall into a few traps. This is due to some special requirements for function call retries, which are detailed with our discussion of non-blocking I/O.

Non-blocking I/O

This paradigm causes all of our I/O calls never to block. If the underlying layer is unable to handle a request, it reports its requirement immediately, without waiting. As we’ve hinted, this adds complexity to our I/O routines.

A non-blocking SSL I/O call returns the reason of failure, but only the application can check to see if that status has been cleared. This is the source of the complexity in implementation. For instance, a call to SSL_read may return SSL_ERROR_WANT_READ, which tells the application that once the underlying I/O layer is ready to fulfill a read request, the SSL call may be retried. Generally, the application’s I/O loop will need to serve both read and write requests, however. The problem we need to solve in the I/O loop is that once we’ve made a call to an SSL I/O function, and it requires a retry, we should not call other I/O functions until the original call has succeeded.

Since the logic for correctly implementing I/O routines for the application can have several subtleties, especially with multiple input and output sources, we’ll look at a detailed example. Example 5-16 provides the code for the function data_transfer . This function takes two SSL objects as parameters, which are expected to have connections to two different peers. The data_transfer function will read data from one connection (A) and write it to the other (B) and at the same time read data from B and write it to A.

Example 5-16. A sample non-blocking I/O loop
  1  #include <openssl/ssl.h>
  2  #include <openssl/err.h>
  3  #include <string.h>
  4   
  5  #define BUF_SIZE 80
  6   
  7  void data_transfer(SSL *A, SSL *B)
  8  {
  9      /* the buffers and the size variables */
 10      unsigned char A2B[BUF_SIZE];
 11      unsigned char B2A[BUF_SIZE];
 12      unsigned int A2B_len = 0;
 13      unsigned int B2A_len = 0;
 14      /* flags to mark that we have some data to write */
 15      unsigned int have_data_A2B = 0;
 16      unsigned int have_data_B2A = 0;
 17      /* flags set by check_availability(  ) that poll for I/O status */
 18      unsigned int can_read_A = 0;
 19      unsigned int can_read_B = 0;
 20      unsigned int can_write_A = 0;
 21      unsigned int can_write_B = 0;
 22      /* flags to mark all the combinations of why we're blocking */
 23      unsigned int read_waiton_write_A = 0;
 24      unsigned int read_waiton_write_B = 0;
 25      unsigned int read_waiton_read_A = 0;
 26      unsigned int read_waiton_read_B = 0;
 27      unsigned int write_waiton_write_A = 0;
 28      unsigned int write_waiton_write_B = 0;
 29      unsigned int write_waiton_read_A = 0;
 30      unsigned int write_waiton_read_B = 0;
 31      /* variable to hold return value of an I/O operation */
 32      int code;
 33   
 34      /* make the underlying I/O layer behind each SSL object non-blocking */
 35      set_non-blocking(A);
 36      set_non-blocking(B);
 37      SSL_set_mode(A, SSL_MODE_ENABLE_PARTIAL_WRITE|
 38                      SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); 
 39      SSL_set_mode(B, SSL_MODE_ENABLE_PARTIAL_WRITE| 
 40                      SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
 41   
 42      for (;;)
 43      {
 44          /* check I/O availability and set flags */
 45          check_availability(A, &can_read_A, &can_write_A,
 46                             B, &can_read_B, &can_write_B);
 47   
 48          /* this "if" statement reads data from A. it will only be entered if
 49           * the following conditions are all true:
 50           * 1. we're not in the middle of a write on A
 51           * 2. there's space left in the A to B buffer
 52           * 3. either we need to write to complete a previously blocked read
 53           *    and now A is available to write, or we can read from A
 54           *    regardless of whether we're blocking for availability to read.
 55           */
 56          if (!(write_waiton_read_A || write_waiton_write_A) &&
 57              (A2B_len != BUF_SIZE) &&
 58              (can_read_A || (can_write_A && read_waiton_write_A)))
 59          {
 60              /* clear the flags since we'll set them based on the I/O call's
 61               * return
 62               */
 63              read_waiton_read_A = 0;
 64              read_waiton_write_A = 0;
 65   
 66              /* read into the buffer after the current position */
 67              code = SSL_read(A, A2B + A2B_len, BUF_SIZE - A2B_len);
 68              switch (SSL_get_error(A, code))
 69              {
 70                  case SSL_ERROR_NONE:
 71                      /* no errors occured.  update the new length and make
 72                       * sure the "have data" flag is set.
 73                       */
 74                      A2B_len += code;
 75                      have_data_A2B = 1;
 76                      break;
 77                  case SSL_ERROR_ZERO_RETURN:
 78                      /* connection closed */
 79                      goto end;
 80                  case SSL_ERROR_WANT_READ:
 81                      /* we need to retry the read after A is available for
 82                       * reading
 83                       */
 84                      read_waiton_read_A = 1;
 85                      break;
 86                  case SSL_ERROR_WANT_WRITE:
 87                      /* we need to retry the read after A is available for
 88                       * writing
 89                       */
 90                      read_waiton_write_A = 1;
 91                      break;
 92                  default:
 93                      /* ERROR */
 94                      goto err;
 95              }
 96          }
 97   
 98          /* this "if" statement is roughly the same as the previous "if"
 99           * statement with A and B switched
100           */
101          if (!(write_waiton_read_B || write_waiton_write_B) &&
102              (B2A_len != BUF_SIZE) &&
103              (can_read_B || (can_write_B && read_waiton_write_B)))
104          {
105              read_waiton_read_B = 0;
106              read_waiton_write_B = 0;
107   
108              code = SSL_read(B, B2A + B2A_len, BUF_SIZE - B2A_len);
109              switch (SSL_get_error(B, code))
110              {
111                  case SSL_ERROR_NONE:
112                      B2A_len += code;
113                      have_data_B2A = 1;
114                      break;
115                  case SSL_ERROR_ZERO_RETURN:
116                      goto end;
117                  case SSL_ERROR_WANT_READ:
118                      read_waiton_read_B = 1;
119                      break;
120                  case SSL_ERROR_WANT_WRITE:
121                      read_waiton_write_B = 1;
122                      break;
123                  default:
124                      goto err;
125              }
126          }
127   
128          /* this "if" statement writes data to A. it will only be entered if
129           * the following conditions are all true:
130           * 1. we're not in the middle of a read on A
131           * 2. there's data in the A to B buffer
132           * 3. either we need to read to complete a previously blocked write
133           *    and now A is available to read, or we can write to A
134           *    regardless of whether we're blocking for availability to write
135           */
136          if (!(read_waiton_write_A || read_waiton_read_A) &&
137              have_data_B2A &&
138              (can_write_A || (can_read_A && write_waiton_read_A)))
139          {
140              /* clear the flags */
141              write_waiton_read_A = 0;
142              write_waiton_write_A = 0;
143   
144              /* perform the write from the start of the buffer */
145              code = SSL_write(A, B2A, B2A_len);
146              switch (SSL_get_error(A, code))
147              {
148                  case SSL_ERROR_NONE:
149                      /* no error occured. adjust the length of the B to A
150                       * buffer to be smaller by the number bytes written.  If
151                       * the buffer is empty, set the "have data" flags to 0,
152                       * or else, move the data from the middle of the buffer
153                       * to the front.
154                       */
155                      B2A_len -= code;
156                      if (!B2A_len)
157                          have_data_B2A = 0;
158                      else
159                          memmove(B2A, B2A + code, B2A_len);
160                      break;
161                  case SSL_ERROR_ZERO_RETURN:
162                      /* connection closed */
163                      goto end;
164                  case SSL_ERROR_WANT_READ:
165                      /* we need to retry the write after A is available for
166                       * reading
167                       */
168                      write_waiton_read_A = 1;
169                      break;
170                  case SSL_ERROR_WANT_WRITE:
171                      /* we need to retry the write after A is available for
172                       * writing
173                       */
174                      write_waiton_write_A = 1;
175                      break;
176                  default:
177                      /* ERROR */
178                      goto err;
179              }
180          }
181   
182          /* this "if" statement is roughly the same as the previous "if"
183           * statement with A and B switched
184           */
185          if (!(read_waiton_write_B || read_waiton_read_B) &&
186              have_data_A2B &&
187              (can_write_B || (can_read_B && write_waiton_read_B)))
188          {
189              write_waiton_read_B = 0;
190              write_waiton_write_B = 0;
191   
192              code = SSL_write(B, A2B, A2B_len);
193              switch (SSL_get_error(B, code))
194              {
195                  case SSL_ERROR_NONE:
196                      A2B_len -= code;
197                      if (!A2B_len)
198                          have_data_A2B = 0;
199                      else
200                          memmove(A2B, A2B + code, A2B_len);
201                      break;
202                  case SSL_ERROR_ZERO_RETURN:
203                      /* connection closed */
204                      goto end;
205                  case SSL_ERROR_WANT_READ:
206                      write_waiton_read_B = 1;
207                      break;
208                  case SSL_ERROR_WANT_WRITE:
209                      write_waiton_write_B = 1;
210                     break;
211                  default:
212                      /* ERROR */
213                      goto err;
214              }
215          }
216      }
217   
218  err:
219      /* if we errored, print then before exiting */
220      fprintf(stderr, "Error(s) occured\n");
221      ERR_print_errors_fp(stderr);
222  end:
223      /* close down the connections. set them back to blocking to simplify. */
224      set_blocking(A);
225      set_blocking(B);
226      SSL_shutdown(A);
227      SSL_shutdown(B);
228  }

Since the code sample is rather large, we’ll dissect it and explain the reasons behind the implementation decisions. We use two buffers, A2B and B2A, to hold the data read from A to be written to B, and the data read from B to be written to A, respectively. The length variables corresponding to each buffer (A2B_len and B2A_len) are initialized to zero; throughout our function, they will hold the number of bytes of data in their counterpart buffer. An important observation to make at this point is that we do not use an offset variable for pointing into our buffer; the data we want to write will always be at the front of our buffers.

We also declare three sets of flags. The first set (have_data_A2B and have_data_B2A) indicates whether there is any data in our buffers. We could have left these two variables out, since throughout the function they will be zero only if the corresponding buffer length’s variable is also zero. We opted to use them for code readability. The next set of variables is availability flags, which are of the form can_read_A, can_write_B, etc. These flags are set to indicate that the named operation on the named object can be performed, i.e., the connection is available to perform the operation without needing to block. The last set of flags is the blocking flags. When one of these is set, it tells us that a particular kind of I/O operation requires the availability of the connection to perform another kind of I/O operation. For instance, if write_waiton_read_B is set, it means that the last write operation performed on B must be retried after B is available to read.

We use three functions in this example that are not explicitly defined. The first is set_nonblocking . This function takes an SSL object as its only argument and must be implemented in a platform-specific way to set the underlying I/O layer of the SSL object to be non-blocking. Likewise, set_blocking needs to set the connection to a blocking state. The last platform-specific function is check_availability . This function’s obligation is to check the I/O status of the underlying layers of both A and B and set the variables appropriately. Additionally, this function should wait until at least one variable is set before returning. We cannot perform any I/O operations if nothing is available for either connection. These omitted functions can be implemented easily. For instance, on a Unix system with SSL objects based on sockets, set_nonblocking and set_blocking can be implemented using the fcntl system call, and the check_availability function can use fd_set data structures along with the select system call.

The calls to SSL_set_mode set two mode variables we’ve not discussed. Normally, a call to SSL_write will not return success until all of the data in the buffer has been written, even with non-blocking I/O. Thus, a call that requests a large amount of data to be written can return many retry requests before succeeding. This behavior is undesirable in our function because we wish to interlace the reading routines with those that write, and to do this effectively, we cannot retry a single call for too long because it will prevent us from reading more data from that connection. To alleviate this problem, the SSL_MODE_ENABLE_PARTIAL_WRITE mode flag instructs the library to allow partially complete writes to count as successes. This allows our function to perform several write operations successfully, and between those calls, we can read more data. Because the SSL protocol is built around sending complete messages on the channel, sometimes we’ll be required to retry a call because only a part of the requested data was actually sent. As a result, all retried operations must be called with the exact same arguments as the original call that caused the error. This behavior can be cumbersome since it does not allow us to add to the write buffer after making a call to SSL_write that needed a retry. To change this property partially, we use the SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER flag. This flags allows us to retry the write operation with different parameters, i.e., a different buffer and size, so long as the original data is still contained in the new buffer. In our example, we use the same buffer, but enable this mode so that we can keep reading into the end of the buffer without causing errors on attempts to retry writes.

The function has one main loop, beginning on line 42 and ending on line 216. In this loop, we have four separated subsections, lines 56-96, lines 101-126, lines 136-180 and lines 185-215. Each of these conditional blocks corresponds respectively to reading data from A, reading data from B, writing data to A, and writing data to B. Looking carefully, we can see that the first and second section mirror each other just as the third and fourth do.

We must be sure it is safe to read. The entry conditions on the if statements do this. They assert we are not in the middle of a read operation by checking the write blocking flags for the connection (!(write_waiton_read_... || write_waiton_write_...)). Additionally, it needs to be sure there is space in the buffers (..._len != BUF_SIZE). In addition to these conditions, we must be sure this is the right time to call the read function, i.e., either we’re waiting to retry a failed read because it was waiting for the connection to be available for writing (can_write_... && read_waiton_write_...), or we simply have data to read (can_read_...). If all of these conditions are met, we can attempt a read. Before we do, though, we reset the blocking flags for the read operation, and then perform the I/O call and check the error code. The I/O call itself instructs the SSL library to write the data into the buffer offset by the number of bytes already stored within it. If the call succeeds, we’ll update the length counter and make sure that the have_data_... flag is set. If we are instructed to retry through either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, we will set the appropriate blocking flag and carry on. If an error occurs, or the end of the SSL connection is detected, we will break out of the I/O loop.

As with read operations, write operations are protected by an if statement to assure entry conditions. These statements ensure that we are not in the middle of a read operation (!(read_waiton_write_... || read_waiton_read_...)). Additionally, there must be data in the buffer (have_data_...). Lastly, the write operation must make sure that writing is possible, i.e., either we’ve been waiting for read availability to retry a write operation (can_read_... && write_waiton_read_...), or there is write availability (can_write_...). Before attempting the write, we must zero the blocking flags. The write operation always tries to write from the beginning of the buffer. If we are successful, we move the data in the buffer forward so that data already written is pushed out. If the buffer is empty after writing, we zero the have_data_... flag. As with the read operation, if we are instructed to retry, we set the corresponding blocking flag and continue. If errors occur, or if the SSL connection has been closed, we break out of the I/O loop.

These paradigms for performing reading and writing enable us to do effective non-blocking communication when combined in the loop. For instance, if B cannot be written to, we will continue to buffer data from A until the buffers fill and we are forced to wait for writing on B. We can extend the general form presented in this function to allow for many different non-blocking I/O needs that applications may have.

SSL Renegotiations

An SSL renegotiation is essentially an SSL handshake during a connection. This causes client credentials to be reevaluated and a new session to be created. We’ve discussed the effects of renegotiation on the I/O and other aspects of a program’s implementation, but haven’t talked about why renegotiations are important.

Since renegotiations cause the creation of a new session, the session key is replaced. For long-lasting SSL connections or for those that transfer a high quantity of data, it is a good idea to replace the session key periodically. In general, the longer a session key exists, the more the likelihood of key compromise increases. Using renegotiations, we can replace the session key so that we don’t encrypt too much data with just one key.

SSL renegotiations can occur during normal application data transfer. When the new handshake is completed, both parties switch to using the new key. The function to request renegotiation on an SSL connection is SSL_renegotiate .

This function does not actually perform the new handshake when called; however, it sets a flag that causes a renegotiation request to be sent to the peer. The request is sent out on the next call to an I/O function on the SSL connection. An important point to consider is that the peer has the option of not renegotiating the connection. If the peer chooses not to respond to the request and to continue transferring data instead, no renegotiation will occur; unless the requestor checks for negotiation success, it may not have happened. This is especially important when making applications that will connect with other non-OpenSSL SSL implementations.

A renegotiation can also be done explicitly. In other words, we will send a request and not send or receive application data until the new handshake has completed successfully. While this will sometimes be the appropriate way to refresh session keys for long-term connections, it serves another important purpose. It allows us to upgrade client authentication from the server side. For instance, our example server above requires a valid client certificate to be presented in order for the initial connection to take place. This is overly restrictive since at connection time, we do not know the client’s intentions.

As an example, consider a simple protocol that allows clients to connect via SSL and send commands to the server. A subset of these commands will be reserved for administrators. Our goal is to allow anyone to connect to the server to run the general commands and allow only administrators to run reserved commands. If we require all users to present a certificate at connection time, we must issue a certificate to every possible client and special certificates to administrators. This can become cumbersome rather quickly. Alternatively, we can run two servers, one for normal users and another for administrators. This is also a suboptimal solution since it requires extra consumption of resources.

We can use renegotiations to make our job simpler. First, we will allow anyone to connect without a certificate. When a client connects, it sends its command. If it’s a reserved command, we require a renegotiation with the more stringent requirements on the client in place. If the client renegotiates successfully, we can accept the command, or otherwise discard it. Thus, we need to issue certificates only to administrators. This option is clearly better than the previous two since it allows us to determine what the client is attempting before putting stronger authentication requirements in place.

Implementing renegotiations

As we’ve said before, to do a passive renegotiation (i.e., one in which the handshake occurs during application I/O) we need to call only SSL_renegotiate. In general, applications can get away with having the handshake occur during application I/O, but making sure that it did indeed happen is important. Unfortunately, this isn’t easy to do with OpenSSL Version 0.9.6. In fact, many aspects of renegotiations don’t work cleanly in this version. However, the forthcoming Version 0.9.7 promises to improve this considerably. These changes are described in the next section.

With Version 0.9.6, we should always use explicit renegotiation since there is no way to determine if a renegotiation request was ignored by the peer. This is the same problem we find in renegotiating client credentials; we will focus on determining if a request was ignored, since it is far more common. Renegotiating for session key refreshment is a subset of this problem. Example 5-17 shows an incomplete code fragment to force a renegotiation from a server.

Example 5-17. Code fragment to force a renegotiation from a server
/* assume ssl is connected and error free up to here */
set_blocking(ssl); /* this is unnecessary if it is already blocking */
SSL_renegotiate(ssl);
SSL_do_handshake(ssl);
if (ssl->state != SSL_ST_OK)
    int_error("Failed to send renegotiation request");
ssl->state |= SSL_ST_ACCEPT;
SSL_do_handshake(ssl);
if (ssl->state != SSL_ST_OK)
    int_error("Failed to complete renegotiation");
/* our renegotiation is complete */

This example uses some functions we’ve seen before. To avoid extra complication, we ensure the SSL object is blocking so we don’t need to retry failed I/O calls. The call to SSL_renegotiate sends out the request to the peer. The function SSL_do_handshake is a generic routine that calls the accept function for server objects or the connect function for client objects. This first call to SSL_do_handshake sends out our request and returns. After doing this, we need to check that the SSL connection hasn’t received any errors. We do this by making sure its state is SSL_ST_OK. At this point, if we call the handshake function again, it will just return if the peer chose not to renegotiate. This occurs because the SSL/TLS protocols allow requests to be ignored. Since we have a reason for renegotiating, and we need it to complete before continuing, we must manually set the SSL_ST_ACCEPT state of the server object. This will cause the subsequent call to SSL_do_handshake, which will force a handshake to occur before continuing.

Obviously, this method of renegotiation isn’t very clean because it requires us to set internal variables of the SSL object manually. Unfortunately, it is the only way to accomplish a forced renegotiation. This code fragment is not complete, though. Consider session caching, which allows a client to skip the handshake by resuming a previously created session. This can be extremely bad when our purpose is to collect stronger client credentials since the client has already obtained a valid session with weak credentials. When we attempt to renegotiate a connection from a server that does session caching, we must take extra precautions that the client doesn’t simply present the previously negotiated session and bypass the handshake. To make this discrepancy between the sessions, we need to change the session ID context. Recall that the session ID context’s function is to discern sessions established with clients for different purposes. To change its value, we use the function SSL_set_session_id_context . It behaves exactly as the SSL_CTX version discussed above, except that it operates on SSL objects. The change to the session ID context must be made before the renegotiation is started.

We haven’t discussed the minor detail of setting stronger requirements for verification of the client during renegotiation. To do this, we use the function SSL_set_verify and pass it our new verify flags. Example 5-18 shows the code fragment that must be built around the fragment shown in Example 5-17 in order for the renegotiation to be effective. This fragment is for a caching server that wishes to upgrade client authentication; if our server isn’t caching, we can omit the calls to set the session ID context.

Example 5-18. Code to cause forced renegotiation in order to request stronger client authentication and distinguish the sessions
/* assume ctx is an SSL_CTX object that is setup to not have any verify
   options. */
int normal_user = 1;
int admin_user = 2;
SSL_CTX_set_session_id_context(ctx, &normal_user, sizeof(int));
/* perform rest of ctx setup, create an ssl object, and connect it */
/* normal SSL I/O operations and application code go here */
/* if we want to upgrade client privilege, we enter the following code block */
SSL_set_verify(ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
               verify_callback);
SSL_set_session_id_context(ssl, &admin_user, sizeof(int));
/* code fragment from Example 5-18 goes here.  the new session is made */
post_connection_check(ssl, host);
/* if everything is error-free, we have properly authenticated the client */

The code in Example 5-18 realizes the solution to the problem we laid out earlier for upgrading client authentication. By changing the session context ID to admin_user, we allow clients previously verified as admin users to resume connections, but no others. This is effective at keeping resumed sessions from being mistaken as privileged sessions. In addition, we set the verify options for the SSL object explicitly, forcing the renegotiation to demand a client certificate or fail. After renegotiation is complete, we call the post-connection check function. In some cases, we may want to tailor the post-connection function to meet application-specific needs.

Renegotiations in 0.9.7

Overall, renegotiation in Version 0.9.6 is inferior to the functions and simplicity that Version 0.9.7 promises. A new function has been added, SSL_regenotiate_pending . This function will return nonzero if a request is sent, but the handshake hasn’t been finished. It will return zero once the handshake is complete. Using this function, we can eliminate most of the ugliness associated with renegotiations in 0.9.6. Before looking at forced renegotiations, we’ll briefly return to passive renegotiations.

In most applications, renegotiations for changing the session key rather than upgrading client authentication are started by byte transfer thresholds. In other words, once our connection has transferred a certain number of bytes, we will renegotiate. Because of this new function, we can simply call SSL_renegotiate when the byte limit is reached. Then we periodically check the value of SSL_renegotiate_pending to determine if the renegotiation completed. Doing this, we can programmatically fail if the handshake isn’t completed in a certain amount of time after the request.

Furthermore, a new SSL option to aid us has been added in Version 0.9.7. By setting SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION with a call to SSL_CTX_set_options, we can automatically prevent clients from being able to resume sessions when we ask for renegotiations, regardless of the session ID context. When our goal is to refresh session keys, this option is invaluable.

Using these two new additions will also allow us to make a much cleaner forced renegotiation for client authentication. We can call SSL_renegotiate to set the flag and make a single call to SSL_do_handshake to send the request out. Instead of setting internal state of the SSL object, we can now just call SSL_do_handshake until we either programmatically timeout or SSL_renegotiate_pending returns zero. If the latter condition is met, our renegotiation completed successfully. Ideally, we want to leave the session ID context changing and not set the new SSL option when performing renegotiation for client authentication. This is better because it allows authenticated users to resume authenticated sessions rather than always perform the full handshake.

Further notes

We’ve limited our discussion of renegotiations to server-side implementation. In general, applications will be made this way since servers dictate session caching, and a server almost always presents credentials. However, it is possible for a client to request or force renegotiation, though it is less common. Doing this is a logical extension of the methods used by a server. We now have a better understanding of renegotiations, why they occur, and why they’re needed for certain operations. Forcing a renegotiation to force the client to provide a certificate to continue the connection is also a popular paradigm for implementing SSL applications. As we discussed early in this section, the alternatives to renegotiation for accomplishing this task are often too burdensome to be a general solution.

One major point that’s been missing up to now is how to make an application react to renegotiation requests. The good news here is that it’s all handled by the OpenSSL library for us. Recall the complications with I/O. The reason we had to handle all the different varieties of retries is that a renegotiation request could be received at any time. When an SSL connection is requested to renegotiate, the implementation automatically does so and completes the new handshake to generate a new session.



[1] Export restrictions once required weak RSA keys for encryption, but stronger keys were acceptable for signing.