Parse NTS-KE response
This commit is contained in:
parent
ab8a31bad4
commit
68c1952383
|
@ -1,3 +1,2 @@
|
|||
defmodule Chronoscope.Gemini do
|
||||
|
||||
end
|
||||
|
|
|
@ -1,3 +1,38 @@
|
|||
defmodule Chronoscope.NTS do
|
||||
require Logger
|
||||
|
||||
alias Chronoscope.NTS.KeyEstablishment
|
||||
|
||||
@timeout_in_milliseconds 3000
|
||||
|
||||
def key_establishment(host, port) do
|
||||
tls_options = :tls_certificate_check.options(host) ++ [alpn_advertised_protocols: ["ntske/1"]]
|
||||
|
||||
case :ssl.connect(host, port, tls_options, @timeout_in_milliseconds) do
|
||||
{:ok, socket} -> perform_key_establishment(socket)
|
||||
{:error, {:tls_alert, {:handshake_failure, error}}} -> {:error, to_string(error)}
|
||||
{:error, error} -> {:error, inspect(error)}
|
||||
error -> {:error, inspect(error)}
|
||||
end
|
||||
end
|
||||
|
||||
defp perform_key_establishment(socket) do
|
||||
:ok = :ssl.send(socket, KeyEstablishment.request())
|
||||
|
||||
receive do
|
||||
{:ssl, _socket, response} ->
|
||||
:ssl.close(socket)
|
||||
KeyEstablishment.parse_response(response)
|
||||
|
||||
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
|
||||
end
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
defmodule Chronoscope.NTS.KeyEstablishment do
|
||||
import Bitwise
|
||||
|
||||
@next_protocol_negotiation <<0x80, 0x01, 0x00, 0x02, 0x00, 0x00>>
|
||||
@aead_algorithm_negotiation <<0x80, 0x04, 0x00, 0x04, 0x00, 0x1E, 0x00, 0x0F>>
|
||||
@end_of_message <<0x80, 0x00, 0x00, 0x00>>
|
||||
|
||||
def request() do
|
||||
@next_protocol_negotiation <> @aead_algorithm_negotiation <> @end_of_message
|
||||
end
|
||||
|
||||
def parse_response(response) do
|
||||
{:ok, Map.update(do_parse_response(response, %{}), :cookies, 0, &length/1)}
|
||||
end
|
||||
|
||||
defp do_parse_response([], acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_parse_response([0x80, 0x00, 0x00, 0x00], acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_parse_response([0x80, 0x01, length_high, length_low | rest], acc) do
|
||||
length = combine_octets(length_high, length_low)
|
||||
{next_protocols, remaining} = Enum.split(rest, length)
|
||||
|
||||
do_parse_response(
|
||||
remaining,
|
||||
Map.put(acc, :next_protocols, parse_next_protocol(next_protocols))
|
||||
)
|
||||
end
|
||||
|
||||
defp do_parse_response([first, 0x04, length_high, length_low | rest], acc)
|
||||
when first == 0x00 or first == 0x80 do
|
||||
length = combine_octets(length_high, length_low)
|
||||
{aead_algorithms, remaining} = Enum.split(rest, length)
|
||||
|
||||
do_parse_response(
|
||||
remaining,
|
||||
Map.put(acc, :aead_algorithms, parse_aead_algorithm(aead_algorithms))
|
||||
)
|
||||
end
|
||||
|
||||
defp do_parse_response([first, 0x05, length_high, length_low | rest], acc)
|
||||
when first == 0x00 or first == 0x80 do
|
||||
length = combine_octets(length_high, length_low)
|
||||
{cookie, remaining} = Enum.split(rest, length)
|
||||
|
||||
do_parse_response(
|
||||
remaining,
|
||||
acc
|
||||
|> Map.update(:cookies, [cookie], &[cookie | &1])
|
||||
|> Map.put(:cookie_length, length)
|
||||
)
|
||||
end
|
||||
|
||||
defp do_parse_response([_, _, length_high, length_low | rest], acc) do
|
||||
length = combine_octets(length_high, length_low)
|
||||
{_, remaining} = Enum.split(rest, length)
|
||||
|
||||
do_parse_response(remaining, acc)
|
||||
end
|
||||
|
||||
defp parse_aead_algorithm([0x00, 0x0F]) do
|
||||
"AEAD_AES_SIV_CMAC_256"
|
||||
end
|
||||
|
||||
defp parse_aead_algorithm([0x00, 0x1E]) do
|
||||
"AEAD_AES_128_GCM_SIV"
|
||||
end
|
||||
|
||||
defp parse_aead_algorithm(_aead_algorithm) do
|
||||
"UNKNOWN"
|
||||
end
|
||||
|
||||
defp parse_next_protocol([0x00, 0x00]) do
|
||||
"NTPv4"
|
||||
end
|
||||
|
||||
defp parse_next_protocol(_next_protocol) do
|
||||
"UNASSIGNED"
|
||||
end
|
||||
|
||||
# todo parse server/port information
|
||||
|
||||
defp combine_octets(high, low) do
|
||||
high <<< 8 ||| low
|
||||
end
|
||||
end
|
|
@ -1,2 +0,0 @@
|
|||
defmodule Chronoscope.NTS.KeyExchange do
|
||||
end
|
|
@ -3,58 +3,23 @@ defmodule ChronoscopeWeb.API.NTS.KeyExchangeController do
|
|||
|
||||
require Logger
|
||||
|
||||
alias Chronoscope.NTS
|
||||
|
||||
@default_port "4460"
|
||||
@timeout_in_milliseconds 3000
|
||||
|
||||
def get(conn, params) do
|
||||
host = to_charlist(params["host"])
|
||||
port = String.to_integer(params["port"] || @default_port)
|
||||
|
||||
tls_options = :tls_certificate_check.options(host) ++ [alpn_advertised_protocols: ["ntse/1"]]
|
||||
|
||||
case :ssl.connect(host, port, tls_options, @timeout_in_milliseconds) do
|
||||
{:ok, socket} ->
|
||||
key_exchange_request =
|
||||
<<0x80, 0x01, 0x00, 0x02, 0x00, 0x00, 0x80, 0x04, 0x00, 0x04, 0x00, 0x1E, 0x00, 0x0F,
|
||||
0x80, 0x00, 0x00, 0x00>>
|
||||
|
||||
:ok = :ssl.send(socket, key_exchange_request)
|
||||
|
||||
receive do
|
||||
{:ssl, _socket, data} ->
|
||||
:ssl.close(socket)
|
||||
ok_response(conn, %{status: :ok, response: parse_response(data)})
|
||||
|
||||
msg ->
|
||||
:ssl.close(socket)
|
||||
Logger.error("received unexpected message: #{inspect(msg)}")
|
||||
ok_response(conn, %{status: :error, reason: :no_response})
|
||||
after
|
||||
@timeout_in_milliseconds ->
|
||||
:ssl.close(socket)
|
||||
Logger.error("timed out waiting for response")
|
||||
ok_response(conn, %{status: :error, reason: :timeout})
|
||||
end
|
||||
|
||||
{:error, {:tls_alert, {:handshake_failure, error}}} ->
|
||||
ok_response(conn, %{status: :error, reason: to_string(error)})
|
||||
case NTS.key_establishment(host, port) do
|
||||
{:ok, configuration} ->
|
||||
ok_response(conn, %{status: :ok, configuration: configuration})
|
||||
|
||||
{:error, error} ->
|
||||
ok_response(conn, %{status: :error, reason: inspect(error)})
|
||||
|
||||
error ->
|
||||
ok_response(conn, %{status: :error, reason: inspect(error)})
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_response([0x80 | _] = _data) do
|
||||
"success"
|
||||
end
|
||||
|
||||
defp parse_response(_data) do
|
||||
"failure"
|
||||
end
|
||||
|
||||
defp ok_response(conn, body) do
|
||||
conn
|
||||
|> Plug.Conn.put_resp_content_type("application/json")
|
||||
|
|
Loading…
Reference in New Issue