Product SiteDocumentation Site

17.2. TLS Clients

Secure use of TLS in a client generally involves all of the following steps. (Individual instructions for specific TLS implementations follow in the next sections.)
It is safe to provide users detailed diagnostics on certificate validation failures. Other causes of handshake failures and, generally speaking, any details on other errors reported by the TLS implementation (particularly exception tracebacks), must not be divulged in ways that make them accessible to potential attackers. Otherwise, it is possible to create decryption oracles.

Important

Depending on the application, revocation checking (against certificate revocations lists or via OCSP) and session resumption are important aspects of production-quality client. These aspects are not yet covered.

17.2.1. Implementation TLS Clients With OpenSSL

In the following code, the error handling is only exploratory. Proper error handling is required for production use, especially in libraries.
The OpenSSL library needs explicit initialization (see Example 17.3, “OpenSSL library initialization”).
Example 17.3. OpenSSL library initialization
// The following call prints an error message and calls exit() if
// the OpenSSL configuration file is unreadable.
OPENSSL_config(NULL);
// Provide human-readable error messages.
SSL_load_error_strings();
// Register ciphers.
SSL_library_init();

After that, a context object has to be created, which acts as a factory for connection objects (Example 17.4, “OpenSSL client context creation”). We use an explicit cipher list so that we do not pick up any strange ciphers when OpenSSL is upgraded. The actual version requested in the client hello depends on additional restrictions in the OpenSSL library. If possible, you should follow the example code and use the default list of trusted root certificate authorities provided by the system because you would have to maintain your own set otherwise, which can be cumbersome.
Example 17.4. OpenSSL client context creation
// Configure a client connection context.  Send a hendshake for the
// highest supported TLS version, and disable compression.
const SSL_METHOD *const req_method = SSLv23_client_method();
SSL_CTX *const ctx = SSL_CTX_new(req_method);
if (ctx == NULL) {
  ERR_print_errors(bio_err);
  exit(1);
}
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION);

// Adjust the ciphers list based on a whitelist.  First enable all
// ciphers of at least medium strength, to get the list which is
// compiled into OpenSSL.
if (SSL_CTX_set_cipher_list(ctx, "HIGH:MEDIUM") != 1) {
  ERR_print_errors(bio_err);
  exit(1);
}
{
  // Create a dummy SSL session to obtain the cipher list.
  SSL *ssl = SSL_new(ctx);
  if (ssl == NULL) {
    ERR_print_errors(bio_err);
    exit(1);
  }
  STACK_OF(SSL_CIPHER) *active_ciphers = SSL_get_ciphers(ssl);
  if (active_ciphers == NULL) {
    ERR_print_errors(bio_err);
    exit(1);
  }
  // Whitelist of candidate ciphers.
  static const char *const candidates[] =  {
    "AES128-GCM-SHA256", "AES128-SHA256", "AES256-SHA256", // strong ciphers
    "AES128-SHA", "AES256-SHA", // strong ciphers, also in older versions
    "RC4-SHA", "RC4-MD5", // backwards compatibility, supposed to be weak
    "DES-CBC3-SHA", "DES-CBC3-MD5", // more backwards compatibility
    NULL
  };
  // Actually selected ciphers.
  char ciphers[300];
  ciphers[0] = '\0';
  for (const char *const *c = candidates; *c; ++c) {
    for (int i = 0; i < sk_SSL_CIPHER_num(active_ciphers); ++i) {
	if (strcmp(SSL_CIPHER_get_name(sk_SSL_CIPHER_value(active_ciphers, i)),
		   *c) == 0) {
	  if (*ciphers) {
	    strcat(ciphers, ":");
	  }
	  strcat(ciphers, *c);
	  break;
	}
    }
  }
  SSL_free(ssl);
  // Apply final cipher list.
  if (SSL_CTX_set_cipher_list(ctx, ciphers) != 1) {
    ERR_print_errors(bio_err);
    exit(1);
  }
}

// Load the set of trusted root certificates.
if (!SSL_CTX_set_default_verify_paths(ctx)) {
  ERR_print_errors(bio_err);
  exit(1);
}

