diff --git a/erlang/LICENSE b/erlang/LICENSE new file mode 100644 index 00000000..7bcf85d0 --- /dev/null +++ b/erlang/LICENSE @@ -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 diff --git a/erlang/README.md b/erlang/README.md new file mode 100644 index 00000000..3aeb88e7 --- /dev/null +++ b/erlang/README.md @@ -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> diff --git a/erlang/rebar.config b/erlang/rebar.config new file mode 100644 index 00000000..b22e2496 --- /dev/null +++ b/erlang/rebar.config @@ -0,0 +1,5 @@ +{erl_opts, [debug_info]}. +{deps, [ + {pkcs7, {git, "https://github.com/camshaft/pkcs7.erl"}} + ] +}. diff --git a/erlang/rebar.lock b/erlang/rebar.lock new file mode 100644 index 00000000..b08e059d --- /dev/null +++ b/erlang/rebar.lock @@ -0,0 +1,4 @@ +[{<<"pkcs7">>, + {git,"https://github.com/camshaft/pkcs7.erl", + {ref,"00218999ce9b60848aa1f36612248d1079c7ef06"}}, + 0}]. diff --git a/erlang/src/fwknop.app.src b/erlang/src/fwknop.app.src new file mode 100644 index 00000000..2dd203b4 --- /dev/null +++ b/erlang/src/fwknop.app.src @@ -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, []} + ]}. diff --git a/erlang/src/fwknop.erl b/erlang/src/fwknop.erl new file mode 100644 index 00000000..5c353689 --- /dev/null +++ b/erlang/src/fwknop.erl @@ -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( <>/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( <> ) ), + + 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( <> ), + Round2 = erlang:md5( <> ), + Round3 = erlang:md5( <> ), + { <>, <> }. + +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 ). diff --git a/erlang/src/fwknop_tests.erl b/erlang/src/fwknop_tests.erl new file mode 100644 index 00000000..7189b854 --- /dev/null +++ b/erlang/src/fwknop_tests.erl @@ -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).