Transport Layer Security (TLS)

Transport Layer Security (TLS, formerly Secure Sockets Layer/SSL) is the recommended way to to protect integrity and confidentiality while data is transferred over an untrusted network connection, and to identify the endpoint. At this chapter we describe the available libraries in Fedora as well as known pitfalls, and safe ways to write applications with them.

When using any library, in addition to this guide, it is recommended to consult the library' documentation.

Common Pitfalls

TLS implementations are difficult to use, and most of them lack a clean API design. The following sections contain implementation-specific advice, and some generic pitfalls are mentioned below.

  • Most TLS implementations have questionable default TLS cipher suites. Most of them enable anonymous Diffie-Hellman key exchange (but we generally want servers to authenticate themselves). Many do not disable ciphers which are subject to brute-force attacks because of restricted key lengths. Some even disable all variants of AES in the default configuration.

    When overriding the cipher suite defaults, it is recommended to disable all cipher suites which are not present on a whitelist, instead of simply enabling a list of cipher suites. This way, if an algorithm is disabled by default in the TLS implementation in a future security update, the application will not re-enable it.

  • The name which is used in certificate validation must match the name provided by the user or configuration file. No host name canonicalization or IP address lookup must be performed.

  • The TLS handshake has very poor performance if the TCP Nagle algorithm is active. You should switch on the TCP_NODELAY socket option (at least for the duration of the handshake), or use the Linux-specific TCP_CORK option.

    Exemple 1. Deactivating the TCP Nagle algorithm
    const int val = 1;
    int ret = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
    if (ret < 0) {
      perror("setsockopt(TCP_NODELAY)");
      exit(1);
    }
  • Implementing proper session resumption decreases handshake overhead considerably. This is important if the upper-layer protocol uses short-lived connections (like most application of HTTPS).

  • Both client and server should work towards an orderly connection shutdown, that is send close_notify alerts and respond to them. This is especially important if the upper-layer protocol does not provide means to detect connection truncation (like some uses of HTTP).

  • When implementing a server using event-driven programming, it is important to handle the TLS handshake properly because it includes multiple network round-trips which can block when an ordinary TCP accept would not. Otherwise, a client which fails to complete the TLS handshake for some reason will prevent the server from handling input from other clients.

  • Unlike regular file descriptors, TLS connections cannot be passed between processes. Some TLS implementations add additional restrictions, and TLS connections generally cannot be used across fork function calls (see fork as a Primitive for Parallelism).

OpenSSL Pitfalls

Some OpenSSL function use tri-state return values. Correct error checking is extremely important. Several functions return int values with the following meaning:

  • The value 1 indicates success (for example, a successful signature verification).

  • The value 0 indicates semantic failure (for example, a signature verification which was unsuccessful because the signing certificate was self-signed).

  • The value -1 indicates a low-level error in the system, such as failure to allocate memory using malloc.

Treating such tri-state return values as booleans can lead to security vulnerabilities. Note that some OpenSSL functions return boolean results or yet another set of status indicators. Each function needs to be checked individually.

Recovering precise error information is difficult. Obtaining OpenSSL error codes shows how to obtain a more precise error code after a function call on an SSL object has failed. However, there are still cases where no detailed error information is available (e.g., if SSL_shutdown fails due to a connection teardown by the other end).

Exemple 2. Obtaining OpenSSL error codes
static void __attribute__((noreturn))
ssl_print_error_and_exit(SSL *ssl, const char *op, int ret)
{
  int subcode = SSL_get_error(ssl, ret);
  switch (subcode) {
  case SSL_ERROR_NONE:
    fprintf(stderr, "error: %s: no error to report\n", op);
    break;
  case SSL_ERROR_WANT_READ:
  case SSL_ERROR_WANT_WRITE:
  case SSL_ERROR_WANT_X509_LOOKUP:
  case SSL_ERROR_WANT_CONNECT:
  case SSL_ERROR_WANT_ACCEPT:
    fprintf(stderr, "error: %s: invalid blocking state %d\n", op, subcode);
    break;
  case SSL_ERROR_SSL:
    fprintf(stderr, "error: %s: TLS layer problem\n", op);
  case SSL_ERROR_SYSCALL:
    fprintf(stderr, "error: %s: system call failed: %s\n", op, strerror(errno));
    break;
  case SSL_ERROR_ZERO_RETURN:
    fprintf(stderr, "error: %s: zero return\n", op);
  }
  exit(1);
}

