I'm working on a toy networking project and I want to add a TLS layer between the server and the client. I'm getting handshake errors that I'm trying to figure out how to debug.
The TL;DR is probably: 'what arguments do I pass to :ssl.listen/2
' but here is the minimal example.
First I create a new project with mix new tls_question
.
I have added :crypto
and :ssl
to mix.exs like so:
def application do
[
extra_applications: [:logger, :crypto, :ssl]
]
end
I have then generated an SSL certificate with
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365
and moved key.pem and cert.pem into the project folder.
I then have the following minimal program
defmodule TlsQuestion do
@ip {127,0,0,1}
@port 4343
def main do
:ssl.start()
{:ok, listen_socket} = :ssl.listen(@port,
[ certs_keys: [
keyfile: "key.pem",
certfile: "cert.pem",
password: "CorrectHorseBatteryStaple"
],
reuseaddr: true
])
spawn(fn -> client() end)
{:ok, accept_socket} = :ssl.transport_accept(listen_socket)
{:ok, accept_socket} = :ssl.handshake(accept_socket)
:ssl.send(accept_socket, "Hello World")
end
def client() do
{:ok, connect_socket} = :ssl.connect(@ip, @port,
[verify: :verify_peer,
cacertfile: "cert.pem",
active: :once], :infinity)
message = :ssl.recv(connect_socket, 0)
IO.puts(message)
end
end
TlsQuestion.main()
From which I call mix run
.
The error message might be enlightening for some but hasn't helped me
== Compilation error in file lib/tls_question.ex ==
** (exit) exited in: :gen_statem.call(#PID<0.164.0>, {:start, :infinity}, :infinity)
** (EXIT) an exception was raised:
** (FunctionClauseError) no function clause matching in :ssl_config.key_conf/1
(ssl 10.8.7) ssl_config.erl:181: :ssl_config.key_conf({:keyfile, "key.pem"})
(ssl 10.8.7) ssl_config.erl:72: :ssl_config.cert_key_pair/3
(stdlib 4.2) lists.erl:1315: :lists.map/2
(ssl 10.8.7) ssl_config.erl:56: :ssl_config.init_certs_keys/3
(ssl 10.8.7) ssl_config.erl:51: :ssl_config.init/2
(ssl 10.8.7) ssl_gen_statem.erl:164: :ssl_gen_statem.ssl_config/3
(ssl 10.8.7) tls_connection.erl:150: :tls_connection.init/1
(stdlib 4.2) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
(stdlib 4.2) gen.erl:243: :gen.do_call/4
(stdlib 4.2) gen_statem.erl:900: :gen_statem.call_dirty/4
(ssl 10.8.7) ssl_gen_statem.erl:1239: :ssl_gen_statem.call/2
(ssl 10.8.7) ssl_gen_statem.erl:234: :ssl_gen_statem.handshake/2
lib/tls_question.ex:16: TlsQuestion.main/0
It looks like it's complaining about something I'm doing with the certificate and key files?
I've passed the certificate to the client as the CA certificate chain (since a self-signed certificate is its own certificate chain). Could that be the issue?
The TL;DR is probably: 'what arguments do I pass to :ssl.listen/2'
listen(Port, Options) -> {ok, ListenSocket} | {error, reason()}
Port
is defined to be an integer:
0...65535
Options
is defined to be a list:
Options = [tls_server_option()]
tls_server_option() =
server_option() |
common_option() |
socket_option() |
transport_option()
common_option() = ...| {certs_keys, certs_keys()} | ...
certs_keys() = [cert_key_conf()]
cert_key_conf() =
#{cert => cert(),
key => key(),
certfile => cert_pem(),
keyfile => key_pem(),
password => key_pem_password()}
Note that cert_key_conf()
is an erlang map, giving you this structure in elixir:
{:ok, listen_socket} = :ssl.listen(@port,
[ certs_keys: [%{
}],
reuseaddr: true
])
Continuing with the type descriptions:
cert_pem() = file:filename() = string() => list of integers
key_pem() = filename() = string() => list of integers
key_pem_pasword() = io_list() => possibly nested list of integers and/or binaries
In erlang, the string() type is a list of integers.
elixir erlang
------ ------
list of itegers: single quotes double quotes
binaries: double quotes, the syntax <<1,34,97>>
or <<97,98,99>>
Starting at the bottom of the type specifications listed above and substituting upwards, gives you:
{:ok, listen_socket} = :ssl.listen(@port,
[ certs_keys: [%{
keyfile: 'key.pem',
certfile: 'cert.pem',
password: 'CorrectHorseBatteryStaple'
}],
reuseaddr: true
])
I'm not sure whether you can omit the keys cert:
and key:
in the map. Also, the docs don't list reuseaddr
as a valid 2-tuple in the Options
list.