[erlang] initial checkin of Erlang client

This commit is contained in:
Michael Stair 2016-11-18 13:29:23 -05:00
parent 00669b2be0
commit 88cff80690
7 changed files with 186 additions and 0 deletions

16
erlang/LICENSE Normal file
View File

@ -0,0 +1,16 @@
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

22
erlang/README.md Normal file
View File

@ -0,0 +1,22 @@
fwknop
=====
Experimental native erlang fwknop client with Rijndael support.
Build
-----
$ rebar3 compile
$ rebar3 eunit
Usage
-----
$ rebar3 shell
1> fwknop:knock("spaserver.domain.com", 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>

5
erlang/rebar.config Normal file
View File

@ -0,0 +1,5 @@
{erl_opts, [debug_info]}.
{deps, [
{pkcs7, {git, "https://github.com/camshaft/pkcs7.erl"}}
]
}.

4
erlang/rebar.lock Normal file
View File

@ -0,0 +1,4 @@
[{<<"pkcs7">>,
{git,"https://github.com/camshaft/pkcs7.erl",
{ref,"00218999ce9b60848aa1f36612248d1079c7ef06"}},
0}].

16
erlang/src/fwknop.app.src Normal file
View File

@ -0,0 +1,16 @@
{application, fwknop,
[{description, "An OTP application"},
{vsn, "0.1.0"},
{registered, []},
{mod, { fwknop_app, []}},
{applications,
[kernel,
stdlib
]},
{env,[]},
{modules, []},
{maintainers, []},
{licenses, []},
{links, []}
]}.

85
erlang/src/fwknop.erl Normal file
View File

@ -0,0 +1,85 @@
%% 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( fwknop ).
-compile( export_all ).
-define( FwknopVersion, "2.0.2" ).
-define( CommandMode, 0 ).
-define( AccessMode, 1 ).
-import( lists, [ dropwhile/2, nth/2, reverse/1, seq/2, split/2 ] ).
-import( pkcs7, [ pad/1 ] ).
packet( Proto, Ip, Port, RijndaelKeyB64, HmacKeyB64 ) ->
E64 = fun( Bin ) -> base64:encode( Bin ) end,
RijndaelKey = base64:decode( RijndaelKeyB64 ),
HmacKey = base64:decode( HmacKeyB64 ),
Rand = random_digits( 16 ),
User = E64( os:getenv( "USER" ) ),
Version = ?FwknopVersion,
MsgType = integer_to_list( ?AccessMode ),
Request = strip_base64(E64( list_to_binary( io_lib:format( "~s,~s/~b", [ Ip, Proto, Port ] ) ) ) ),
Time = timestamp(),
Message = list_to_binary( io_lib:format( "~s:~s:~p:~s:~s:~s", [ Rand, User, Time, Version, MsgType, Request ] ) ),
error_logger:info_msg( "Message: ~s~n", [ Message ] ),
Digest = strip_base64( E64( crypto:hash( sha256, Message ) ) ),
Plaintext = pkcs7:pad( <<Message/binary, <<":">>/binary, Digest/binary>> ),
Salt = crypto:strong_rand_bytes( 8 ),
{Key, IV} = pbkdf1( Salt, RijndaelKey ),
Magic = <<"Salted__">>,
Ciphertext = crypto:block_encrypt( aes_cbc256, Key, IV, Plaintext ),
SpaData = strip_base64( E64( <<Magic/binary, Salt/binary, Ciphertext/binary>> ) ),
Hmac = strip_base64( E64( crypto:hmac( sha256, HmacKey, SpaData ) ) ),
error_logger:info_msg( "HMAC: ~s~n", [ Hmac ] ),
% strip encoded magic word
SpaData2 = binary:part(SpaData, {10, size(SpaData) - 10} ),
<< SpaData2/binary, Hmac/binary>>.
%
% miscellaneous utilities
%
pbkdf1( Salt, Key ) ->
Round1 = erlang:md5( <<Key/binary, Salt/binary>> ),
Round2 = erlang:md5( <<Round1/binary, Key/binary, Salt/binary>> ),
Round3 = erlang:md5( <<Round2/binary, Key/binary, Salt/binary>> ),
{ <<Round1/binary, Round2/binary>>, <<Round3/binary>> }.
strip_base64( Bin ) ->
F = fun( C ) -> C == $= end,
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 ) ] ).
timestamp() ->
{ A, B, _ } = os:timestamp(),
list_to_integer( integer_to_list( (A * 1000000) + B ) ).
knock( Host, Port, RijndaelKeyB64, HmacKeyB64, { Proto, SrcIp, DstPort } ) ->
Packet = packet( Proto, SrcIp, DstPort, RijndaelKeyB64, HmacKeyB64 ),
{ ok, Socket } = gen_udp:open( 0, [binary] ),
gen_udp:send( Socket, Host, Port, Packet ).

View File

@ -0,0 +1,38 @@
%% 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( fwknop_tests ).
-include_lib("eunit/include/eunit.hrl").
pbkdf1_test() ->
Salt = <<0,0,0,0,0,0,0,0>>,
RijndaelKey = base64:decode("Sz80RjpXOlhH2olGuKBUamHKcqyMBsS9BTgLaMugUsg="),
ExpectedKey = <<80,137,195,6,117,8,63,199,226,93,78,205,231,238,241,80,217,161,149,164,60,102,129,175,81,53,82,23,137,50,236,37>>,
ExpectedIV = <<56,251,47,154,60,96,84,106,192,163,161,216,59,202,166,203>>,
{ExpectedKey, ExpectedIV} = fwknop:pbkdf1(Salt, RijndaelKey).
strip_base64_test() ->
Encoded = base64:encode( "Salted" ),
Encoded2 = base64:encode( "Salted_" ),
Encoded3 = base64:encode( "Salted__" ),
<< "U2FsdGVk" >> = fwknop:strip_base64(Encoded),
<< "U2FsdGVkXw" >> = fwknop:strip_base64(Encoded2),
<< "U2FsdGVkX18" >> = fwknop:strip_base64(Encoded3).