The OPENSSL_config function is documented to never fail. In reality, it can terminate the entire process if there is a failure accessing the configuration file. An error message is written to standard error, but which might not be visible if the function is called from a daemon process.

OpenSSL contains two separate ASN.1 DER decoders. One set of decoders operate on BIO handles (the input/output stream abstraction provided by OpenSSL); their decoder function names start with d2i_ and end in _fp or _bio (e.g., d2i_X509_fp or d2i_X509_bio). These decoders must not be used for parsing data from untrusted sources; instead, the variants without the _fp and _bio (e.g., d2i_X509) shall be used. The BIO variants have received considerably less testing and are not very robust.

For the same reason, the OpenSSL command line tools (such as openssl x509) are generally generally less robust than the actual library code. They use the BIO functions internally, and not the more robust variants.

The command line tools do not always indicate failure in the exit status of the openssl process. For instance, a verification failure in openssl verify result in an exit status of zero.

OpenSSL command-line commands, such as openssl genrsa, do not ensure that physical entropy is used for key generation—they obtain entropy from /dev/urandom and other sources, but not from /dev/random. This can result in weak keys if the system lacks a proper entropy source (e.g., a virtual machine with solid state storage). Depending on local policies, keys generated by these OpenSSL tools should not be used in high-value, critical functions.

The OpenSSL server and client applications (openssl s_client and openssl s_server) are debugging tools and should never be used as generic clients. For instance, the s_client tool reacts in a surprising way to lines starting with R and Q.

OpenSSL allows application code to access private key material over documented interfaces. This can significantly increase the part of the code base which has to undergo security certification.

GnuTLS Pitfalls

Older versions of GnuTLS had several peculiarities described in previous versions of this guide; as of GnuTLS 3.3.10, these issues are no longer applicable.

OpenJDK Pitfalls

The Java cryptographic framework is highly modular. As a result, when you request an object implementing some cryptographic functionality, you cannot be completely sure that you end up with the well-tested, reviewed implementation in OpenJDK.

OpenJDK (in the source code as published by Oracle) and other implementations of the Java platform require that the system administrator has installed so-called unlimited strength jurisdiction policy files. Without this step, it is not possible to use the secure algorithms which offer sufficient cryptographic strength. Most downstream redistributors of OpenJDK remove this requirement.

Some versions of OpenJDK use /dev/random as the randomness source for nonces and other random data which is needed for TLS operation, but does not actually require physical randomness. As a result, TLS applications can block, waiting for more bits to become available in /dev/random.

NSS Pitfalls

NSS was not designed to be used by other libraries which can be linked into applications without modifying them. There is a lot of global state. There does not seem to be a way to perform required NSS initialization without race conditions.

If the NSPR descriptor is in an unexpected state, the SSL_ForceHandshake function can succeed, but no TLS handshake takes place, the peer is not authenticated, and subsequent data is exchanged in the clear.

NSS disables itself if it detects that the process underwent a fork after the library has been initialized. This behavior is required by the PKCS#11 API specification.

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.)

  • The client must configure the TLS library to use a set of trusted root certificates. These certificates are provided by the system in various formats and files. These are documented in update-ca-trust man page in Fedora. Portable applications should not hard-code any paths; they should rely on APIs which set the default for the system trust store.

  • The client selects sufficiently strong cryptographic primitives and disables insecure ones (such as no-op encryption). Compression support and SSL version 3 or lower must be disabled (including the SSLv2-compatible handshake).

  • The client initiates the TLS connection. The Server Name Indication extension should be used if supported by the TLS implementation. Before switching to the encrypted connection state, the contents of all input and output buffers must be discarded.

  • The client needs to validate the peer certificate provided by the server, that is, the client must check that there is a cryptographically protected chain from a trusted root certificate to the peer certificate. (Depending on the TLS implementation, a TLS handshake can succeed even if the certificate cannot be validated.)

  • The client must check that the configured or user-provided server name matches the peer certificate provided by the server.

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.

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.

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 OpenSSL library initialization).

Exemple 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 (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.

Exemple 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 Deactivating the TCP Nagle algorithm), the actual connection object needs to be created, as show in OpenSSL client context creation. If the handshake started by SSL_connect fails, the ssl_print_error_and_exit function from 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.

