chronoscope/lib/chronoscope/nts/key_establishment_client.ex

65 lines
1.8 KiB
Elixir

defmodule Chronoscope.NTS.KeyEstablishmentClient do
require Logger
alias Chronoscope.NTS.KeyEstablishmentRequest
alias Chronoscope.NTS.KeyEstablishmentResponse
@timeout_in_milliseconds 3000
def key_establishment(%{host: host, port: port}) do
case ssl_connect(host, port) do
{:ok, socket} -> perform_key_establishment(socket)
{:error, {:tls_alert, {:handshake_failure, error}}} -> {:error, to_string(error)}
{:error, :timeout} -> {:error, :timeout}
{:error, error} -> {:error, inspect(error)}
error -> {:error, inspect(error)}
end
end
defp ssl_connect(host, port) do
:ssl.connect(host, port, tls_options(host), @timeout_in_milliseconds)
end
defp tls_options(host) do
host
|> :tls_certificate_check.options()
|> Keyword.put(:alpn_advertised_protocols, ["ntske/1"])
end
defp perform_key_establishment(socket) do
:ok = :ssl.send(socket, KeyEstablishmentRequest.create())
{:ok, peercert} = :ssl.peercert(socket)
# fixme - don't use receive inside genserver
receive do
{:ssl, _socket, response} ->
:ssl.close(socket)
case KeyEstablishmentResponse.parse(response) do
{:ok, x} -> {:ok, Map.put(x, :cert_expiration, certificate_expiration(peercert))}
# todo - indicate errors in server response
error -> error
end
msg ->
:ssl.close(socket)
Logger.error("received unexpected message: #{inspect(msg)}")
{:error, :no_response}
after
@timeout_in_milliseconds ->
:ssl.close(socket)
Logger.error("timed out waiting for response")
{:error, :timeout}
end
end
defp certificate_expiration(certificate) do
{:Validity, _, {:utcTime, expiration}} =
certificate
|> X509.Certificate.from_der!()
|> X509.Certificate.validity()
expiration
end
end