Using TLS 1.3 with Phoenix

TLS 1.3 has been out for a little over a year now, but it has been unavailable in Phoenix due to erlang’s handling of ssl. With the most recent version of erlang released (22.2.3) these issues should now be solved. I’ve spent a bit of time going down the rabbit hole of getting our servers to run the protocol so our end users can get better performance and security. If you’d like to upgrade follow the directions below and let me know in the comments if you run into any issues.

YOU MUST BE RUNNING ERLANG 22.2.3 OR THIS WILL NOT WORK

Within your endpoint configuration copy/paste the following code within the :https keyword

   https: [
     ...
      honor_cipher_order: true,
      ciphers: [
        'TLS_AES_128_GCM_SHA256',
        'TLS_AES_256_GCM_SHA384',
        'TLS_CHACHA20_POLY1305_SHA256',
        'ECDHE-ECDSA-AES128-GCM-SHA256',
        'ECDHE-RSA-AES128-GCM-SHA256',
        'ECDHE-ECDSA-AES256-GCM-SHA384',
        'ECDHE-RSA-AES256-GCM-SHA384',
        'ECDHE-ECDSA-CHACHA20-POLY1305',
        'ECDHE-RSA-CHACHA20-POLY1305',
        'DHE-RSA-AES128-GCM-SHA256',
        'DHE-RSA-AES256-GCM-SHA384'
      ],
      eccs: [
        :x25519,
        :secp256r1,
        :secp384r1
      ],
      secure_renegotiate: true,
      reuse_sessions: true,
      versions: [:"tlsv1.3", :"tlsv1.2"],
      ...
    ]

Cipher and eccs are based off of the work done in OWASP Cipher String Cheat
Sheet
and Mozilla’s Server Side TLS v5.3. This should give you compatibility with almost all modern devices and should lead to an A+ rating in SSL Labs and Immuniweb

Let me know if you have any questions or run into any problems if you use this config for your project.

25 Likes

WARNING

It looks like there are errors if you make a connection using secure websockets. I’m uncertain if this is within phoenix, or erlang but the configuration above shouldn’t have to change for things to work out of the box. I’m going to dig into the errors and will report back in this thread if/when there is news on a fix.

4 Likes

I’m watching this thread with anticipation. ^.^

Thanks for your work on this!

1 Like

Has anyone managed to make a HTTPoison call using TLS1.3 (over nginx)? And using X25519 generated certs?

Ok, so after a few months of waiting and filing bug reports it looks like everything works when using erlang 23.0.0 or greater.

1 Like

Are we likely to see a TLS 1.3 enabled cipher suite soon, or shall one simply copy/paste the settings above?

I can reopen a pull request to plug, but I’m not sure when it will be merged since we require the use of erlang 23.0.0 or greater. To get this working today you would need to copy/paste the above code.

2 Likes

Thanks for the response. FYI, with Elrang 23.0.2 and the latest Elixir/Phoenix packages on Debian Stretch I am getting:

Failed to start Ranch listener PrjWeb.Endpoint.HTTPS in :ranch_ssl:listen([cacerts: :…, key: :…, cert: :…, alpn_preferred_protocols: [“h2”, “http/1.1”], next_protocols_advertised: [“h2”, “http/1.1”], dhfile: ‘/home/[…]’, cacertfile: ‘/etc/[…]’, certfile: ‘/etc/[…]’, keyfile: ‘/etc/[…]’, port: 8443, ciphers: […], eccs: [:x25519, :secp256r1, :secp384r1], honor_cipher_order: true, reuse_sessions: true, secure_renegotiate: true, versions: [:“tlsv1.3”, :“tlsv1.2”]]) for reason {:options, {:insufficient_crypto_support, {:“tlsv1.3”, {:versions, [:“tlsv1.3”, :“tlsv1.2”]}}}}

Oh well, I suppose TLS 1.3 can wait a bit longer.

What version of ranch are you using?

I am not using ranch directly, mix deps.tree prints:

|-- plug_cowboy ~> 2.1 (Hex package)
| |-- cowboy ~> 2.7 (Hex package)
| | |-- cowlib ~> 2.9.1 (Hex package)
| | `-- ranch ~> 1.7.1 (Hex package)

Update to cowboy 2.8

This error is coming from Erlang/OTP’s ssl application, not from Ranch or Cowboy. It means OTP was compiled against an OpenSSL version that does not support the necessary primitives to enable TLS 1.3. It seems OTP won’t enable TLS1.3 unless Chacha20-Poly1305 and Curve25519 are both available (even though strictly speaking both are SHOULD requirements in the RFC, not MUST).

1 Like

Adding {:cowboy, “~> 2.8”, override: true} to mix.exs does not seem to make a difference. From my trials, the above settings only work when :“tlsv1.3” is removed from the versions list.

I’ve been reading this thread with interest as we would also like to add TLS1.3 capability to our web application. It seems we have come across the same error as @konstantine, and aren’t able to get the underlying crypto support required to get this going out of the box.
@voltone - would you be able to suggest any tweaks to our OpenSSL or OTP installation, to enable support for the required ciphers?

Well, you’re going to need a recent version of OpenSSL for your target environment. How to get it depends on your OS/distribution.

When building OTP from source, either manually or using asdf/kerl, keep in mind that you may have multiple versions of OpenSSL installed, so you may have to use the --with-ssl=/path/to/openssl build option to select the correct one. With asdf/kerl you can do that by setting KERL_CONFIGURE_OPTIONS=--with-ssl=/path/to/openssl.

The requirements for enabling TLS 1.3 in OTP are listed here. If you want to find out which specific algorithm is missing you’re going to have to check the output of :crypto.supports() against that list manually.

If you get stuck, include your OS details, how you’ve installed OpenSSL and Erlang/OTP, what versions, and the algorithms that appear to be missing, so you can compare notes with others who may have had more luck with a similar environment.

1 Like

Thank you! I checked the list and sure enough one of the eccs options was missing in my env. However, since I was testing this in a Desktop environment, I then checked it against the list running on the embedded device and the ciphers and supports were all there.
Unfortunately, although TLS1.3 does appear to work when it’s initiated from the Browser (I also verified network traffic for TLS1.3 handshaking), I’m getting a very strange error when I try to set TLS1.3 as the minimum or main security level.

Start Call: :ranch_conns_sup_sup.start_link(MyEndpoint.HTTPS, :ranch_ssl, :cowboy_tls, :logger)
Restart: :permanent
Shutdown: :infinity
Type: :supervisor
18:53:23.613: [error] iex : Failed to start Ranch listener MyEndpoint.HTTPS in :ranch_ssl:listen(%{max_connections: 16384, num_acceptors: 12, socket_opts: [cacerts: :..., key: :..., cert: :..., alpn_preferred_protocols: ["h2", "http/1.1"], next_protocols_advertised: ["h2", "http/1.1"], reuse_sessions: true, secure_renegotiate: true, certfile: '/ssl/self_signed_ssl_cert.pem', keyfile: '/ssl/self_signed_ssl_key.pem', port: 443, ciphers: [{:any, :aes_256_gcm, :aead, :sha384}, {:any, :aes_128_gcm, :aead, :sha256}, {:any, :chacha20_poly1305, :aead, :sha256}, {:any, :aes_128_ccm, :aead, :sha256}, {:any, :aes_128_ccm_8, :aead, :sha256}, {:ecdhe_ecdsa, :aes_256_gcm, :aead, :sha384}, {:ecdhe_rsa, :aes_256_gcm, :aead, :sha384}, {:ecdhe_ecdsa, :aes_256_cbc, :sha384, :sha384}, {:ecdhe_rsa, :aes_256_cbc, :sha384, :sha384}, {:ecdh_ecdsa, :aes_256_gcm, :aead, :sha384}, {:ecdh_rsa, :aes_256_gcm, :aead, :sha384}, {:ecdh_ecdsa, :aes_256_cbc, :sha384, :sha384}, {:ecdh_rsa, :aes_256_cbc, :sha384, :sha384}, {:dhe_rsa, :aes_256_gcm, :aead, :sha384}, {:dhe_dss, :aes_256_gcm, :aead, :sha384}, {:dhe_rsa, :aes_256_cbc, :sha256}, {:dhe_dss, :aes_256_cbc, :sha256}, {:ecdhe_ecdsa, :aes_128_gcm, :aead, :sha256}, {:ecdhe_rsa, :aes_128_gcm, :aead, :sha256}, {:ecdhe_ecdsa, :chacha20_poly1305, :aead, :sha256}, {:ecdhe_rsa, :chacha20_poly1305, :aead, :sha256}, {:ecdhe_ecdsa, :aes_128_cbc, :sha256, :sha256}, {:ecdhe_rsa, :aes_128_cbc, :sha256, :sha256}, {:ecdh_ecdsa, :aes_128_gcm, :aead, :sha256}, {:ecdh_rsa, :aes_128_gcm, :aead, :sha256}, {:ecdh_ecdsa, :aes_128_cbc, :sha256, :sha256}, {:ecdh_rsa, :aes_128_cbc, :sha256, :sha256}, {:dhe_rsa, :aes_128_gcm, :aead, :sha256}, {:dhe_dss, :aes_128_gcm, :aead, :sha256}, {:dhe_rsa, :chacha20_poly1305, :aead, :sha256}, {:dhe_rsa, :aes_128_cbc, :sha256}, {:dhe_dss, :aes_128_cbc, :sha256}, {:ecdhe_ecdsa, :aes_256_cbc, :sha}, {:ecdhe_rsa, :aes_256_cbc, ...}, {:dhe_rsa, ...}, {...}, ...], versions: [:"tlsv1.3"], ip: {0, 0, 0, 0, 0, 0, 0, 0}, honor_cipher_order: true]}) for reason {:options, :dependency, {:secure_renegotiate, {:versions, [:tlsv1, :"tlsv1.1", :"tlsv1.2"]}}} (unknown POSIX error)

18:53:23.616: [error] supervisor.start_children/2 : Child :ranch_acceptors_sup of Supervisor #PID<0.19763.0> (:ranch_listener_sup) failed to start
** (exit) {:listen_error, MyEndpoint.HTTPS, **{:options, :dependency, {:secure_renegotiate, {:versions, [:tlsv1, :"tlsv1.1", :"tlsv1.2"]}}}}**
Start Call: :ranch_acceptors_sup.start_link(MyEndpoint.HTTPS, :ranch_ssl, :logger)
Restart: :permanent
Shutdown: :infinity
Type: :supervisor
18:53:23.633: [error] proc_lib.crash_report/4 : Process #PID<0.19777.0> terminating
** (exit) {:listen_error, MyEndpoint.HTTPS, {:options, :dependency, {:secure_renegotiate, {:versions, [:tlsv1, :"tlsv1.1", :"tlsv1.2"]}}}}
    (ranch 2.0.0) /workdir/firmware/deps/lces2/ranch/src/ranch_acceptors_sup.erl:95: :ranch_acceptors_sup.listen_error/5
    (ranch 2.0.0) /workdir/firmware/deps/lces2/ranch/src/ranch_acceptors_sup.erl:54: :ranch_acceptors_sup.start_listen_sockets/5
    (ranch 2.0.0) /workdir/firmware/deps/lces2/ranch/src/ranch_acceptors_sup.erl:34: :ranch_acceptors_sup.init/1
    (stdlib 3.13) supervisor.erl:301: :supervisor.init/1
    (stdlib 3.13) gen_server.erl:417: :gen_server.init_it/2
    (stdlib 3.13) gen_server.erl:385: :gen_server.init_it/6
    (stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Initial Call: :ranch_acceptors_sup.init/1

I updated ranch to 2.0 with a minor change to make it work with plug_cowboy 2.8. However, I still get that error. I get the feeling it’s looking to support only [:tlsv1, :“tlsv1.1”, :“tlsv1.2”] but I’m not sure where it’s getting that list from, as the underlying ssl lib seems to have tlsv1.3 support.

I think it’s trying to tell you that the :secure_renegotiate option is specific to prior versions of TLS, and it is not applicable to the TLS version you selected. A bit annoying that it this is considered a fatal error rather than just a warning. Try overriding the :ssl options and not setting :secure_renegotiate (or setting it to false)

Thank you very much for that hint. You were absolutely right, and as soon as I removed :secure_renegotiate it threw up exact same error for the next 3 options as I fixed them:

  • reuse_sessions,
  • next_protocols_advertised,
  • alpn_preferred_protocols

Unfortunately, it seems that setting these options to false is not enough and I had to remove them in ranch before running ssl:listen. It’s probably possible to also do the same by removing the forced addition of these options in Plug and Plug Cowboy packages.

I opened an issue on Plug.

4 Likes

@tomciopp This config does not work for erlang/otp 24! Could you help me to find some clue to find out what should i provide to use TLS1.3?