Exemple 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 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.

Exemple 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 (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.

Exemple 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);

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.

Exemple 8. Closing an OpenSSL connection in an orderly fashion
SSL_CTX_free(ctx);

Implementation TLS Clients With GnuTLS

This section describes how to implement a TLS client with full certificate validation (but without certificate revocation checking). Note that the error handling in is only exploratory and needs to be replaced before production use.

Before setting up TLS connections, a credentials objects has to be allocated and initialized with the set of trusted root CAs (Initializing a GnuTLS credentials structure).

Exemple 9. Initializing a GnuTLS credentials structure
// Load the trusted CA certificates.
gnutls_certificate_credentials_t cred = NULL;
int ret = gnutls_certificate_allocate_credentials (&cred);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_certificate_allocate_credentials: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}

ret = gnutls_certificate_set_x509_system_trust(cred);
if (ret == 0) {
  fprintf(stderr, "error: no certificates found in system trust store\n");
  exit(1);
}
if (ret < 0) {
  fprintf(stderr, "error: gnutls_certificate_set_x509_system_trust: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}

After the last TLS connection has been closed, this credentials object should be freed:

gnutls_certificate_free_credentials(cred);

During its lifetime, the credentials object can be used to initialize TLS session objects from multiple threads, provided that it is not changed.

Once the TCP connection has been established, the Nagle algorithm should be disabled (see Deactivating the TCP Nagle algorithm). After that, the socket can be associated with a new GnuTLS session object. The previously allocated credentials object provides the set of root CAs. Then the TLS handshake must be initiated. This is shown in Establishing a TLS client connection using GnuTLS.

Exemple 10. Establishing a TLS client connection using GnuTLS
// Create the session object.
gnutls_session_t session;
ret = gnutls_init(&session, GNUTLS_CLIENT);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_init: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}

// Configure the cipher preferences.
const char *errptr = NULL;
ret = gnutls_priority_set_direct(session, "NORMAL", &errptr);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_priority_set_direct: %s\n"
	    "error: at: \"%s\"\n", gnutls_strerror(ret), errptr);
  exit(1);
}

// Install the trusted certificates.
ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_credentials_set: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}

// Associate the socket with the session object and set the server
// name.
gnutls_transport_set_int(session, sockfd);
ret = gnutls_server_name_set(session, GNUTLS_NAME_DNS,
			       host, strlen(host));
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_server_name_set: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}

// Establish the session.
ret = gnutls_handshake(session);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_handshake: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}

After the handshake has been completed, the server certificate needs to be verified against the server’s hostname (Verifying a server certificate using GnuTLS). In the example, the user-defined certificate_validity_override function is called if the verification fails, so that a separate, user-specific trust store can be checked. This function call can be omitted if the functionality is not needed.

Exemple 11. Verifying a server certificate using GnuTLS
// Obtain the server certificate chain.  The server certificate
// itself is stored in the first element of the array.
unsigned certslen = 0;
const gnutls_datum_t *const certs =
  gnutls_certificate_get_peers(session, &certslen);
if (certs == NULL || certslen == 0) {
  fprintf(stderr, "error: could not obtain peer certificate\n");
  exit(1);
}

// Validate the certificate chain.
unsigned status = (unsigned)-1;
ret = gnutls_certificate_verify_peers3(session, host, &status);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_certificate_verify_peers3: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}
if (status != 0 && !certificate_validity_override(certs[0])) {
  gnutls_datum_t msg;
#if GNUTLS_VERSION_AT_LEAST_3_1_4
  int type = gnutls_certificate_type_get (session);
  ret = gnutls_certificate_verification_status_print(status, type, &out, 0);
#else
  ret = -1;
#endif
  if (ret == 0) {
    fprintf(stderr, "error: %s\n", msg.data);
    gnutls_free(msg.data);
    exit(1);
  } else {
    fprintf(stderr, "error: certificate validation failed with code 0x%x\n",
	      status);
    exit(1);
  }
}

An established TLS session can be used for sending and receiving data, as in Using a GnuTLS session.

