mirror of
https://github.com/seriyps/mtproto_proxy.git
synced 2026-08-04 07:08:10 +00:00
Make it possible to restrict connections by TLS SNI domains
This commit is contained in:
parent
35c8f0dafe
commit
e0c07667c4
4 changed files with 57 additions and 20 deletions
|
|
@ -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).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue