Catching up

Posted 2017-02-27 09:28:27.000000

Before I start a new series of posts, in which I’ll be changing gears a little, I want to tie up some loose ends from the previous post.

Expired and revoked

In that last post I mentioned some testing endpoints I had setup of for checking the certificate verification logic of HTTPS clients:

UPDATE: Please note that the test endpoints are no longer available!

I have now added two more endpoints:

The expired endpoint uses an otherwise valid certificate, issued by Let’s Encrypt and with the correct hostname, that has expired. The revoked endpoint, as you can guess, uses a certificate that has been revoked by the CA. Some poeple would argue that certificate revocation is broken by design, so our mileage with that last one may vary.

Please note that I do not intend to maintain these endpoints indefinitely: they should be working at the time of writing, but I reserve the right to abandon or disable them at any time. If you’re adding TLS handshake testing to your integration test suite (excellent idea!), please find another service out there, or even better: build your own. Which brings me to the next topic…

Phoenix as a TLS test server

It is really quite simple to configure a simple Phoenix application to be a TLS testing endpoint. I’m going to assume you have some certificates available for testing, either from a real CA (like I did with Let’s Encrypt) or from your own private/corporate PKI. I will be using as the domain name, which is useful for testing on the loopback interface, but you may want to set up a domain and some subdomains of your own if you want something more permanent or if you’re using CA-issued certificates.

Start by creating a simple app, without a database and without JS asset management:

mix test_tls --module TestTLS --no-ecto --no-brunch

Now open the config/dev.exs (and/or config/prod.exs) file, and replace the default http key with this https configuration:

config :test_tls, TestTLS.Endpoint,
  https: [
    port: 4001,
    sni_fun: &TestTLS.Endpoint.ssloptions/1,
    keyfile: "priv/cert/privkey.pem",
    certfile: "priv/cert/cert.pem",
    cacertfile: "priv/cert/chain.pem",
    secure_renegotiate: true,
    honor_cipher_order: true
  # ...snip...

Add the following function clauses to the Endpoint module:

def ssloptions('') do
    certfile: 'priv/cert/selfsigned.pem',
    keyfile: 'priv/cert/selfsigned_key.pem'
# Catch-all clause at the end, using defaults
def ssloptions(_hostname), do: []

Once you’ve placed the certificates in the priv/cert directory and you start the Phoenix app, you should be able to connect to and get the default certificate, and to get the alternative certificate instead.

But wait, there is more: the ssloptions/1 callback can return almost any of the options that Erlang’s :ssl module supports for server-side configuration. For instance, you can add a hostname that requires the client to support TLS v1.2:

def ssloptions('') do
    versions: [:'tlsv1.2']

Or a hostname that supports only a narrow set of cipher suites:

def ssloptions('') do
    ciphers: Enum.filter(:ssl.cipher_suites(),
      & elem(&1, 0) in [:ecdhe_rsa, :ecdhe_ecdsa])

Connection closed by IIS

Finally, a note on the {:error, :closed} response that people have reported when interworking with some Microsoft IIS hosts using Erlang/OTP 18.3.2 onward.

The root cause is described here. Basically, if the server is configured with a SHA256 certificate (as it should), the server aborts the TLS handshake without so much as an Alert response if it believes the client does not support SHA256.

Unfortunately, Erlang’s :ssl module only includes the TLS extension for indicating support for ‘advanced’ signature algorithms in its handshake message if it can be sure the connection will use a TLS version that supports it. When using the default settings, with TLS 1.0 through 1.2 enabled, the extension is not sent, and MS IIS assumes SHA256 certificates are not supported.

The workaround is to force the handshake to use TLS 1.2. This is probably a good idea in any case, since older TLS versions have some known weaknesses.