Make it possible to restrict connections by TLS SNI domains

parent 35c8f0da
......@@ -11,11 +11,12 @@
-behaviour(mtp_codec).
-export([format_secret/2]).
-export([from_client_hello/2,
new/0,
try_decode_packet/2,
encode_packet/2]).
-export_type([codec/0]).
-export_type([codec/0, meta/0]).
-include_lib("hut/include/hut.hrl").
......@@ -56,16 +57,39 @@
-define(TLS_CHANGE_CIPHER, ?TLS_REC_CHANGE_CIPHER, ?TLS_12_VERSION, 0, 1, 1).
-define(EXT_SNI, 0).
-define(EXT_SNI_HOST_NAME, 0).
-define(APP, mtproto_proxy).
-opaque codec() :: #st{}.
-type meta() :: #{session_id := binary(),
timestamp := non_neg_integer(),
sni_domain => binary()}.
%% @doc format TLS secret
-spec format_secret(binary(), binary()) -> binary().
format_secret(Secret, Domain) when byte_size(Secret) == 16 ->
base64url(<<16#ee, Secret/binary, Domain/binary>>);
format_secret(HexSecret, Domain) when byte_size(HexSecret) == 32 ->
format_secret(mtp_handler:unhex(HexSecret), Domain).
base64url(Bin) ->
%% see https://hex.pm/packages/base64url
<< << (urlencode_digit(D)) >> || <<D>> <= base64:encode(Bin), D =/= $= >>.
urlencode_digit($/) -> $_;
urlencode_digit($+) -> $-;
urlencode_digit(D) -> D.
-spec from_client_hello(binary(), binary()) ->
{ok, iodata(), binary(), non_neg_integer(), codec()}.
{ok, iodata(), meta(), codec()}.
from_client_hello(Data, Secret) ->
#client_hello{pseudorandom = ClientDigest,
session_id = SessionId} = CliHlo = parse_client_hello(Data),
session_id = SessionId,
extensions = Extensions} = CliHlo = parse_client_hello(Data),
?log(debug, "TLS ClientHello=~p", [CliHlo]),
ServerDigest = make_server_digest(Data, Secret),
<<Zeroes:(?DIGEST_LEN - 4)/binary, _/binary>> = XoredDigest =
......@@ -84,7 +108,15 @@ from_client_hello(Data, Secret) ->
Response = [as_tls_frame(?TLS_REC_HANDSHAKE, SrvHello),
CC,
DD],
{ok, Response, SessionId, Timestamp, new()}.
Meta0 = #{session_id => SessionId,
timestamp => Timestamp},
Meta = case lists:keyfind(?EXT_SNI, 1, Extensions) of
{_, [{?EXT_SNI_HOST_NAME, Domain}]} ->
Meta0#{sni_domain => Domain};
_ ->
Meta0
end,
{ok, Response, Meta, new()}.
parse_client_hello(<<?TLS_REC_HANDSHAKE, ?TLS_10_VERSION, 512:16/unsigned-big, %Frame
......
......@@ -288,12 +288,13 @@ handle_upstream_data(Bin, #state{codec = Codec0} = S0) ->
parse_upstream_data(<<?TLS_START, _/binary>> = AllData,
#state{stage = tls_hello, secret = Secret, codec = Codec0} = S) when
#state{stage = tls_hello, secret = Secret, codec = Codec0,
addr = {Ip, _}, listener = Listener} = S) when
byte_size(AllData) >= (?TLS_CLIENT_HELLO_LEN + 5) ->
assert_protocol(mtp_fake_tls),
<<Data:(?TLS_CLIENT_HELLO_LEN + 5)/binary, Tail/binary>> = AllData,
{ok, Response, SessionId, Timestamp, TlsCodec} = mtp_fake_tls:from_client_hello(Data, Secret),
maybe_check_tls_replay(SessionId, Timestamp),
{ok, Response, Meta, TlsCodec} = mtp_fake_tls:from_client_hello(Data, Secret),
check_tls_access(Listener, Ip, Meta),
Codec1 = mtp_codec:replace(tls, true, TlsCodec, Codec0),
Codec = mtp_codec:push_back(tls, Tail, Codec1),
ok = up_send_raw(Response, S),
......@@ -354,9 +355,15 @@ maybe_check_replay(Packet) ->
ok
end.
maybe_check_tls_replay(_SessionId, _Timestamp) ->
%% TODO
ok.
check_tls_access(_Listener, _Ip, #{sni_domain := Domain}) ->
%% TODO validate timestamp!
%% TODO some more scalable solution
AllowedDomains = application:get_env(?APP, tls_allowed_domains, []),
lists:member(Domain, AllowedDomains)
orelse error({protocol_error, tls_sni_domain_not_allowed, Domain});
check_tls_access(_, Ip, Meta) ->
error({protocol_error, tls_no_sni, {Ip, Meta}}).
up_send(Packet, #state{stage = tunnel, codec = UpCodec} = S) ->
%% ?log(debug, ">Up: ~p", [Packet]),
......
......@@ -58,6 +58,10 @@
%% protocols will be immediately disallowed.
{allowed_protocols, [mtp_fake_tls, mtp_secure, mtp_abridged, mtp_intermediate]},
%% Which domains to allow in TLS SNI
%% XXX: this option is experimental and will be removed later!
{tls_allowed_domains, [<<"en.wikipedia.org">>]},
{init_dc_connections, 2},
{clients_per_dc_connection, 300},
......
......@@ -81,6 +81,7 @@ running_ports() ->
end
end, mtp_listeners()).
%%====================================================================
%% Internal functions
%%====================================================================
......@@ -171,8 +172,9 @@ build_urls(Host, Port, Secret, Protocols) ->
end, Protocols)),
lists:map(
fun(mtp_fake_tls) ->
RawSecret = mtp_handler:unhex(Secret),
ProtoSecret = base64url(<<16#ee, RawSecret/binary, "en.wikipedia.org">>),
%% Print just for 1st domain as example
{ok, [Domain | _]} = application:get_env(?APP, tls_allowed_domains),
ProtoSecret = mtp_fake_tls:format_secret(Secret, Domain),
MkUrl(ProtoSecret);
(mtp_secure) ->
ProtoSecret = ["dd", Secret],
......@@ -181,14 +183,6 @@ build_urls(Host, Port, Secret, Protocols) ->
MkUrl(Secret)
end, UrlTypes).
base64url(Bin) ->
%% see https://hex.pm/packages/base64url
<< << (urlencode_digit(D)) >> || <<D>> <= base64:encode(Bin), D =/= $= >>.
urlencode_digit($/) -> $_;
urlencode_digit($+) -> $-;
urlencode_digit(D) -> D.
-ifdef(TEST).
report(Fmt, Args) ->
?log(debug, Fmt, Args).
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment