:ssl and :httpc in Erlang/Elixir
Source
This text is a short (very) summary on ElixirConfEU 2019 speech about ssl in Erlang from Bram Verburg. More to have copy-paste code than to explain anything.
To get a proper explanation on that topic I highly recommend watching the video.
Problem
Problem is, Erlang :ssl
is somewhat complicated. Especially for guys like me who knows almost nothing about SSL/TLS.
By default :ssl
(and :httpc
) supports TLS connection (successfully connects to TLS hosts) but does not make so securely, because it does not perform server authenticity check. Default options look (somewhat) like this: [ssl: [verify: :verify_none]]
.
If you want (like me) to perform secure http requests default options do not work.
Solution
First, turn on authenticity check:
verify: :verify_peer
Then configure cert file. Ubuntu with openssl
installed have them at /etc/ssl/certs/ca-certificates.crt
:
cacertfile: '/etc/ssl/certs/ca-certificates.crt'
Next, to enable wildcard certificates we should specify custom (somewhat) hostname check function:
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
And finally, if you need to connect to an old server with outdated algorithms these algorightms should be enabled:
ciphers: :ssl.cipher_suites(:all, :"tlsv1.2")
Resulting config looks like this (without ciphers
):
[
verify: :verify_peer,
cacertfile: '/etc/ssl/certs/ca-certificates.crt',
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
And some code with :httpc
:
ssl_options = [
verify: :verify_peer,
cacertfile: '/etc/ssl/certs/ca-certificates.crt',
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
connection_options = [ssl: ssl_options]
url = 'https://www.google.com:443/'
:httpc.request(:get, {url, []}, connection_options, [])
Storing configuration
Sometimes it’s important to be able to turn server authenticity check off, because insecurely running program is less of an issue than not running at all. In that case there has to be a way to turn it off without rewriting the program.
I prefer to have a separate module for :ssl
options which gets configuration from ENV and saves it in persistent_term:
defmodule MyApp.HTTPCSSLOptions do
def get do
get_stored() || get_from_env!()
end
defp get_stored do
:persistent_term.get(__MODULE__, nil)
end
defp get_from_env! do
options =
case verify_type() do
:verify_none -> verify_none_options()
:verify_peer -> verify_peer_options()
end
:persistent_term.put(__MODULE__, options)
options
end
defp verify_type do
case String.downcase(Application.get_env(:my_app, :ssl)[:verify]) do
"verify_none" -> :verify_none
"verify_peer" -> :verify_peer
_ -> :verify_peer
end
end
defp verify_none_options do
[verify: :verify_none]
end
defp verify_peer_options do
cacertfile = Application.get_env(:my_app, :ssl)[:casertfile]
[
verify: :verify_peer,
cacertfile: to_charlist(cacertfile),
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
end
end
And then use that module:
url = 'https://www.google.com:443/'
:httpc.request(:get, {url, []}, [ssl: MyApp.HTTPCSSLOptions.get()], [])