Merge pull request #260 from mstair/erlang_server

Adding Erlang server component
This commit is contained in:
Michael Rash 2018-03-16 22:11:15 -04:00 committed by GitHub
commit 576d09afbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 3 deletions

View File

@ -13,10 +13,13 @@ Usage
-----
$ rebar3 shell
1> fwknop:knock("spaserver.domain.com", 62201, "Sz80RjpXOlhH2olGuKBUamHKcqyMBsS9BTgLaMugUsg=", "c0TOaMJ2aVPdYTh4Aa25Dwxni7PrLo2zLAtBoVwSepkvH6nLcW45Cjb9zaEC2SQd03kaaV+Ckx3FhCh5ohNM5Q==", { tcp, "1.1.1.1", 22 } ).
1> server:start("Sz80RjpXOlhH2olGuKBUamHKcqyMBsS9BTgLaMugUsg=", "c0TOaMJ2aVPdYTh4Aa25Dwxni7PrLo2zLAtBoVwSepkvH6nLcW45Cjb9zaEC2SQd03kaaV+Ckx3FhCh5ohNM5Q==").
2> fwknop:knock("localhost", 62201, "Sz80RjpXOlhH2olGuKBUamHKcqyMBsS9BTgLaMugUsg=", "c0TOaMJ2aVPdYTh4Aa25Dwxni7PrLo2zLAtBoVwSepkvH6nLcW45Cjb9zaEC2SQd03kaaV+Ckx3FhCh5ohNM5Q==", { tcp, "1.1.1.1", 22 } ).
=INFO REPORT==== 15-Nov-2016::15:49:16 ===
Message: 0428888364523312:bXMxNzg0:1479224956:2.0.2:1:MTI3LjAuMC4xLHRjcC84NDQz
=INFO REPORT==== 15-Nov-2016::15:49:16 ===
HMAC: KDZrTwZ+gFpqgzk8+BCXvYhRCxCzk084UyNzhihiWLU
ok
2>
Client request:{127,0,0,1}
Got message: <<"1.1.1.1,tcp/22">>
3>

View File

@ -73,7 +73,7 @@ strip_base64( Bin ) ->
list_to_binary( reverse( dropwhile( F, reverse( binary_to_list( Bin ) ) ) ) ).
random_digits( N ) ->
list_to_binary( [ nth( crypto:rand_uniform( 1, 10 ), "0123456789" ) || _ <- seq( 1, N ) ] ).
list_to_binary( [ nth( rand:uniform( 10 ), "0123456789" ) || _ <- seq( 1, N ) ] ).
timestamp() ->
{ A, B, _ } = os:timestamp(),

102
erlang/src/server.erl Normal file
View File

@ -0,0 +1,102 @@
%% License (GNU General Public License):
%%
%% This program is free software; you can redistribute it and/or
%% modify it under the terms of the GNU General Public License
%% as published by the Free Software Foundation; either version 2
%% of the License, or (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program; if not, write to the Free Software
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
-module( server ).
-compile( export_all ).
-import( fwknop, [ strip_base64/1, pbkdf1/2 ] ).
-export( [ start/2 ] ).
start(RijndaelKeyB64, HmacKeyB64) ->
spawn(fun() -> server(62201, RijndaelKeyB64, HmacKeyB64) end).
server(Port, RijndaelKeyB64, HmacKeyB64) ->
{ok, Socket} = gen_udp:open(Port, [binary, {active, false}]),
accept(Socket, { RijndaelKeyB64, HmacKeyB64 } ).
accept(Socket, { Key, Hmac } = Creds) ->
inet:setopts(Socket, [{active, once}]),
receive
{udp, Socket, Host, _, Bin} ->
io:format("Client request:~p~n",[Host]),
{ok, Ciphertext} = verify( Bin, Hmac ),
{ok, Plaintext} = decrypt( Ciphertext, Key ),
{ok, Message} = decode( Plaintext ),
error_logger:info_msg( "Got message: ~p~n", [ Message ] ),
accept( Socket, Creds )
end.
verify( SpaData, HmacKeyB64 ) ->
Magic = <<"U2FsdGVkX1">>,
HmacKey = base64:decode( HmacKeyB64 ),
% sha2.h:#define SHA256_B64_LEN 43
HmacLen = 43,
% write something to just split this on size vs two parts
DataHmac = binary:part(SpaData, {byte_size(SpaData), -HmacLen}),
EncodedMsg = binary:part(SpaData, {0, byte_size(SpaData) - HmacLen}),
ExpectedHmac = fwknop:strip_base64( base64:encode( crypto:hmac( sha256, HmacKey, << Magic/binary, EncodedMsg/binary >> ) ) ),
%io:format("DataHmac: ~s~n",[DataHmac]),
%io:format("ExpectedHmac: ~s~n",[ExpectedHmac]),
ExpectedHmac = DataHmac,
% handle replay-attack (just compare hmacs or decode to digests?)
% verify len is a blocksize?
DecodedData = base64:decode( unstrip_base64( <<Magic/binary, EncodedMsg/binary>> ) ),
% skip "Salted__"
DecodedMsg = binary:part( DecodedData, {byte_size(DecodedData), -(byte_size(DecodedData) - 8) } ),
{ ok, DecodedMsg }.
decrypt( Ciphertext, RijndaelKeyB64 ) ->
RijndaelKey = base64:decode( RijndaelKeyB64 ),
% split again on size
Salt = binary:part( Ciphertext, {0, 8} ),
EncryptedMsg = binary:part( Ciphertext, {byte_size(Ciphertext), -(byte_size(Ciphertext) - 8) } ),
{Key, IV} = fwknop:pbkdf1( Salt, RijndaelKey ),
{ok, crypto:block_decrypt( aes_cbc256, Key, IV, EncryptedMsg ) }.
decode( Plaintext ) ->
Unpadded = pkcs7:unpad( Plaintext ),
%[Rand, User, Time, Version, MsgType, Request, Digest] = re:split( Unpadded, ":", [{return,binary}]),
%io:format( "~s:~s:~s:~s:~s:~s:~s~n", [ Rand, User, Time, Version, MsgType, Request, Digest] ),
[_, _, _, _, _, Request, _] = re:split( Unpadded, ":", [{return,binary}]),
[Ip, Proto, Port] = re:split( base64:decode( unstrip_base64( Request) ), "[,/]", [{return,binary}]),
Message = list_to_binary( io_lib:format( "~s,~s/~s", [ Ip, Proto, Port ] ) ),
{ok, Message}.
%
% miscellaneous utilities
%
unstrip_base64( Bin ) ->
case ( size( Bin ) rem 4 ) of
0 -> Bin;
N -> Pad = list_to_binary(string:copies("=", (4-N) )),
<< Bin/binary, Pad/binary >>
end.