libtls: rethinking the TLS/SSL API

Making it easier and safer to write applications that use TLS


Joel Sing <[email protected]>

linux.conf.au 2017, Hobart, Australia

What is libtls?

A bit of history...

April 2014 - LibreSSL

A bit of history...

July 2014 - libtls

"Hey, we should really create a better API."

TLS is complex and messy!

It should be easy to use...

But I just want to write an application that has TLS support!

Why not just use existing APIs?

Let's write some code...

Presuming that we've already done the work to resolve DNS and establish a connected socket:

if ((ssl_ctx = SSL_CTX_new(SSLv23_method())) == NULL)
        errx(1, "failed to create ssl context");
if ((ssl = SSL_new(ssl_ctx)) == NULL)
        errx(1, "failed to create ssl");

if (SSL_set_fd(ssl, s) != 1)
        errx(1, "failed to set fd");

if (SSL_connect(ssl) != 1)
        errx(1, "failed to connect");

if (SSL_write(ssl, buf, len) != len)
        errx(1, "SSL_write failed");
if ((ret = SSL_read(ssl, buf, sizeof(buf))) <= 0)
        errx(1, "SSL_read failed");

if (SSL_shutdown(ssl) != 1)
        errx(1, "SSL_shutdown failed");
      

What just happened?

We should really fix that...

Certificate Verification

We should really fix that...

Server Name Verification

libtls design philosophy

The libtls API

For full details read the man page, but in summary:

tls_init()

tls_config_new()
tls_config_set*()

tls_client() or tls_server()
tls_configure()
tls_connect*() or tls_accept*()
tls_handshake() ]
tls_read()/tls_write()
tls_close()
      

Let's rewrite that earlier code...

if ((ctx = tls_client()) == NULL)
        err(1, "failed to create client");
if (tls_configure(ctx, NULL) == -1)
        err(1, "failed to configure: %s", tls_error(ctx));

if (tls_connect(ctx, "www.linux.conf.au", "https") == -1)
        err(1, "failed to connect: %s", tls_error(ctx));

if (tls_write(ctx, buf, len) != len)
        errx(1, "failed to write: %s", tls_error(ctx));
if ((ret = tls_read(ctx, buf, sizeof(buf))) <= 0)
        errx(1, "failed to read: %s", tls_error(ctx));

if (tls_close(ctx) == -1)
        errx(1, "failed to close: %s", tls_error(ctx));
      

What just happened?

The libtls API design

General API design rules

Some more API examples

Application-Layer Protocol Negotiation (ALPN)

libssl

After you've converted and packed your ALPN protocols into wire format (length prefixed strings)...

static int
tls_server_alpn_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen,
    const unsigned char *in, unsigned int inlen, void *arg)
{
        struct tls *ctx = arg;

        if (SSL_select_next_proto((unsigned char**)out, outlen,
            ctx->config->alpn, ctx->config->alpn_len, in, inlen) ==
            OPENSSL_NPN_NEGOTIATED)
                return (SSL_TLSEXT_ERR_OK);

        return (SSL_TLSEXT_ERR_NOACK);
}
...
SSL_CTX_set_alpn_select_cb(*ssl_ctx, tls_server_alpn_cb, ctx);
      

Application-Layer Protocol Negotiation (ALPN)

libtls

if (tls_config_set_alpn(server_cfg, "h2,http/1.1") == -1)
        err(...);
      

Server Name Indication (SNI) - Server Side

libssl

For each additional certificate:

Register a callback that:

Server Name Indication (SNI) - Server Side

libtls

For each additional certificate:

if (tls_config_add_keypair_file(server_cfg, cert_path, key_path) == -1)
        err(...);
      

A quick look at return codes and error handling

libssl

If one of the following functions returns <= 0, call SSL_get_error().

ret = SSL_connect(ssl);
if (ret <= 0) {
       ssl_err = SSL_get_error(ssl, ret);
       switch (ssl_err) {
       case SSL_ERROR_NONE:
       case SSL_ERROR_ZERO_RETURN:
               break;

       case SSL_ERROR_WANT_READ:  
       case SSL_ERROR_WANT_WRITE:
       case SSL_ERROR_WANT_CONNECT:
       case SSL_ERROR_WANT_ACCEPT:
               continue;

       case SSL_ERROR_WANT_X509_LOOKUP:
               errx(1, "want x509 lookup");

       case SSL_ERROR_SSL:
               errstr = "unknown";
               if ((ssl_err_code = ERR_peek_error()) != 0)
                       errstr = ERR_error_string(ssl_err_code, NULL);
               errx(1, "SSL error: %s", errstr);

       case SSL_ERROR_SYSCALL:
               errstr = "unknown";
               if ((ssl_err_code = ERR_peek_error()) != 0)
                       errstr = ERR_error_string(ssl_err_code, NULL);
               else if (ret == -1)
                       errstr = strerror(errno);
               errx(1, "SSL error: %s", errstr);
       }
}
      

A quick look at return codes and error handling

libtls

If a function returns -1, call tls_config_error() or tls_error() to find out what went wrong.

The following functions, also have two special return values TLS_WANT_POLLIN and TLS_WANT_POLLOUT.

So what is wrong with a bad API?

But it was this way when I found it!

Bad APIs lead to:

So what is wrong with a bad API?

"A principled solution to the problem must involve a complete redesign of the SSL libraries' API"

The most dangerous code in the world: validating SSL certificates in non-browser software (2012 paper)

So what is wrong with a bad API?

"The failure of various applications to note Python's negligence in this matter is a source of regular CVE assignment"

PEP 476 - Enabling certificate verification by default for stdlib http clients (2014)

Cites 11 CVEs in various applications, all due to bad default behaviour.

Some examples of how not to do things

SSL_CIPHER_description

desc = SSL_CIPHER_description(cipher, NULL, 0);
      

Some examples of how not to do things

SSL_CIPHER_description

Must strcmp to determine what to do!

desc = SSL_CIPHER_description(cipher, NULL, 0);
if (strcmp(desc, "OPENSSL_malloc Error") == 0) {
        fprintf(stderr, "out of memory\n");
        goto err;
}
fprintf(stdout, "%s", desc);
free(desc);
      

Some examples of how not to do things

SSL_CIPHER_description

Some lessons to learn from this:

Some examples of how not to do things

X509_verify_cert()

But wait, there's more!

Call X509_STORE_CTX_get_error() to get the internal error code, which might:

  • Be X509_V_OK
  • Indicate an internal error (e.g. out of memory)
  • Indicate a verification failure (e.g. unknown issuer)

Some examples of how not to do things

X509_verify_cert()

Some examples of how not to do things

X509_verify_cert()

Some lessons to learn from this:

OpenBSD: our development playground

In summary

Thank you!

www.libressl.org

/