A single context object can be used to create multiple connection objects. It is safe to use the same SSL_CTX object for creating connections concurrently from multiple threads, provided that the SSL_CTX object is not modified (e.g., callbacks must not be changed).
After creating the TCP socket and disabling the Nagle algorithm (per Example 17.1, “Deactivating the TCP Nagle algorithm”), the actual connection object needs to be created, as show in Example 17.4, “OpenSSL client context creation”. If the handshake started by SSL_connect fails, the ssl_print_error_and_exit function from Example 17.2, “Obtaining OpenSSL error codes” is called.
The certificate_validity_override function provides an opportunity to override the validity of the certificate in case the OpenSSL check fails. If such functionality is not required, the call can be removed, otherwise, the application developer has to implement it.
The host name passed to the functions SSL_set_tlsext_host_name and X509_check_host must be the name that was passed to getaddrinfo or a similar name resolution function. No host name canonicalization must be performed. The X509_check_host function used in the final step for host name matching is currently only implemented in OpenSSL 1.1, which is not released yet. In case host name matching fails, the function certificate_host_name_override is called. This function should check user-specific certificate store, to allow a connection even if the host name does not match the certificate. This function has to be provided by the application developer. Note that the override must be keyed by both the certificate and the host name.
Example 17.5. Creating a client connection using OpenSSL
// Create the connection object.
SSL *ssl = SSL_new(ctx);
if (ssl == NULL) {
  ERR_print_errors(bio_err);
  exit(1);
}
SSL_set_fd(ssl, sockfd);

// Enable the ServerNameIndication extension
if (!SSL_set_tlsext_host_name(ssl, host)) {
  ERR_print_errors(bio_err);
  exit(1);
}

// Perform the TLS handshake with the server.
ret = SSL_connect(ssl);
if (ret != 1) {
  // Error status can be 0 or negative.
  ssl_print_error_and_exit(ssl, "SSL_connect", ret);
}

// Obtain the server certificate.
X509 *peercert = SSL_get_peer_certificate(ssl);
if (peercert == NULL) {
  fprintf(stderr, "peer certificate missing");
  exit(1);
}

// Check the certificate verification result.  Allow an explicit
// certificate validation override in case verification fails.
int verifystatus = SSL_get_verify_result(ssl);
if (verifystatus != X509_V_OK && !certificate_validity_override(peercert)) {
  fprintf(stderr, "SSL_connect: verify result: %s\n",
	    X509_verify_cert_error_string(verifystatus));
  exit(1);
}

// Check if the server certificate matches the host name used to
// establish the connection.
// FIXME: Currently needs OpenSSL 1.1.
if (X509_check_host(peercert, (const unsigned char *)host, strlen(host),
		      0) != 1
    && !certificate_host_name_override(peercert, host)) {
  fprintf(stderr, "SSL certificate does not match host name\n");
  exit(1);
}

X509_free(peercert);


The connection object can be used for sending and receiving data, as in Example 17.6, “Using an OpenSSL connection to send and receive data”. It is also possible to create a BIO object and use the SSL object as the underlying transport, using BIO_set_ssl.
Example 17.6. Using an OpenSSL connection to send and receive data
const char *const req = "GET / HTTP/1.0\r\n\r\n";
if (SSL_write(ssl, req, strlen(req)) < 0) {
  ssl_print_error_and_exit(ssl, "SSL_write", ret);
}
char buf[4096];
ret = SSL_read(ssl, buf, sizeof(buf));
if (ret < 0) {
  ssl_print_error_and_exit(ssl, "SSL_read", ret);
}

When it is time to close the connection, the SSL_shutdown function needs to be called twice for an orderly, synchronous connection termination (Example 17.7, “Closing an OpenSSL connection in an orderly fashion”). This exchanges close_notify alerts with the server. The additional logic is required to deal with an unexpected close_notify from the server. Note that is necessary to explicitly close the underlying socket after the connection object has been freed.
Example 17.7. Closing an OpenSSL connection in an orderly fashion
// Send the close_notify alert.
ret = SSL_shutdown(ssl);
switch (ret) {
case 1:
  // A close_notify alert has already been received.
  break;
case 0:
  // Wait for the close_notify alert from the peer.
  ret = SSL_shutdown(ssl);
  switch (ret) {
  case 0:
    fprintf(stderr, "info: second SSL_shutdown returned zero\n");
    break;
  case 1:
    break;
  default:
    ssl_print_error_and_exit(ssl, "SSL_shutdown 2", ret);
  }
  break;
default:
  ssl_print_error_and_exit(ssl, "SSL_shutdown 1", ret);
}
SSL_free(ssl);
close(sockfd);

Example 17.8, “Closing an OpenSSL connection in an orderly fashion” shows how to deallocate the context object when it is no longer needed because no further TLS connections will be established.
Example 17.8. Closing an OpenSSL connection in an orderly fashion
SSL_CTX_free(ctx);