调整协议,只加密头部分

This commit is contained in:
anlicheng 2026-07-01 16:48:45 +08:00
parent 94dc39805e
commit 22db3e1f46
4 changed files with 65 additions and 87 deletions

View File

@ -13,8 +13,9 @@ Run
$ rebar3 shell $ rebar3 shell
The UDP listener is configured in `config/sys.config` and listens on port `16380` The UDP listener is configured in `config/sys.config`; the checked-in config
by default. It accepts RelayKit UDP relay frames. listens on port `1443`, while the code fallback default is `16380`. It accepts
RelayKit UDP relay frames.
Protocol Protocol
-------- --------
@ -29,7 +30,8 @@ The decrypted plaintext is one `RKP1` frame:
magic(4) = "RKP1", version(1) = 1, type(1), reserved(2), magic(4) = "RKP1", version(1) = 1, type(1), reserved(2),
stream_id(8), payload_len(4), payload(payload_len) stream_id(8), payload_len(4), payload(payload_len)
Frame types are `open = 1`, `data = 2`, `close = 3`, and `error = 4`. Frame types are `open = 1`, `open_ack = 2`, `data = 3`, `close = 4`, and
`error = 5`.
`open` payload is binary encoded as: `open` payload is binary encoded as:
host_len(2), host(host_len), port(2), host_len(2), host(host_len), port(2),

View File

@ -1,45 +1,33 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%% @doc ChaCha20-Poly1305 helpers compatible with CryptoKit ChaChaPoly. %% @doc ChaCha20-Poly1305 helpers compatible with CryptoKit ChaChaPoly.
%% 1.
%% @end %% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(chacha20_cipher). -module(chacha20_cipher).
-export([encrypt/2, decrypt/2]). -export([encrypt/2, decrypt/4]).
-define(KEY_BYTES, 32). -define(KEY_BYTES, 32).
-define(NONCE_BYTES, 12). -define(NONCE_BYTES, 12).
-define(TAG_BYTES, 16). -define(TAG_BYTES, 16).
-define(AAD, <<>>). -define(AAD, <<>>).
encrypt(PlainText, Key) when is_binary(PlainText) -> encrypt(PlainText, Key) when is_binary(PlainText), byte_size(Key) =:= ?KEY_BYTES ->
case valid_key(Key) of
true ->
Nonce = crypto:strong_rand_bytes(?NONCE_BYTES), Nonce = crypto:strong_rand_bytes(?NONCE_BYTES),
try crypto:crypto_one_time_aead( try crypto:crypto_one_time_aead(chacha20_poly1305, Key, Nonce, PlainText, ?AAD, true) of
chacha20_poly1305, Key, Nonce, PlainText, ?AAD, true
) of
{CipherText, Tag} -> {CipherText, Tag} ->
{ok, <<Nonce/binary, CipherText/binary, Tag/binary>>} {ok, <<Nonce/binary, CipherText/binary, Tag/binary>>}
catch catch
_Class:_Reason -> _Class:_Reason ->
{error, crypto_failed} {error, crypto_failed}
end; end;
false ->
{error, invalid_key}
end;
encrypt(_PlainText, _Key) -> encrypt(_PlainText, _Key) ->
{error, invalid_plaintext}. {error, invalid_plaintext}.
decrypt(Combined, Key) when is_binary(Combined), byte_size(Combined) >= ?NONCE_BYTES + ?TAG_BYTES -> decrypt(Nonce, CipherText, Tag, Key) when is_binary(Nonce), is_binary(CipherText), is_binary(Tag), is_binary(Key) ->
case valid_key(Key) of try crypto:crypto_one_time_aead(chacha20_poly1305, Key, Nonce, CipherText, ?AAD, Tag, false) of
true ->
<<Nonce:?NONCE_BYTES/binary, Rest/binary>> = Combined,
CipherTextBytes = byte_size(Rest) - ?TAG_BYTES,
<<CipherText:CipherTextBytes/binary, Tag:?TAG_BYTES/binary>> = Rest,
try crypto:crypto_one_time_aead(
chacha20_poly1305, Key, Nonce, CipherText, ?AAD, Tag, false
) of
error -> error ->
{error, authentication_failed}; {error, authentication_failed};
PlainText -> PlainText ->
@ -47,16 +35,4 @@ decrypt(Combined, Key) when is_binary(Combined), byte_size(Combined) >= ?NONCE_B
catch catch
_Class:_Reason -> _Class:_Reason ->
{error, crypto_failed} {error, crypto_failed}
end; end.
false ->
{error, invalid_key}
end;
decrypt(Combined, _Key) when is_binary(Combined) ->
{error, ciphertext_too_short};
decrypt(_Combined, _Key) ->
{error, invalid_ciphertext}.
valid_key(Key) when is_binary(Key), byte_size(Key) =:= ?KEY_BYTES ->
true;
valid_key(_Key) ->
false.

