79 lines
2.3 KiB
Elixir
79 lines
2.3 KiB
Elixir
defmodule Chronoscope.NTS.KeyEstablishmentClient do
|
|
require Logger
|
|
|
|
alias Chronoscope.Certificate
|
|
alias Chronoscope.NTS.KeyEstablishmentRequest
|
|
alias Chronoscope.NTS.KeyEstablishmentResponse
|
|
|
|
@timeout_in_milliseconds 3500
|
|
|
|
@ssl Application.compile_env(:chronoscope, :ssl, :ssl)
|
|
|
|
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, handshake_failure_message("#{error}")}
|
|
{:error, {:tls_alert, {:no_application_protocol, error}}} -> {:error, String.trim("#{error}")}
|
|
{:error, :timeout} -> {:error, :timeout}
|
|
{:error, error} -> {:error, inspect(error)}
|
|
error -> {:error, inspect(error)}
|
|
end
|
|
end
|
|
|
|
defp ssl_connect(host, port) do
|
|
host
|
|
|> String.to_charlist()
|
|
|> @ssl.connect(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)
|
|
|
|
peercert
|
|
|> await_response()
|
|
|> tap(fn _ -> @ssl.close(socket) end)
|
|
end
|
|
|
|
defp await_response(peercert) do
|
|
receive do
|
|
{:ssl, _socket, response} ->
|
|
parse_response(response, peercert)
|
|
|
|
msg ->
|
|
Logger.error("received unexpected message: #{inspect(msg)}")
|
|
{:error, :no_response}
|
|
after
|
|
@timeout_in_milliseconds ->
|
|
Logger.error("timed out waiting for response")
|
|
{:error, :timeout}
|
|
end
|
|
end
|
|
|
|
defp parse_response(response, peercert) do
|
|
response
|
|
|> KeyEstablishmentResponse.parse()
|
|
|> Map.put(:cert_expiration, Certificate.expiration_date(peercert))
|
|
|> then(&{:ok, &1})
|
|
end
|
|
|
|
defp handshake_failure_message(error) do
|
|
cond do
|
|
error =~ ~r/\{bad_cert,hostname_check_failed\}$/ ->
|
|
"The certificate is NOT trusted. The name in the certificate does not match the expected."
|
|
|
|
error =~ ~r/\{bad_cert,unable_to_match_altnames\}$/ ->
|
|
"The certificate is NOT trusted. The name in the certificate does not match the expected."
|
|
|
|
true ->
|
|
String.trim(error)
|
|
end
|
|
end
|
|
end
|