Curl with TLSv1.3 and openSSL on macOS

This took me some time to figure out as I couldn’t find that much resources about it online. It all starts with me trying to be a good citizen of the web and use a modern configuration for my web server. Mozilla provides a configuration service where the “modern” one only support TLSv1.3. This is great, I think. Maybe not for everyone, right now, if you want to support old browsers, but for my use case it’s great. I enabled it on one of my sites to start with and tested it in Firefox and called it a day. A couple of weeks later I tried it with curl on my macOS Mojave machine.

$ curl --tlsv1.3 https://bolmaster2.com
curl: (4) LibreSSL was built without TLS 1.3 support

Hm. I thought this should work. Here I’m using the pre-shipped curl with macOS Mojave which is using LibreSSL 2.6.5. Check it by running:

$ curl --version
curl 7.54.0 (x86_64-apple-darwin18.0) libcurl/7.54.0 LibreSSL/2.6.5 zlib/1.2.11 nghttp2/1.24.1
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz HTTP2 UnixSockets HTTPS-proxy

It turns out that LibreSSL doesn’t have support for TLSv1.3.

Building curl with openssl

So, I thought: why isn’t curl using openssl? I’m using openssl on my dev machine, installed with homebrew, so I should just be able to use that. One thing that’s easy to miss here is that homebrew’s default openssl formula is using version 1.0.2 and openssl first started to support TLSv1.3 on version 1.1.1. Homebrew has a separate formula for openssl 1.1.1 called [email protected].

So you need to use openssl 1.1.1. That shouldn’t be a problem. Compiling curl with openssl 1.1.1 was a bit of a PITA because of curl’s dependencies was also needed to be compiled with openssl 1.1.1. Anyway, the homebrew maintainers are starting to move formulas to compile with [email protected]. So when you read this, there’s probably not gonna be a problem. Then you should just be able to run:

brew install curl-openssl

UPDATE: The homebrew-core curl-openssl formula now uses [email protected]. All is needed to use TLSv1.3 with curl is the above command ✅

Now when you run:

/usr/local/opt/curl-openssl/bin/curl --version

it should yield something like this:

curl 7.65.3 (x86_64-apple-darwin18.7.0) libcurl/7.65.3 OpenSSL/1.1.1c zlib/1.2.11 brotli/1.0.7 c-ares/1.15.0 libidn2/2.2.0 libssh2/1.9.0 nghttp2/1.39.2 librtmp/2.3
Release-Date: 2019-07-19
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS brotli GSS-API HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz Metalink NTLM NTLM_WB SPNEGO SSL TLS-SRP UnixSockets

The important part is that is says OpenSSL/1.1.1c.

Add to PATH

To be able to reach the new version with curl command you need to add it to your PATH. Depending on what shell you use this will look differently:

# Using bash
echo 'export PATH="/usr/local/opt/curl-openssl/bin/:$PATH"' >> ~/.bashrc
# Using zsh
echo 'export PATH="/usr/local/opt/curl-openssl/bin/:$PATH"' >> ~/.zshrc

Restart your terminal and now you should get the same output as before with:

curl --version

And finally TLSv1.3 should work 😅:

$ curl --tlsv1.3 -v https://bolmaster2.com
*   Trying 2606:4700:3034::6812:3865:443...
* Connected to bolmaster2.com (2606:4700:3034::6812:3865) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /usr/local/etc/[email protected]/cert.pem
  CApath: /usr/local/etc/[email protected]/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=CA; L=San Francisco; O=Cloudflare, Inc.; CN=sni.cloudflaressl.com
*  start date: Oct  8 00:00:00 2019 GMT
*  expire date: Oct  7 12:00:00 2020 GMT
*  subjectAltName: host "bolmaster2.com" matched cert's "bolmaster2.com"
*  issuer: C=US; ST=CA; L=San Francisco; O=CloudFlare, Inc.; CN=CloudFlare Inc ECC CA-2
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7f8ac6801400)
> GET / HTTP/2
> Host: bolmaster2.com
> user-agent: curl/7.65.3
> accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* Connection state changed (MAX_CONCURRENT_STREAMS == 256)!
...

Resources