Exemple 12. Using a GnuTLS session
char buf[4096];
snprintf(buf, sizeof(buf), "GET / HTTP/1.0\r\nHost: %s\r\n\r\n", host);
ret = gnutls_record_send(session, buf, strlen(buf));
if (ret < 0) {
  fprintf(stderr, "error: gnutls_record_send: %s\n", gnutls_strerror(ret));
  exit(1);
}
ret = gnutls_record_recv(session, buf, sizeof(buf));
if (ret < 0) {
  fprintf(stderr, "error: gnutls_record_recv: %s\n", gnutls_strerror(ret));
  exit(1);
}

In order to shut down a connection in an orderly manner, you should call the gnutls_bye function. Finally, the session object can be deallocated using gnutls_deinit (see Closing a GnuTLS session in an orderly fashion).

Exemple 13. Closing a GnuTLS session in an orderly fashion
// Initiate an orderly connection shutdown.
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
if (ret < 0) {
  fprintf(stderr, "error: gnutls_bye: %s\n", gnutls_strerror(ret));
  exit(1);
}
// Free the session object.
gnutls_deinit(session);

Implementing TLS Clients With OpenJDK

The examples below use the following cryptographic-related classes:

import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import sun.security.util.HostnameChecker;

If compatibility with OpenJDK 6 is required, it is necessary to use the internal class sun.security.util.HostnameChecker. (The public OpenJDK API does not provide any support for dissecting the subject distinguished name of an X.509 certificate, so a custom-written DER parser is needed—or we have to use an internal class, which we do below.) In OpenJDK 7, the setEndpointIdentificationAlgorithm method was added to the javax.net.ssl.SSLParameters class, providing an official way to implement host name checking.

TLS connections are established using an SSLContext instance. With a properly configured OpenJDK installation, the SunJSSE provider uses the system-wide set of trusted root certificate authorities, so no further configuration is necessary. For backwards compatibility with OpenJDK6, the TLSv1 provider has to be supported as a fall-back option. This is shown in Setting up an SSLContext for OpenJDK TLS clients.

Exemple 14. Setting up an SSLContext for OpenJDK TLS clients
// Create the context.  Specify the SunJSSE provider to avoid
// picking up third-party providers.  Try the TLS 1.2 provider
// first, then fall back to TLS 1.0.
SSLContext ctx;
try {
    ctx = SSLContext.getInstance("TLSv1.2", "SunJSSE");
} catch (NoSuchAlgorithmException e) {
    try {
        ctx = SSLContext.getInstance("TLSv1", "SunJSSE");
    } catch (NoSuchAlgorithmException e1) {
        // The TLS 1.0 provider should always be available.
        throw new AssertionError(e1);
    } catch (NoSuchProviderException e1) {
        throw new AssertionError(e1);
    }
} catch (NoSuchProviderException e) {
    // The SunJSSE provider should always be available.
    throw new AssertionError(e);
}
ctx.init(null, null, null);

In addition to the context, a TLS parameter object will be needed which adjusts the cipher suites and protocols (Setting up SSLParameters for TLS use with OpenJDK). Like the context, these parameters can be reused for multiple TLS connections.

Exemple 15. Setting up SSLParameters for TLS use with OpenJDK
// Prepare TLS parameters.  These have to applied to every TLS
// socket before the handshake is triggered.
SSLParameters params = ctx.getDefaultSSLParameters();
// Do not send an SSL-2.0-compatible Client Hello.
ArrayList<String> protocols = new ArrayList<String>(
    Arrays.asList(params.getProtocols()));
protocols.remove("SSLv2Hello");
params.setProtocols(protocols.toArray(new String[protocols.size()]));
// Adjust the supported ciphers.
ArrayList<String> ciphers = new ArrayList<String>(
    Arrays.asList(params.getCipherSuites()));
ciphers.retainAll(Arrays.asList(
    "TLS_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA",
    "TLS_RSA_WITH_AES_128_CBC_SHA",
    "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
    "SSL_RSA_WITH_RC4_128_SHA1",
    "SSL_RSA_WITH_RC4_128_MD5",
    "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"));
params.setCipherSuites(ciphers.toArray(new String[ciphers.size()]));

As initialized above, the parameter object does not yet require host name checking. This has to be enabled separately, and this is only supported by OpenJDK 7 and later:

params.setEndpointIdentificationAlgorithm("HTTPS");

All application protocols can use the "HTTPS" algorithm. (The algorithms have minor differences with regard to wildcard handling, which should not matter in practice.)

