forked from dvv/termit
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 8c6269d
Showing
8 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
ebin | ||
deps | ||
.eunit | ||
.ct | ||
logs | ||
test/*.beam | ||
erl_crash.dump |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
Copyright (c) 2013 Vladimir Dronnikov <[email protected]> | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
this software and associated documentation files (the "Software"), to deal in | ||
the Software without restriction, including without limitation the rights to | ||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||
the Software, and to permit persons to whom the Software is furnished to do so, | ||
subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
all: deps compile check test | ||
|
||
deps: | ||
rebar get-deps | ||
|
||
compile: | ||
rebar compile | ||
|
||
run: compile | ||
sh start.sh | ||
|
||
clean: | ||
rebar clean | ||
rm -fr ebin .ct test/*.beam | ||
|
||
check: | ||
rebar eunit skip_deps=true | ||
|
||
test: | ||
#rebar ct | ||
mkdir -p .ct | ||
ct_run -dir test -logdir .ct -pa ebin | ||
|
||
dist: deps compile | ||
echo TODO | ||
|
||
.PHONY: all deps compile check test run clean dist | ||
.SILENT: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
Termit | ||
============== | ||
|
||
Library for serializing Erlang terms to signed encrypted binaries and reliably deserializing them back. | ||
|
||
Usage | ||
-------------- | ||
|
||
A typical use case is to provide means to keep secrets put in public domain, e.g. secure cookies. | ||
|
||
```erlang | ||
Term = {this, is, an, [erlang, <<"term">>]}. | ||
Cookie = termit:encode_base64(Term, <<"cekpet">>). | ||
|
||
% time-to-live is 1000 seconds =:= secret valid no more than 1000 seconds | ||
{ok, Term} = termit:decode_base64(Cookie, <<"cekpet">>, 1000). | ||
% time-to-live is 0 seconds =:= expired | ||
{error, expired} = termit:decode_base64(Cookie, <<"cekpet">>, 0). | ||
|
||
% check whether secret was not forged | ||
{error, forged} = termit:decode_base64(<<Cookie/binary, "1">>, <<"cekpet">>, 1000). | ||
{error, forged} = termit:decode_base64(Cookie, <<"secret">>, 1000). | ||
{error, forged} = termit:decode_base64(undefined, <<"cekpet">>, 1000). | ||
``` | ||
|
||
[License](termit/blob/master/LICENSE.txt) | ||
------- | ||
|
||
Copyright (c) 2013 Vladimir Dronnikov <[email protected]> | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
this software and associated documentation files (the "Software"), to deal in | ||
the Software without restriction, including without limitation the rights to | ||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||
the Software, and to permit persons to whom the Software is furnished to do so, | ||
subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{lib_dirs, ["deps"]}. | ||
|
||
{erl_opts, [ | ||
debug_info, | ||
warn_format, | ||
warn_export_vars, | ||
warn_obsolete_guard, | ||
warn_bif_clash | ||
]}. | ||
|
||
{cover_enabled, true}. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{application, termit, [ | ||
{description, "Securely serialize/deserialize Erlang terms"}, | ||
{vsn, "0.0.1"}, | ||
{registered, []}, | ||
{applications, [ | ||
kernel, | ||
stdlib | ||
]}, | ||
{env, []} | ||
]}. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
%% | ||
%% @doc Serialize an Erlang term to signed encrypted binary and | ||
%% deserialize it back ensuring it's not been forged. | ||
%% | ||
|
||
-module(termit). | ||
-author('Vladimir Dronnikov <[email protected]>'). | ||
|
||
-export([ | ||
encode/2, decode/3, | ||
encode_base64/2, decode_base64/3 | ||
]). | ||
|
||
%% | ||
%% ----------------------------------------------------------------------------- | ||
%% @doc Serialize Term, encrypt and sign the result with Secret. | ||
%% Return binary(). | ||
%% ----------------------------------------------------------------------------- | ||
%% | ||
|
||
-spec encode(Term :: any(), Secret :: binary()) -> Cipher :: binary(). | ||
|
||
encode(Term, Secret) -> | ||
Bin = term_to_binary(Term), | ||
Enc = encrypt(Bin, Secret), | ||
{MegaSecs, Secs, _} = erlang:now(), | ||
Time = list_to_binary(integer_to_list(MegaSecs * 1000000 + Secs)), | ||
Sig = sign(<<Time/binary, Enc/binary>>, Secret), | ||
<<Sig/binary, Time/binary, Enc/binary>>. | ||
|
||
%% | ||
%% ----------------------------------------------------------------------------- | ||
%% @doc Given a result of encode/2, i.e. a signed encrypted binary, | ||
%% check the signature, uncrypt and deserialize into original term. | ||
%% Check it timestamp encoded into the data is not older than Ttl. | ||
%% Return {ok, Term} or {error, Reason}. | ||
%% ----------------------------------------------------------------------------- | ||
%% | ||
|
||
-spec decode( | ||
Cipher :: binary(), | ||
Secret :: binary(), | ||
Ttl :: non_neg_integer() | ||
) -> {ok, Term :: any()} | {error, Reason :: atom()}. | ||
|
||
%% @todo how do we know time is 10 octets? | ||
|
||
decode(<<Sig:32/binary, Time:10/binary, Enc/binary>>, Secret, Ttl) -> | ||
case sign(<<Time/binary, Enc/binary>>, Secret) of | ||
% signature ok? | ||
Sig -> | ||
Bin = uncrypt(Enc, Secret), | ||
% deserialize | ||
try binary_to_term(Bin, [safe]) of | ||
Term -> | ||
% not yet expired? | ||
{MegaSecs, Secs, _} = erlang:now(), | ||
Now = MegaSecs * 1000000 + Secs, | ||
Expires = list_to_integer(binary_to_list(Time)) + Ttl, | ||
case Expires > Now of | ||
true -> | ||
{ok, Term}; | ||
false -> | ||
{error, expired} | ||
end | ||
catch _:_ -> | ||
{error, badarg} | ||
end; | ||
_ -> | ||
{error, forged} | ||
end; | ||
|
||
%% N.B. unmatched binaries are forged | ||
decode(Bin, _, _) when is_binary(Bin) -> | ||
{error, forged}. | ||
|
||
%% | ||
%% ----------------------------------------------------------------------------- | ||
%% @doc Get 32-byte SHA1 sum of Data salted with Secret. | ||
%% ----------------------------------------------------------------------------- | ||
%% | ||
|
||
-spec sign(binary(), binary()) -> binary(). | ||
|
||
sign(Data, Secret) -> | ||
crypto:sha256([Data, Secret]). | ||
|
||
%% | ||
%% ----------------------------------------------------------------------------- | ||
%% @doc Encrypt Bin using Secret. | ||
%% ----------------------------------------------------------------------------- | ||
%% | ||
|
||
-spec encrypt(binary(), binary()) -> binary(). | ||
|
||
encrypt(Bin, Secret) -> | ||
<<Key:16/binary, IV:16/binary>> = crypto:sha256(Secret), | ||
crypto:aes_cfb_128_encrypt(Key, IV, Bin). | ||
|
||
%% | ||
%% ----------------------------------------------------------------------------- | ||
%% @doc Uncrypt Bin using Secret. | ||
%% ----------------------------------------------------------------------------- | ||
%% | ||
|
||
-spec uncrypt(binary(), binary()) -> binary(). | ||
|
||
uncrypt(Bin, Secret) -> | ||
<<Key:16/binary, IV:16/binary>> = crypto:sha256(Secret), | ||
crypto:aes_cfb_128_decrypt(Key, IV, Bin). | ||
|
||
%% | ||
%% ----------------------------------------------------------------------------- | ||
%% Conversion helpers | ||
%% ----------------------------------------------------------------------------- | ||
%% | ||
|
||
encode_base64(Term, Secret) -> | ||
base64:encode(encode(Term, Secret)). | ||
|
||
decode_base64(undefined, _, _) -> | ||
{error, forged}; | ||
|
||
decode_base64(Bin, Secret, Ttl) when is_binary(Bin) -> | ||
decode(base64:decode(Bin), Secret, Ttl). | ||
|
||
%% | ||
%% ----------------------------------------------------------------------------- | ||
%% Some unit tests | ||
%% ----------------------------------------------------------------------------- | ||
%% | ||
|
||
-ifdef(TEST). | ||
-include_lib("eunit/include/eunit.hrl"). | ||
|
||
encrypt_test() -> | ||
Secret = <<"Make It Elegant">>, | ||
Bin = <<"Transire Benefaciendo">>, | ||
?assertEqual(Bin, uncrypt(encrypt(Bin, Secret), Secret)), | ||
?assert(Bin =/= uncrypt(encrypt(Bin, Secret), <<Secret/binary, "1">>)), | ||
?assert(Bin =/= uncrypt(encrypt(Bin, <<Secret/binary, "1">>), Secret)). | ||
|
||
smoke_test() -> | ||
Term = {a, b, c, [d, "e", <<"foo">>]}, | ||
Secret = <<"TopSecRet">>, | ||
Enc = encode(Term, Secret), | ||
% decode encoded term with valid time to live | ||
?assertEqual({ok, Term}, decode(Enc, Secret, 1)), | ||
% expired data | ||
?assertEqual({error, expired}, decode(encode(Term, Secret), Secret, 0)), | ||
% forged data | ||
?assertEqual({error, forged}, decode(<<"1">>, Secret, 1)), | ||
?assertEqual({error, forged}, decode(<<Enc/binary, "1">>, Secret, 1)). | ||
|
||
encode_test(_Config) -> | ||
Term = {a, b, c, [d, "e", <<"foo">>]}, | ||
Secret = <<"TopSecRet">>, | ||
undefined = decode_base64(undefined, a, b), | ||
{ok, Term} = decode_base64(encode_base64(Term, Secret), Secret, 1), | ||
{error, expired} = decode_base64(encode_base64(Term, Secret), Secret, 0). | ||
|
||
-endif. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
-module(termit_SUITE). | ||
-author('Vladimir Dronnikov <[email protected]>'). | ||
|
||
%% interface | ||
-export([all/0]). | ||
|
||
%% tests | ||
-export([encode_test/1]). | ||
|
||
-include_lib("common_test/include/ct.hrl"). | ||
|
||
all() -> | ||
[encode_test]. | ||
|
||
encode_test(_Config) -> | ||
Term = {a, b, c, [d, "e", <<"foo">>]}, | ||
Secret = <<"TopSecRet">>, | ||
{error, forged} = termit:decode_base64(undefined, a, b), | ||
{ok, Term} = termit:decode_base64( | ||
termit:encode_base64(Term, Secret), Secret, 1), | ||
{error, expired} = termit:decode_base64( | ||
termit:encode_base64(Term, Secret), Secret, 0). |