Parse NTS-KE response
This commit is contained in:
parent
ab8a31bad4
commit
68c1952383
|
@ -1,3 +1,2 @@
|
||||||
defmodule Chronoscope.Gemini do
|
defmodule Chronoscope.Gemini do
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,38 @@
|
||||||
defmodule Chronoscope.NTS do
|
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
|
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
|
require Logger
|
||||||
|
|
||||||
|
alias Chronoscope.NTS
|
||||||
|
|
||||||
@default_port "4460"
|
@default_port "4460"
|
||||||
@timeout_in_milliseconds 3000
|
|
||||||
|
|
||||||
def get(conn, params) do
|
def get(conn, params) do
|
||||||
host = to_charlist(params["host"])
|
host = to_charlist(params["host"])
|
||||||
port = String.to_integer(params["port"] || @default_port)
|
port = String.to_integer(params["port"] || @default_port)
|
||||||
|
|
||||||
tls_options = :tls_certificate_check.options(host) ++ [alpn_advertised_protocols: ["ntse/1"]]
|
case NTS.key_establishment(host, port) do
|
||||||
|
{:ok, configuration} ->
|
||||||
case :ssl.connect(host, port, tls_options, @timeout_in_milliseconds) do
|
ok_response(conn, %{status: :ok, configuration: configuration})
|
||||||
{: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)})
|
|
||||||
|
|
||||||
{:error, error} ->
|
{:error, error} ->
|
||||||
ok_response(conn, %{status: :error, reason: inspect(error)})
|
ok_response(conn, %{status: :error, reason: inspect(error)})
|
||||||
|
|
||||||
error ->
|
|
||||||
ok_response(conn, %{status: :error, reason: inspect(error)})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_response([0x80 | _] = _data) do
|
|
||||||
"success"
|
|
||||||
end
|
|
||||||
|
|
||||||
defp parse_response(_data) do
|
|
||||||
"failure"
|
|
||||||
end
|
|
||||||
|
|
||||||
defp ok_response(conn, body) do
|
defp ok_response(conn, body) do
|
||||||
conn
|
conn
|
||||||
|> Plug.Conn.put_resp_content_type("application/json")
|
|> Plug.Conn.put_resp_content_type("application/json")
|
||||||
|
|
Loading…
Reference in New Issue