View File

@ -13,17 +13,6 @@
-define(TCP_OPTIONS, [binary, {packet, raw}, {active, once}, {nodelay, true}]). -define(TCP_OPTIONS, [binary, {packet, raw}, {active, once}, {nodelay, true}]).
-define(CHACHA20_KEY, <<
16#9F, 16#4A, 16#6C, 16#0D,
16#8B, 16#21, 16#7E, 16#3F,
16#42, 16#D9, 16#AA, 16#78,
16#13, 16#C5, 16#E2, 16#B6,
16#F0, 16#A9, 16#27, 16#BC,
16#6D, 16#31, 16#E8, 16#4C,
16#55, 16#FA, 16#10, 16#2A,
16#7E, 16#9D, 16#3C, 16#BB
>>).
-record(stream, { -record(stream, {
id :: non_neg_integer(), id :: non_neg_integer(),
socket :: inet:socket(), socket :: inet:socket(),
@ -38,7 +27,6 @@
peer :: {inet:ip_address(), inet:port_number()}, peer :: {inet:ip_address(), inet:port_number()},
idle_timeout :: timeout(), idle_timeout :: timeout(),
connect_timeout :: timeout(), connect_timeout :: timeout(),
cipher_key :: binary(),
users = #{} :: #{binary() => binary()}, users = #{} :: #{binary() => binary()},
streams = #{} :: #{non_neg_integer() => stream()}, streams = #{} :: #{non_neg_integer() => stream()},
sockets = #{} :: #{inet:socket() => non_neg_integer()} sockets = #{} :: #{inet:socket() => non_neg_integer()}
@ -57,7 +45,6 @@ init([{udp, Server, _Sock}, Peer, IdleTimeout, ConnectTimeout, Users]) ->
peer = Peer, peer = Peer,
idle_timeout = IdleTimeout, idle_timeout = IdleTimeout,
connect_timeout = ConnectTimeout, connect_timeout = ConnectTimeout,
cipher_key = ?CHACHA20_KEY,
users = normalize_users(Users) users = normalize_users(Users)
}, IdleTimeout}. }, IdleTimeout}.
@ -90,16 +77,7 @@ terminate(_Reason, #state{streams = Streams}) ->
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
handle_encrypted_datagram(Packet, State = #state{idle_timeout = IdleTimeout, cipher_key = Key}) -> handle_encrypted_datagram(Packet, State = #state{idle_timeout = IdleTimeout}) ->
case chacha20_cipher:decrypt(Packet, Key) of
{ok, PlainPacket} ->
handle_datagram(PlainPacket, State);
{error, Reason} ->
logger:error("UDP relay ignored undecryptable datagram: ~p", [Reason]),
{noreply, State, IdleTimeout}
end.
handle_datagram(Packet, State = #state{idle_timeout = IdleTimeout}) ->
case relay_server_udp_protocol:decode(Packet) of case relay_server_udp_protocol:decode(Packet) of
{ok, connection_close} -> {ok, connection_close} ->
{stop, normal, State}; {stop, normal, State};
@ -246,21 +224,19 @@ remove_stream(StreamId, State = #state{streams = Streams, sockets = Sockets}) ->
close_remote(#stream{socket = Socket}) -> close_remote(#stream{socket = Socket}) ->
gen_tcp:close(Socket). gen_tcp:close(Socket).
send_frame(_Type, _StreamId, <<>>, _State) -> send_frame(Type, StreamId, <<Part:1200/binary, Rest/binary>>, State)
ok; when byte_size(Rest) > 0 ->
send_frame(Type, StreamId, <<Part:1200/binary, Rest/binary>>, State) ->
case send_frame0(Type, StreamId, Part, State) of case send_frame0(Type, StreamId, Part, State) of
ok -> ok ->
send_frame(Type, StreamId, Rest, State); send_frame(Type, StreamId, Rest, State);
Error -> Error ->
Error Error
end; end;
send_frame(Type, StreamId, Rest, State) -> send_frame(Type, StreamId, Payload, State) ->
send_frame0(Type, StreamId, Rest, State). send_frame0(Type, StreamId, Payload, State).
send_frame0(Type, StreamId, Part, #state{server = Server, peer = Peer, cipher_key = Key}) -> send_frame0(Type, StreamId, Part, #state{server = Server, peer = Peer}) ->
PlainPacket = relay_server_udp_protocol:encode(Type, StreamId, Part), case relay_server_udp_protocol:encode(Type, StreamId, Part) of
case chacha20_cipher:encrypt(PlainPacket, Key) of
{ok, Packet} -> {ok, Packet} ->
logger:error("UDP relay encrypt packet size: ~p", [byte_size(Packet)]), logger:error("UDP relay encrypt packet size: ~p", [byte_size(Packet)]),
Server ! {datagram, Peer, Packet}, Server ! {datagram, Peer, Packet},

View File

@ -14,28 +14,52 @@
-define(CONNECTION_CLOSE, 16#FF). -define(CONNECTION_CLOSE, 16#FF).
-define(CHACHA20_KEY, <<
16#9F, 16#4A, 16#6C, 16#0D,
16#8B, 16#21, 16#7E, 16#3F,
16#42, 16#D9, 16#AA, 16#78,
16#13, 16#C5, 16#E2, 16#B6,
16#F0, 16#A9, 16#27, 16#BC,
16#6D, 16#31, 16#E8, 16#4C,
16#55, 16#FA, 16#10, 16#2A,
16#7E, 16#9D, 16#3C, 16#BB
>>).
decode(<<Nonce:12/binary, CipherHead:20/binary, Tag:16/binary, Body/binary>>) ->
case chacha20_cipher:decrypt(Nonce, CipherHead, Tag, ?CHACHA20_KEY) of
{ok, PlainText} ->
decode0(PlainText, Body);
Error ->
Error
end.
%% %%
decode(<<"RKP1", ?VERSION:8, ?CONNECTION_CLOSE:8, 0:16, 0:64, 0:32, _Rest/binary>>) -> decode0(<<"RKP1", ?VERSION:8, ?CONNECTION_CLOSE:8, 0:16, 0:64, 0:32>>, _Body) ->
{ok, connection_close}; {ok, connection_close};
decode(<<"RKP1", ?VERSION:8, TypeNo:8, 0:16, StreamId:64, PayloadLen:32, Rest/binary>>) -> decode0(<<"RKP1", ?VERSION:8, TypeNo:8, 0:16, StreamId:64, PayloadLen:32>>, Body) when is_binary(Body) ->
case {type(TypeNo), byte_size(Rest) >= PayloadLen} of case {type(TypeNo), byte_size(Body) >= PayloadLen} of
{undefined, _} -> {undefined, _} ->
{error, unknown_frame_type}; {error, unknown_frame_type};
{_, false} -> {_, false} ->
{error, truncated_frame}; {error, truncated_frame};
{Type, true} -> {Type, true} ->
<<Payload:PayloadLen/binary, _Trailing/binary>> = Rest, <<Payload:PayloadLen/binary, _Trailing/binary>> = Body,
{ok, {Type, StreamId, Payload}} {ok, {Type, StreamId, Payload}}
end; end;
decode(Packet) when byte_size(Packet) < ?HEADER_LENGTH -> decode0(Header, _Body) when byte_size(Header) < ?HEADER_LENGTH ->
{error, frame_too_short}; {error, frame_too_short};
decode(_Packet) -> decode0(_Header, _Body) ->
{error, bad_frame_header}. {error, bad_frame_header}.
encode(Type, StreamId, Payload) when is_binary(Payload) -> encode(Type, StreamId, Payload) when is_atom(Type), is_integer(StreamId), is_binary(Payload) ->
TypeNo = type_no(Type), TypeNo = type_no(Type),
PayloadLen = byte_size(Payload), PayloadLen = byte_size(Payload),
<<"RKP1", ?VERSION:8, TypeNo:8, 0:16, StreamId:64, PayloadLen:32, Payload/binary>>. Head = <<"RKP1", ?VERSION:8, TypeNo:8, 0:16, StreamId:64, PayloadLen:32>>,
case chacha20_cipher:encrypt(Head, ?CHACHA20_KEY) of
{ok, EncryptHead} ->
<<EncryptHead/binary, Payload/binary>>;
Error ->
Error
end.
decode_open_request(<<HostLen:16, Host:HostLen/binary, Port:16, decode_open_request(<<HostLen:16, Host:HostLen/binary, Port:16,
UsernameLen:16, Username:UsernameLen/binary, UsernameLen:16, Username:UsernameLen/binary,