Establishing a TLS connection with OpenJDK shows how to establish the connection. Before the handshake is initialized, the protocol and cipher configuration has to be performed, by applying the parameter object params. (After this point, changes to params will not affect this TLS socket.) As mentioned initially, host name checking requires using an internal API on OpenJDK 6.

Exemple 16. Establishing a TLS connection with OpenJDK
// Create the socket and connect it at the TCP layer.
SSLSocket socket = (SSLSocket) ctx.getSocketFactory()
    .createSocket(host, port);

// Disable the Nagle algorithm.
socket.setTcpNoDelay(true);

// Adjust ciphers and protocols.
socket.setSSLParameters(params);

// Perform the handshake.
socket.startHandshake();

// Validate the host name.  The match() method throws
// CertificateException on failure.
X509Certificate peer = (X509Certificate)
    socket.getSession().getPeerCertificates()[0];
// This is the only way to perform host name checking on OpenJDK 6.
HostnameChecker.getInstance(HostnameChecker.TYPE_TLS).match(
    host, peer);

Starting with OpenJDK 7, the last lines can be omitted, provided that host name verification has been enabled by calling the setEndpointIdentificationAlgorithm method on the params object (before it was applied to the socket).

The TLS socket can be used as a regular socket, as shown in Using a TLS client socket in OpenJDK.

Exemple 17. Using a TLS client socket in OpenJDK
socket.getOutputStream().write("GET / HTTP/1.0\r\n\r\n"
    .getBytes(Charset.forName("UTF-8")));
byte[] buffer = new byte[4096];
int count = socket.getInputStream().read(buffer);
System.out.write(buffer, 0, count);

Overriding server certificate validation with OpenJDK 6

Overriding certificate validation requires a custom trust manager. With OpenJDK 6, the trust manager lacks information about the TLS session, and to which server the connection is made. Certificate overrides have to be tied to specific servers (host names). Consequently, different TrustManager and SSLContext objects have to be used for different servers.

In the trust manager shown in A customer trust manager for OpenJDK TLS clients, the server certificate is identified by its SHA-256 hash.

Exemple 18. A customer trust manager for OpenJDK TLS clients
public class MyTrustManager implements X509TrustManager {
    private final byte[] certHash;

    public MyTrustManager(byte[] certHash) throws Exception {
        this.certHash = certHash;
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType)
            throws CertificateException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain,
            String authType) throws CertificateException {
        byte[] digest = getCertificateDigest(chain[0]);
        String digestHex = formatHex(digest);

        if (Arrays.equals(digest, certHash)) {
            System.err.println("info: accepting certificate: " + digestHex);
        } else {
            throw new CertificateException("certificate rejected: "  +
                    digestHex);
        }
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}

This trust manager has to be passed to the init method of the SSLContext object, as show in Using a custom TLS trust manager with OpenJDK.

Exemple 19. Using a custom TLS trust manager with OpenJDK
SSLContext ctx;
try {
    ctx = SSLContext.getInstance("TLSv1.2", "SunJSSE");
} catch (NoSuchAlgorithmException e) {
    try {
        ctx = SSLContext.getInstance("TLSv1", "SunJSSE");
    } catch (NoSuchAlgorithmException e1) {
        throw new AssertionError(e1);
    } catch (NoSuchProviderException e1) {
        throw new AssertionError(e1);
    }
} catch (NoSuchProviderException e) {
    throw new AssertionError(e);
}
MyTrustManager tm = new MyTrustManager(certHash);
ctx.init(null, new TrustManager[] {tm}, null);

When certificate overrides are in place, host name verification should not be performed because there is no security requirement that the host name in the certificate matches the host name used to establish the connection (and it often will not). However, without host name verification, it is not possible to perform transparent fallback to certification validation using the system certificate store.

The approach described above works with OpenJDK 6 and later versions. Starting with OpenJDK 7, it is possible to use a custom subclass of the javax.net.ssl.X509ExtendedTrustManager class. The OpenJDK TLS implementation will call the new methods, passing along TLS session information. This can be used to implement certificate overrides as a fallback (if certificate or host name verification fails), and a trust manager object can be used for multiple servers because the server address is available to the trust manager.

Implementing TLS Clients With NSS

The following code shows how to implement a simple TLS client using NSS. These instructions apply to NSS version 3.14 and later. Versions before 3.14 need different initialization code.

Keep in mind that the error handling needs to be improved before the code can be used in production.

Using NSS needs several header files, as shown in Include files for NSS.

Exemple 20. Include files for NSS
// NSPR include files
#include <prerror.h>
#include <prinit.h>

// NSS include files
#include <nss.h>
#include <pk11pub.h>
#include <secmod.h>
#include <ssl.h>
#include <sslproto.h>

// Private API, no other way to turn a POSIX file descriptor into an
// NSPR handle.
NSPR_API(PRFileDesc*) PR_ImportTCPSocket(int);

Initializing the NSS library is shown in Initializing the NSS library. This initialization procedure overrides global state. We only call NSS_SetDomesticPolicy if there are no strong ciphers available, assuming that it has already been called otherwise. This avoids overriding the process-wide cipher suite policy unnecessarily.

The simplest way to configured the trusted root certificates involves loading the libnssckbi.so NSS module with a call to the SECMOD_LoadUserModule function. The root certificates are compiled into this module. (The PEM module for NSS, libnsspem.so, offers a way to load trusted CA certificates from a file.)

Exemple 21. Initializing the NSS library
PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
NSSInitContext *const ctx =
  NSS_InitContext("sql:/etc/pki/nssdb", "", "", "", NULL,
		    NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
if (ctx == NULL) {
  const PRErrorCode err = PR_GetError();
  fprintf(stderr, "error: NSPR error code %d: %s\n",
	    err, PR_ErrorToName(err));
  exit(1);
}

// Ciphers to enable.
static const PRUint16 good_ciphers[] = {
  TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
  TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
  TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
  TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
  TLS_RSA_WITH_AES_128_GCM_SHA256,
  TLS_RSA_WITH_AES_256_GCM_SHA384,
  TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
  TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
  TLS_RSA_WITH_AES_128_CBC_SHA,
  TLS_RSA_WITH_AES_256_CBC_SHA,
  SSL_RSA_WITH_3DES_EDE_CBC_SHA,
  SSL_NULL_WITH_NULL_NULL // sentinel
};

// Check if the current policy allows any strong ciphers.  If it
// doesn't, set the cipher suite policy.  This is not thread-safe
// and has global impact.  Consequently, we only do it if absolutely
// necessary.
int found_good_cipher = 0;
for (const PRUint16 *p = good_ciphers; *p != SSL_NULL_WITH_NULL_NULL;
     ++p) {
  PRInt32 policy;
  if (SSL_CipherPolicyGet(*p, &policy) != SECSuccess) {
    const PRErrorCode err = PR_GetError();
    fprintf(stderr, "error: policy for cipher %u: error %d: %s\n",
	      (unsigned)*p, err, PR_ErrorToName(err));
    exit(1);
  }
  if (policy == SSL_ALLOWED) {
    fprintf(stderr, "info: found cipher %x\n", (unsigned)*p);
    found_good_cipher = 1;
    break;
  }
}
if (!found_good_cipher) {
  if (NSS_SetDomesticPolicy() != SECSuccess) {
    const PRErrorCode err = PR_GetError();
    fprintf(stderr, "error: NSS_SetDomesticPolicy: error %d: %s\n",
	      err, PR_ErrorToName(err));
    exit(1);
  }
}

// Initialize the trusted certificate store.
char module_name[] = "library=libnssckbi.so name=\"Root Certs\"";
SECMODModule *module = SECMOD_LoadUserModule(module_name, NULL, PR_FALSE);
if (module == NULL || !module->loaded) {
  const PRErrorCode err = PR_GetError();
  fprintf(stderr, "error: NSPR error code %d: %s\n",
	    err, PR_ErrorToName(err));
  exit(1);
}

Some of the effects of the initialization can be reverted with the following function calls:

SECMOD_DestroyModule(module);
NSS_ShutdownContext(ctx);

After NSS has been initialized, the TLS connection can be created (Creating a TLS connection with NSS). The internal PR_ImportTCPSocket function is used to turn the POSIX file descriptor sockfd into an NSPR file descriptor. (This function is de-facto part of the NSS public ABI, so it will not go away.) Creating the TLS-capable file descriptor requires a model descriptor, which is configured with the desired set of protocols. The model descriptor is not needed anymore after TLS support has been activated for the existing connection descriptor.

The call to SSL_BadCertHook can be omitted if no mechanism to override certificate verification is needed. The bad_certificate function must check both the host name specified for the connection and the certificate before granting the override.

Triggering the actual handshake requires three function calls, SSL_ResetHandshake, SSL_SetURL, and SSL_ForceHandshake. (If SSL_ResetHandshake is omitted, SSL_ForceHandshake will succeed, but the data will not be encrypted.) During the handshake, the certificate is verified and matched against the host name.

Exemple 22. Creating a TLS connection with NSS
// Wrap the POSIX file descriptor.  This is an internal NSPR
// function, but it is very unlikely to change.
PRFileDesc* nspr = PR_ImportTCPSocket(sockfd);
sockfd = -1; // Has been taken over by NSPR.

// Add the SSL layer.
{
  PRFileDesc *model = PR_NewTCPSocket();
  PRFileDesc *newfd = SSL_ImportFD(NULL, model);
  if (newfd == NULL) {
    const PRErrorCode err = PR_GetError();
    fprintf(stderr, "error: NSPR error code %d: %s\n",
	      err, PR_ErrorToName(err));
    exit(1);
  }
  model = newfd;
  newfd = NULL;
  if (SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE) != SECSuccess) {
    const PRErrorCode err = PR_GetError();
    fprintf(stderr, "error: set SSL_ENABLE_SSL2 error %d: %s\n",
	      err, PR_ErrorToName(err));
    exit(1);
  }
  if (SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE) != SECSuccess) {
    const PRErrorCode err = PR_GetError();
    fprintf(stderr, "error: set SSL_V2_COMPATIBLE_HELLO error %d: %s\n",
	      err, PR_ErrorToName(err));
    exit(1);
  }
  if (SSL_OptionSet(model, SSL_ENABLE_DEFLATE, PR_FALSE) != SECSuccess) {
    const PRErrorCode err = PR_GetError();
    fprintf(stderr, "error: set SSL_ENABLE_DEFLATE error %d: %s\n",
	      err, PR_ErrorToName(err));
    exit(1);
  }

  // Allow overriding invalid certificate.
  if (SSL_BadCertHook(model, bad_certificate, (char *)host) != SECSuccess) {
    const PRErrorCode err = PR_GetError();
    fprintf(stderr, "error: SSL_BadCertHook error %d: %s\n",
	      err, PR_ErrorToName(err));
    exit(1);
  }

  newfd = SSL_ImportFD(model, nspr);
  if (newfd == NULL) {
    const PRErrorCode err = PR_GetError();
    fprintf(stderr, "error: SSL_ImportFD error %d: %s\n",
	      err, PR_ErrorToName(err));
    exit(1);
  }
  nspr = newfd;
  PR_Close(model);
}

// Perform the handshake.
if (SSL_ResetHandshake(nspr, PR_FALSE) != SECSuccess) {
  const PRErrorCode err = PR_GetError();
  fprintf(stderr, "error: SSL_ResetHandshake error %d: %s\n",
	    err, PR_ErrorToName(err));
  exit(1);
}
if (SSL_SetURL(nspr, host) != SECSuccess) {
  const PRErrorCode err = PR_GetError();
  fprintf(stderr, "error: SSL_SetURL error %d: %s\n",
	    err, PR_ErrorToName(err));
  exit(1);
}
if (SSL_ForceHandshake(nspr) != SECSuccess) {
  const PRErrorCode err = PR_GetError();
  fprintf(stderr, "error: SSL_ForceHandshake error %d: %s\n",
	    err, PR_ErrorToName(err));
  exit(1);
}

After the connection has been established, Using NSS for sending and receiving data shows how to use the NSPR descriptor to communicate with the server.

Exemple 23. Using NSS for sending and receiving data
char buf[4096];
snprintf(buf, sizeof(buf), "GET / HTTP/1.0\r\nHost: %s\r\n\r\n", host);
PRInt32 ret = PR_Write(nspr, buf, strlen(buf));
if (ret < 0) {
  const PRErrorCode err = PR_GetError();
  fprintf(stderr, "error: PR_Write error %d: %s\n",
	    err, PR_ErrorToName(err));
  exit(1);
}
ret = PR_Read(nspr, buf, sizeof(buf));
if (ret < 0) {
  const PRErrorCode err = PR_GetError();
  fprintf(stderr, "error: PR_Read error %d: %s\n",
	    err, PR_ErrorToName(err));
  exit(1);
}

Closing NSS client connections shows how to close the connection.

Exemple 24. Closing NSS client connections
// Send close_notify alert.
if (PR_Shutdown(nspr, PR_SHUTDOWN_BOTH) != PR_SUCCESS) {
  const PRErrorCode err = PR_GetError();
  fprintf(stderr, "error: PR_Read error %d: %s\n",
	    err, PR_ErrorToName(err));
  exit(1);
}
// Closes the underlying POSIX file descriptor, too.
PR_Close(nspr);

Implementing TLS Clients With Python

The Python distribution provides a TLS implementation in the ssl module (actually a wrapper around OpenSSL). The exported interface is somewhat restricted, so that the client code shown below does not fully implement the recommendations in OpenSSL Pitfalls.

Currently, most Python function which accept https:// URLs or otherwise implement HTTPS support do not perform certificate validation at all. (For example, this is true for the httplib and xmlrpclib modules.) If you use HTTPS, you should not use the built-in HTTP clients. The Curl class in the curl module, as provided by the python-pycurl package implements proper certificate validation.

The ssl module currently does not perform host name checking on the server certificate. Implementing TLS host name checking Python (without wildcard support) shows how to implement certificate matching, using the parsed certificate returned by getpeercert.

Exemple 25. Implementing TLS host name checking Python (without wildcard support)
def check_host_name(peercert, name):
    """Simple certificate/host name checker.  Returns True if the
    certificate matches, False otherwise.  Does not support
    wildcards."""
    # Check that the peer has supplied a certificate.
    # None/{} is not acceptable.
    if not peercert:
        return False
    if peercert.has_key("subjectAltName"):
        for typ, val in peercert["subjectAltName"]:
            if typ == "DNS" and val == name:
                return True
    else:
        # Only check the subject DN if there is no subject alternative
        # name.
        cn = None
        for attr, val in peercert["subject"]:
            # Use most-specific (last) commonName attribute.
            if attr == "commonName":
                cn = val
        if cn is not None:
            return cn == name
    return False

To turn a regular, connected TCP socket into a TLS-enabled socket, use the ssl.wrap_socket function. The function call in Establishing a TLS client connection with Python provides additional arguments to override questionable defaults in OpenSSL and in the Python module.

  • ciphers="HIGH:-aNULL:-eNULL:-PSK:RC4-SHA:RC4-MD5" selects relatively strong cipher suites with certificate-based authentication. (The call to check_host_name function provides additional protection against anonymous cipher suites.)

  • ssl_version=ssl.PROTOCOL_TLSv1 disables SSL 2.0 support. By default, the ssl module sends an SSL 2.0 client hello, which is rejected by some servers. Ideally, we would request OpenSSL to negotiated the most recent TLS version supported by the server and the client, but the Python module does not allow this.

  • cert_reqs=ssl.CERT_REQUIRED turns on certificate validation.

  • ca_certs='/etc/ssl/certs/ca-bundle.crt' initializes the certificate store with a set of trusted root CAs. Unfortunately, it is necessary to hard-code this path into applications because the default path in OpenSSL is not available through the Python ssl module.

The ssl module (and OpenSSL) perform certificate validation, but the certificate must be compared manually against the host name, by calling the check_host_name defined above.

Exemple 26. Establishing a TLS client connection with Python
sock = ssl.wrap_socket(sock,
                       ciphers="HIGH:-aNULL:-eNULL:-PSK:RC4-SHA:RC4-MD5",
                       ssl_version=ssl.PROTOCOL_TLSv1,
                       cert_reqs=ssl.CERT_REQUIRED,
                       ca_certs='/etc/ssl/certs/ca-bundle.crt')
# getpeercert() triggers the handshake as a side effect.
if not check_host_name(sock.getpeercert(), host):
    raise IOError("peer certificate does not match host name")

After the connection has been established, the TLS socket can be used like a regular socket:

sock.write("GET / HTTP/1.1\r\nHost: " + host + "\r\n\r\n")
print sock.read()

Closing the TLS socket is straightforward as well:

sock.close()