
The API for pwhash_str returns a cstring in the output buffer. These are null terminated. However, we return the full buffer as a binary back to Erlang. This means that we have a buffer with 0'es in the end. The tests take this buffer and passes it back in as is. Hence all the tests pass. However, it is conceivable that if we write said buffer to disk somewhere, we are not going to write those 0's out. When we then load the ASCII-armored Argon2 string into memory again, it is not 0-terminated as a cstring should be, and this produces errors all over the place. The fix is twofold: * Return the full buffer to Erlang, but use binary:split/2 to create a subbinary with the relevant part. * Add a 0 in the end of ASCII Argon2 string before passing it to libsodium Since we are looking at pwhashing, and Argon2, we expect the computational problem to be memory bound. Thus, spending a bit more work in memory is not going to have any considerable impact on the speed of this system.
869 lines
27 KiB
Erlang
869 lines
27 KiB
Erlang
-module(enacl_eqc).
|
|
-include_lib("eqc/include/eqc.hrl").
|
|
-compile(export_all).
|
|
|
|
-ifndef(mini).
|
|
-compile({parse_transform, eqc_parallelize}).
|
|
-define(FAULT(Arg1, Arg2), fault(Arg1, Arg2)).
|
|
-define(FAULT_RATE(Arg1, Arg2, Arg3), fault_rate(Arg1, Arg2, Arg3)).
|
|
-else.
|
|
-define(FAULT(Arg1, Arg2), noop_fault(Arg1, Arg2)).
|
|
-define(FAULT_RATE(Arg1, Arg2, Arg3), noop_fault_rate(Arg1, Arg2, Arg3)).
|
|
-endif.
|
|
|
|
start()->
|
|
eqc:module(?MODULE).
|
|
|
|
noop_fault(_Bad, Good) -> Good.
|
|
|
|
noop_fault_rate(_1, _2, Gen) -> Gen.
|
|
|
|
non_byte_int() ->
|
|
oneof([
|
|
?LET(N, nat(), -(N+1)),
|
|
?LET(N, nat(), N+256)
|
|
]).
|
|
|
|
g_iolist() ->
|
|
?SIZED(Sz, g_iolist(Sz)).
|
|
|
|
g_iolist(0) ->
|
|
?FAULT(
|
|
oneof([
|
|
elements([a,b,c]),
|
|
real(),
|
|
non_byte_int()
|
|
]),
|
|
return([]));
|
|
g_iolist(N) ->
|
|
?FAULT(
|
|
oneof([
|
|
elements([a,b,c]),
|
|
real(),
|
|
non_byte_int()
|
|
]),
|
|
frequency([
|
|
{1, g_iolist(0)},
|
|
{N, ?LAZY(list(oneof([char(), binary(), g_iolist(N div 4)])))}
|
|
])).
|
|
|
|
g_iodata() ->
|
|
?FAULT(
|
|
oneof([elements([a,b,c]), real()]),
|
|
oneof([binary(), g_iolist(), eqc_gen:largebinary(64*1024)])).
|
|
|
|
v_iolist([]) -> true;
|
|
v_iolist([B|Xs]) when is_binary(B) -> v_iolist(Xs);
|
|
v_iolist([C|Xs]) when is_integer(C), C >= 0, C < 256 -> v_iolist(Xs);
|
|
v_iolist([L|Xs]) when is_list(L) ->
|
|
v_iolist(L) andalso v_iolist(Xs);
|
|
v_iolist(_) -> false.
|
|
|
|
v_iodata(B) when is_binary(B) -> true;
|
|
v_iodata(Structure) -> v_iolist(Structure).
|
|
|
|
%% Generator for binaries of a given size with different properties and fault injection:
|
|
g_binary(Sz) ->
|
|
?FAULT(g_binary_bad(Sz), g_binary_good(Sz)).
|
|
|
|
g_binary_good(Sz) when Sz =< 32 -> binary(Sz);
|
|
g_binary_good(Sz) -> eqc_gen:largebinary(Sz).
|
|
|
|
g_binary_bad(Sz) ->
|
|
frequency([
|
|
{5, ?SUCHTHAT(B, binary(), byte_size(B) /= Sz)},
|
|
{1, elements([a, b])},
|
|
{1, int()},
|
|
{1, g_iodata()}
|
|
]).
|
|
|
|
v_binary(Sz, N) when is_binary(N) ->
|
|
byte_size(N) == Sz;
|
|
v_binary(_, _) -> false.
|
|
|
|
|
|
%% Typical generators based on the binaries
|
|
nonce() -> g_binary(enacl:box_nonce_size()).
|
|
nonce_valid(N) -> v_binary(enacl:box_nonce_size(), N).
|
|
|
|
%% Generator of natural numbers
|
|
g_nat() ->
|
|
?FAULT(g_nat_bad(), nat()).
|
|
|
|
g_nat_bad() ->
|
|
oneof([
|
|
elements([a,b,c]),
|
|
real(),
|
|
binary(),
|
|
?LET(X, nat(), -X)
|
|
]).
|
|
|
|
is_nat(N) when is_integer(N), N >= 0 -> true;
|
|
is_nat(_) -> false.
|
|
|
|
keypair_good() ->
|
|
#{ public := PK, secret := SK} = enacl:box_keypair(),
|
|
{PK, SK}.
|
|
|
|
keypair_bad() ->
|
|
?LET(X, elements([pk, sk]),
|
|
begin
|
|
#{ public := PK, secret := SK} = enacl:box_keypair(),
|
|
case X of
|
|
pk ->
|
|
PKBytes = enacl:box_public_key_bytes(),
|
|
{oneof([return(a), nat(), ?SUCHTHAT(B, binary(), byte_size(B) /= PKBytes)]), SK};
|
|
sk ->
|
|
SKBytes = enacl:box_secret_key_bytes(),
|
|
{PK, oneof([return(a), nat(), ?SUCHTHAT(B, binary(), byte_size(B) /= SKBytes)])}
|
|
end
|
|
end).
|
|
|
|
keypair() ->
|
|
?FAULT(keypair_bad(), keypair_good()).
|
|
|
|
%% CRYPTO BOX
|
|
%% ---------------------------
|
|
%% * box/4
|
|
%% * box_open/4
|
|
%% * box_beforenm/2
|
|
%% * box_afternm/3
|
|
%% * box_open_afternm/3
|
|
keypair_valid(PK, SK) when is_binary(PK), is_binary(SK) ->
|
|
PKBytes = enacl:box_public_key_bytes(),
|
|
SKBytes = enacl:box_secret_key_bytes(),
|
|
byte_size(PK) == PKBytes andalso byte_size(SK) == SKBytes;
|
|
keypair_valid(_PK, _SK) -> false.
|
|
|
|
prop_box_keypair() ->
|
|
?FORALL(_X, return(dummy),
|
|
ok_box_keypair(enacl:box_keypair())).
|
|
|
|
ok_box_keypair(#{ public := _, secret := _}) -> true;
|
|
ok_box_keypair(_) -> false.
|
|
|
|
box(Msg, Nonce , PK, SK) ->
|
|
try
|
|
enacl:box(Msg, Nonce, PK, SK)
|
|
catch
|
|
error:badarg -> badarg
|
|
end.
|
|
|
|
box_seal(Msg, PK) ->
|
|
try
|
|
enacl:box_seal(Msg, PK)
|
|
catch
|
|
error:badarg -> badarg
|
|
end.
|
|
|
|
box_seal_open(Cph, PK, SK) ->
|
|
try
|
|
enacl:box_seal_open(Cph, PK, SK)
|
|
catch
|
|
error:badarg -> badarg
|
|
end.
|
|
|
|
box_open(CphText, Nonce, PK, SK) ->
|
|
try
|
|
enacl:box_open(CphText, Nonce, PK, SK)
|
|
catch
|
|
error:badarg -> badarg
|
|
end.
|
|
|
|
failure(badarg) -> true;
|
|
failure({error, failed_verification}) -> true;
|
|
failure(X) -> {failure, X}.
|
|
|
|
prop_box_correct() ->
|
|
?FORALL({Msg, Nonce, {PK1, SK1}, {PK2, SK2}},
|
|
{?FAULT_RATE(1, 40, g_iodata()),
|
|
?FAULT_RATE(1, 40, nonce()),
|
|
?FAULT_RATE(1, 40, keypair()),
|
|
?FAULT_RATE(1, 40, keypair())},
|
|
begin
|
|
case v_iodata(Msg) andalso nonce_valid(Nonce) andalso keypair_valid(PK1, SK1) andalso keypair_valid(PK2, SK2) of
|
|
true ->
|
|
Key = enacl:box_beforenm(PK2, SK1),
|
|
Key = enacl:box_beforenm(PK1, SK2),
|
|
CipherText = enacl:box(Msg, Nonce, PK2, SK1),
|
|
CipherText = enacl:box_afternm(Msg, Nonce, Key),
|
|
{ok, DecodedMsg} = enacl:box_open(CipherText, Nonce, PK1, SK2),
|
|
{ok, DecodedMsg} = enacl:box_open_afternm(CipherText, Nonce, Key),
|
|
equals(iolist_to_binary(Msg), DecodedMsg);
|
|
false ->
|
|
case box(Msg, Nonce, PK2, SK1) of
|
|
badarg -> true;
|
|
Res -> failure(box_open(Res, Nonce, PK1, SK2))
|
|
end
|
|
end
|
|
end).
|
|
|
|
prop_box_failure_integrity() ->
|
|
?FORALL({Msg, Nonce, {PK1, SK1}, {PK2, SK2}},
|
|
{?FAULT_RATE(1, 40, g_iodata()),
|
|
?FAULT_RATE(1, 40, nonce()),
|
|
?FAULT_RATE(1, 40, keypair()),
|
|
?FAULT_RATE(1, 40, keypair())},
|
|
begin
|
|
case v_iodata(Msg)
|
|
andalso nonce_valid(Nonce)
|
|
andalso keypair_valid(PK1, SK1)
|
|
andalso keypair_valid(PK2, SK2) of
|
|
true ->
|
|
Key = enacl:box_beforenm(PK2, SK1),
|
|
CipherText = enacl:box(Msg, Nonce, PK2, SK1),
|
|
Err = enacl:box_open([<<"x">>, CipherText], Nonce, PK1, SK2),
|
|
Err = enacl:box_open_afternm([<<"x">>, CipherText], Nonce, Key),
|
|
equals(Err, {error, failed_verification});
|
|
false ->
|
|
case box(Msg, Nonce, PK2, SK1) of
|
|
badarg -> true;
|
|
Res ->
|
|
failure(box_open(Res, Nonce, PK1, SK2))
|
|
end
|
|
end
|
|
end).
|
|
|
|
|
|
%% PRECOMPUTATIONS
|
|
beforenm_key() ->
|
|
?LET([{PK1, SK1}, {PK2, SK2}], [?FAULT_RATE(1, 40, keypair()), ?FAULT_RATE(1, 40, keypair())],
|
|
case keypair_valid(PK1, SK1) andalso keypair_valid(PK2, SK2) of
|
|
true ->
|
|
enacl:box_beforenm(PK1, SK2);
|
|
false ->
|
|
oneof([
|
|
elements([a,b,c]),
|
|
real(),
|
|
?SUCHTHAT(X, binary(), byte_size(X) /= enacl:box_beforenm_bytes())
|
|
])
|
|
end).
|
|
|
|
v_key(K) when is_binary(K) -> byte_size(K) == enacl:box_beforenm_bytes();
|
|
v_key(_) -> false.
|
|
|
|
prop_beforenm_correct() ->
|
|
?FORALL([{PK1, SK1}, {PK2, SK2}], [?FAULT_RATE(1, 40, keypair()), ?FAULT_RATE(1, 40, keypair())],
|
|
case keypair_valid(PK1, SK1) andalso keypair_valid(PK2, SK2) of
|
|
true ->
|
|
equals(enacl:box_beforenm(PK1, SK2), enacl:box_beforenm(PK2, SK1));
|
|
false ->
|
|
badargs(fun() ->
|
|
K = enacl:box_beforenm(PK1, SK2),
|
|
K = enacl:box_beforenm(PK2, SK1)
|
|
end)
|
|
end).
|
|
|
|
prop_afternm_correct() ->
|
|
?FORALL([Msg, Nonce, Key],
|
|
[?FAULT_RATE(1, 40, g_iodata()),
|
|
?FAULT_RATE(1, 40, nonce()),
|
|
?FAULT_RATE(1, 40, beforenm_key())],
|
|
begin
|
|
case v_iodata(Msg) andalso nonce_valid(Nonce) andalso v_key(Key) of
|
|
true ->
|
|
CipherText = enacl:box_afternm(Msg, Nonce, Key),
|
|
equals({ok, iolist_to_binary(Msg)}, enacl:box_open_afternm(CipherText, Nonce, Key));
|
|
false ->
|
|
try enacl:box_afternm(Msg, Nonce, Key) of
|
|
CipherText ->
|
|
try enacl:box_open_afternm(CipherText, Nonce, Key) of
|
|
{ok, _M} -> false;
|
|
{error, failed_validation} -> false
|
|
catch
|
|
error:badarg -> true
|
|
end
|
|
catch
|
|
error:badarg -> true
|
|
end
|
|
end
|
|
end).
|
|
|
|
%% SIGNATURES
|
|
%% ----------
|
|
|
|
prop_sign_keypair() ->
|
|
?FORALL(_D, return(dummy),
|
|
begin
|
|
#{ public := _, secret := _ } = enacl:sign_keypair(),
|
|
true
|
|
end).
|
|
|
|
sign_keypair_bad() ->
|
|
?LET(X, elements([pk, sk]),
|
|
begin
|
|
KP = enacl:sign_keypair(),
|
|
case X of
|
|
pk ->
|
|
Sz = enacl:sign_keypair_public_size(),
|
|
?LET(Wrong, oneof([a, int(), ?SUCHTHAT(B, binary(), byte_size(B) /= Sz)]),
|
|
KP#{ public := Wrong });
|
|
sk ->
|
|
Sz = enacl:sign_keypair_secret_size(),
|
|
?LET(Wrong, oneof([a, int(), ?SUCHTHAT(B, binary(), byte_size(B) /= Sz)]),
|
|
KP#{ secret := Wrong })
|
|
end
|
|
end).
|
|
|
|
sign_keypair_good() ->
|
|
return(enacl:sign_keypair()).
|
|
|
|
sign_keypair() ->
|
|
?FAULT(sign_keypair_bad(), sign_keypair_good()).
|
|
|
|
sign_keypair_public_valid(#{ public := Public })
|
|
when is_binary(Public) ->
|
|
byte_size(Public) == enacl:sign_keypair_public_size();
|
|
sign_keypair_public_valid(_) -> false.
|
|
|
|
sign_keypair_secret_valid(#{ secret := Secret })
|
|
when is_binary(Secret) ->
|
|
byte_size(Secret) == enacl:sign_keypair_secret_size();
|
|
sign_keypair_secret_valid(_) -> false.
|
|
|
|
sign_keypair_valid(KP) ->
|
|
sign_keypair_public_valid(KP) andalso sign_keypair_secret_valid(KP).
|
|
|
|
prop_sign_detached() ->
|
|
?FORALL({Msg, KeyPair},
|
|
{?FAULT_RATE(1, 40, g_iodata()),
|
|
?FAULT_RATE(1, 40, sign_keypair())},
|
|
begin
|
|
case v_iodata(Msg) andalso sign_keypair_secret_valid(KeyPair) of
|
|
true ->
|
|
#{ secret := Secret } = KeyPair,
|
|
enacl:sign_detached(Msg, Secret),
|
|
true;
|
|
false ->
|
|
#{ secret := Secret } = KeyPair,
|
|
badargs(fun() -> enacl:sign_detached(Msg, Secret) end)
|
|
end
|
|
end).
|
|
|
|
prop_sign() ->
|
|
?FORALL({Msg, KeyPair},
|
|
{?FAULT_RATE(1, 40, g_iodata()),
|
|
?FAULT_RATE(1, 40, sign_keypair())},
|
|
begin
|
|
case v_iodata(Msg) andalso sign_keypair_secret_valid(KeyPair) of
|
|
true ->
|
|
#{ secret := Secret } = KeyPair,
|
|
enacl:sign(Msg, Secret),
|
|
true;
|
|
false ->
|
|
#{ secret := Secret } = KeyPair,
|
|
badargs(fun() -> enacl:sign(Msg, Secret) end)
|
|
end
|
|
end).
|
|
|
|
signed_message_good(M) ->
|
|
#{ public := PK, secret := SK} = enacl:sign_keypair(),
|
|
SM = enacl:sign(M, SK),
|
|
frequency([
|
|
{3, return({{valid, SM}, PK})},
|
|
{1, ?LET(X, elements([sm, pk]),
|
|
case X of
|
|
sm -> {{invalid, binary(byte_size(SM))}, PK};
|
|
pk -> {{invalid, SM}, binary(byte_size(PK))}
|
|
end)}]).
|
|
|
|
signed_message_good_d(M) ->
|
|
#{ public := PK, secret := SK} = enacl:sign_keypair(),
|
|
Sig = enacl:sign_detached(M, SK),
|
|
frequency([
|
|
{3, return({{valid, Sig}, PK})},
|
|
{1, ?LET(X, elements([sm, pk]),
|
|
case X of
|
|
sm -> {{invalid, binary(byte_size(Sig))}, PK};
|
|
pk -> {{invalid, Sig}, binary(byte_size(PK))}
|
|
end)}]).
|
|
|
|
signed_message_bad() ->
|
|
Sz = enacl:sign_keypair_public_size(),
|
|
{binary(), oneof([a, int(), ?SUCHTHAT(B, binary(Sz), byte_size(B) /= Sz)])}.
|
|
|
|
signed_message_bad_d() ->
|
|
Sz = enacl:sign_keypair_public_size(),
|
|
{binary(), oneof([a, int(), ?SUCHTHAT(B, binary(Sz), byte_size(B) /= Sz)])}.
|
|
|
|
signed_message(M) ->
|
|
?FAULT(signed_message_bad(), signed_message_good(M)).
|
|
|
|
signed_message_d(M) ->
|
|
?FAULT(signed_message_bad(), signed_message_good(M)).
|
|
|
|
signed_message_valid({valid, _}, _) -> true;
|
|
signed_message_valid({invalid, _}, _) -> true;
|
|
signed_message_valid(_, _) -> false.
|
|
|
|
prop_sign_detached_open() ->
|
|
?FORALL(Msg, g_iodata(),
|
|
?FORALL({SignMsg, PK}, signed_message_d(Msg),
|
|
case v_iodata(Msg) andalso signed_message_valid(SignMsg, PK) of
|
|
true ->
|
|
case SignMsg of
|
|
{valid, Sig} ->
|
|
equals({ok, Msg}, enacl:sign_verify_detached(Sig, Msg, PK));
|
|
{invalid, Sig} ->
|
|
equals({error, failed_verification}, enacl:sign_verify_detached(Sig, Msg, PK))
|
|
end;
|
|
false ->
|
|
badargs(fun() -> enacl:sign_verify_detached(SignMsg, Msg, PK) end)
|
|
end)).
|
|
|
|
prop_sign_open() ->
|
|
?FORALL(Msg, g_iodata(),
|
|
?FORALL({SignMsg, PK}, signed_message(Msg),
|
|
case v_iodata(Msg) andalso signed_message_valid(SignMsg, PK) of
|
|
true ->
|
|
case SignMsg of
|
|
{valid, SM} ->
|
|
equals({ok, iolist_to_binary(Msg)}, enacl:sign_open(SM, PK));
|
|
{invalid, SM} ->
|
|
equals({error, failed_verification}, enacl:sign_open(SM, PK))
|
|
end;
|
|
false ->
|
|
badargs(fun() -> enacl:sign_open(SignMsg, PK) end)
|
|
end)).
|
|
|
|
prop_seal_box_failure_integrity() ->
|
|
?FORALL({Msg, {PK1, SK1}}, {?FAULT_RATE(1,40,g_iodata()), ?FAULT_RATE(1,40,keypair())},
|
|
begin
|
|
case v_iodata(Msg) andalso keypair_valid(PK1, SK1) of
|
|
true ->
|
|
CT = enacl:box_seal(Msg, PK1),
|
|
Err = enacl:box_seal_open([<<"x">>, CT], PK1, SK1),
|
|
equals(Err, {error, failed_verification});
|
|
false ->
|
|
case box_seal(Msg, PK1) of
|
|
badarg -> true;
|
|
Res ->
|
|
failure(box_seal_open(Res, PK1, SK1))
|
|
end
|
|
end
|
|
end).
|
|
|
|
prop_seal_box_correct() ->
|
|
?FORALL({Msg, {PK1, SK1}},
|
|
{?FAULT_RATE(1, 40, g_iodata()),
|
|
?FAULT_RATE(1, 40, keypair())},
|
|
begin
|
|
case v_iodata(Msg) andalso keypair_valid(PK1, SK1) of
|
|
true ->
|
|
SealedCipherText = enacl:box_seal(Msg, PK1),
|
|
{ok, DecodedMsg} = enacl:box_seal_open(SealedCipherText, PK1, SK1),
|
|
equals(iolist_to_binary(Msg), DecodedMsg);
|
|
false ->
|
|
case box_seal(Msg, PK1) of
|
|
badarg -> true;
|
|
Res -> failure(box_seal_open(Res, PK1, SK1))
|
|
end
|
|
end
|
|
end).
|
|
|
|
|
|
%% CRYPTO SECRET BOX
|
|
%% ------------------------------------------------------------
|
|
%% * secretbox/3
|
|
%% * secretbo_open/3
|
|
secret_key_good() ->
|
|
Sz = enacl:secretbox_key_size(),
|
|
binary(Sz).
|
|
|
|
secret_key_bad() ->
|
|
oneof([return(a),
|
|
nat(),
|
|
?SUCHTHAT(B, binary(), byte_size(B) /= enacl:secretbox_key_size())]).
|
|
|
|
secret_key() ->
|
|
?FAULT(secret_key_bad(), secret_key_good()).
|
|
|
|
secret_key_valid(SK) when is_binary(SK) ->
|
|
Sz = enacl:secretbox_key_size(),
|
|
byte_size(SK) == Sz;
|
|
secret_key_valid(_SK) -> false.
|
|
|
|
secretbox(Msg, Nonce, Key) ->
|
|
try enacl:secretbox(Msg, Nonce, Key)
|
|
catch error:badarg -> badarg
|
|
end.
|
|
|
|
secretbox_open(Msg, Nonce, Key) ->
|
|
try enacl:secretbox_open(Msg, Nonce, Key)
|
|
catch error:badarg -> badarg
|
|
end.
|
|
|
|
prop_secretbox_correct() ->
|
|
?FORALL({Msg, Nonce, Key},
|
|
{?FAULT_RATE(1, 40, g_iodata()),
|
|
?FAULT_RATE(1, 40, nonce()),
|
|
?FAULT_RATE(1, 40, secret_key())},
|
|
begin
|
|
case v_iodata(Msg) andalso nonce_valid(Nonce) andalso secret_key_valid(Key) of
|
|
true ->
|
|
CipherText = enacl:secretbox(Msg, Nonce, Key),
|
|
{ok, DecodedMsg} = enacl:secretbox_open(CipherText, Nonce, Key),
|
|
equals(iolist_to_binary(Msg), DecodedMsg);
|
|
false ->
|
|
case secretbox(Msg, Nonce, Key) of
|
|
badarg -> true;
|
|
Res ->
|
|
failure(secretbox_open(Res, Nonce, Key))
|
|
end
|
|
end
|
|
end).
|
|
|
|
prop_secretbox_failure_integrity() ->
|
|
?FORALL({Msg, Nonce, Key}, {g_iodata(), nonce(), secret_key()},
|
|
begin
|
|
CipherText = enacl:secretbox(Msg, Nonce, Key),
|
|
Err = enacl:secretbox_open([<<"x">>, CipherText], Nonce, Key),
|
|
equals(Err, {error, failed_verification})
|
|
end).
|
|
|
|
%% AEAD ChaCha20Poly1305
|
|
%% ------------------------------------------------------------
|
|
%% * aead_chacha20poly1305_encrypt/4,
|
|
%% * aead_chacha20poly1305_decrypt/4,
|
|
prop_aead_chacha20poly1305() ->
|
|
?FORALL({Key, Msg, AD, Nonce},
|
|
{binary(32), binary(), ?LET(ADBytes, choose(0,16), binary(ADBytes)), largeint()},
|
|
begin
|
|
EncryptMsg = enacl:aead_chacha20poly1305_encrypt(Key, Nonce, AD, Msg),
|
|
equals(enacl:aead_chacha20poly1305_decrypt(Key, Nonce, AD, EncryptMsg), Msg)
|
|
end).
|
|
|
|
prop_aead_chacha20poly1305_fail() ->
|
|
?FORALL({Key, Msg, AD, Nonce},
|
|
{binary(32), binary(), ?LET(ADBytes, choose(0,16), binary(ADBytes)), largeint()},
|
|
begin
|
|
EncryptMsg = enacl:aead_chacha20poly1305_encrypt(Key, Nonce, AD, Msg),
|
|
case enacl:aead_chacha20poly1305_decrypt(Key, Nonce, AD, <<0:8, EncryptMsg/binary>>) of
|
|
{error, _} -> true;
|
|
_ -> false
|
|
end
|
|
end).
|
|
|
|
%% CRYPTO STREAM
|
|
%% ------------------------------------------------------------
|
|
%% * stream/3
|
|
prop_stream_correct() ->
|
|
?FORALL({Len, Nonce, Key},
|
|
{int(),
|
|
?FAULT_RATE(1, 40, nonce()),
|
|
?FAULT_RATE(1, 40, secret_key())},
|
|
case Len >= 0 andalso nonce_valid(Nonce) andalso secret_key_valid(Key) of
|
|
true ->
|
|
CipherStream = enacl:stream(Len, Nonce, Key),
|
|
equals(Len, byte_size(CipherStream));
|
|
false ->
|
|
badargs(fun() -> enacl:stream(Len, Nonce, Key) end)
|
|
end).
|
|
|
|
xor_bytes(<<A, As/binary>>, <<B, Bs/binary>>) ->
|
|
[A bxor B | xor_bytes(As, Bs)];
|
|
xor_bytes(<<>>, <<>>) -> [].
|
|
|
|
%% prop_stream_xor_correct() ->
|
|
%% ?FORALL({Msg, Nonce, Key},
|
|
%% {?FAULT_RATE(1, 40, g_iodata()),
|
|
%% ?FAULT_RATE(1, 40, nonce()),
|
|
%% ?FAULT_RATE(1, 40, secret_key())},
|
|
%% case v_iodata(Msg) andalso nonce_valid(Nonce) andalso secret_key_valid(Key) of
|
|
%% true ->
|
|
%% Stream = enacl:stream(iolist_size(Msg), Nonce, Key),
|
|
%% CipherText = enacl:stream_xor(Msg, Nonce, Key),
|
|
%% StreamXor = enacl:stream_xor(CipherText, Nonce, Key),
|
|
%% conjunction([
|
|
%% {'xor', equals(iolist_to_binary(Msg), StreamXor)},
|
|
%% {stream, equals(iolist_to_binary(xor_bytes(Stream, iolist_to_binary(Msg))), CipherText)}
|
|
%% ]);
|
|
%% false ->
|
|
%% badargs(fun() -> enacl:stream_xor(Msg, Nonce, Key) end)
|
|
%% end).
|
|
|
|
%% CRYPTO AUTH
|
|
%% ------------------------------------------------------------
|
|
%% * auth/2
|
|
%% * auth_verify/3
|
|
prop_auth_correct() ->
|
|
?FORALL({Msg, Key},
|
|
{?FAULT_RATE(1, 40, g_iodata()),
|
|
?FAULT_RATE(1, 40, secret_key())},
|
|
case v_iodata(Msg) andalso secret_key_valid(Key) of
|
|
true ->
|
|
Authenticator = enacl:auth(Msg, Key),
|
|
equals(Authenticator, enacl:auth(Msg, Key));
|
|
false ->
|
|
badargs(fun() -> enacl:auth(Msg, Key) end)
|
|
end).
|
|
|
|
authenticator_bad() ->
|
|
oneof([a, int(), ?SUCHTHAT(X, binary(), byte_size(X) /= enacl:auth_size())]).
|
|
|
|
authenticator_good(Msg, Key) when is_binary(Key) ->
|
|
Sz = enacl:secretbox_key_size(),
|
|
case v_iodata(Msg) andalso byte_size(Key) == Sz of
|
|
true ->
|
|
frequency([{1, ?LAZY({invalid, binary(enacl:auth_size())})},
|
|
{3, return({valid, enacl:auth(Msg, Key)})}]);
|
|
false ->
|
|
binary(enacl:auth_size())
|
|
end;
|
|
authenticator_good(_Msg, _Key) ->
|
|
binary(enacl:auth_size()).
|
|
|
|
authenticator(Msg, Key) ->
|
|
?FAULT(authenticator_bad(), authenticator_good(Msg, Key)).
|
|
|
|
authenticator_valid({valid, _}) -> true;
|
|
authenticator_valid({invalid, _}) -> true;
|
|
authenticator_valid(_) -> false.
|
|
|
|
prop_auth_verify_correct() ->
|
|
?FORALL({Msg, Key},
|
|
{?FAULT_RATE(1, 40, g_iodata()),
|
|
?FAULT_RATE(1, 40, secret_key())},
|
|
?FORALL(Authenticator, authenticator(Msg, Key),
|
|
case v_iodata(Msg) andalso secret_key_valid(Key) andalso authenticator_valid(Authenticator) of
|
|
true ->
|
|
case Authenticator of
|
|
{valid, A} ->
|
|
equals(true, enacl:auth_verify(A, Msg, Key));
|
|
{invalid, A} ->
|
|
equals(false, enacl:auth_verify(A, Msg, Key))
|
|
end;
|
|
false ->
|
|
badargs(fun() -> enacl:auth_verify(Authenticator, Msg, Key) end)
|
|
end)).
|
|
|
|
%% CRYPTO ONETIME AUTH
|
|
%% ------------------------------------------------------------
|
|
%% * onetime_auth/2
|
|
%% * onetime_auth_verify/3
|
|
prop_onetimeauth_correct() ->
|
|
?FORALL({Msg, Key},
|
|
{?FAULT_RATE(1, 40, g_iodata()),
|
|
?FAULT_RATE(1, 40, secret_key())},
|
|
case v_iodata(Msg) andalso secret_key_valid(Key) of
|
|
true ->
|
|
Authenticator = enacl:onetime_auth(Msg, Key),
|
|
equals(Authenticator, enacl:onetime_auth(Msg, Key));
|
|
false ->
|
|
badargs(fun() -> enacl:onetime_auth(Msg, Key) end)
|
|
end).
|
|
|
|
ot_authenticator_bad() ->
|
|
oneof([a, int(), ?SUCHTHAT(X, binary(), byte_size(X) /= enacl:onetime_auth_size())]).
|
|
|
|
ot_authenticator_good(Msg, Key) when is_binary(Key) ->
|
|
Sz = enacl:secretbox_key_size(),
|
|
case v_iodata(Msg) andalso byte_size(Key) == Sz of
|
|
true ->
|
|
frequency([{1, ?LAZY({invalid, binary(enacl:onetime_auth_size())})},
|
|
{3, return({valid, enacl:onetime_auth(Msg, Key)})}]);
|
|
false ->
|
|
binary(enacl:onetime_auth_size())
|
|
end;
|
|
ot_authenticator_good(_Msg, _Key) ->
|
|
binary(enacl:auth_size()).
|
|
|
|
ot_authenticator(Msg, Key) ->
|
|
?FAULT(ot_authenticator_bad(), ot_authenticator_good(Msg, Key)).
|
|
|
|
ot_authenticator_valid({valid, _}) -> true;
|
|
ot_authenticator_valid({invalid, _}) -> true;
|
|
ot_authenticator_valid(_) -> false.
|
|
|
|
prop_onetime_auth_verify_correct() ->
|
|
?FORALL({Msg, Key},
|
|
{?FAULT_RATE(1, 40, g_iodata()),
|
|
?FAULT_RATE(1, 40, secret_key())},
|
|
?FORALL(Authenticator, ot_authenticator(Msg, Key),
|
|
case v_iodata(Msg) andalso secret_key_valid(Key) andalso ot_authenticator_valid(Authenticator) of
|
|
true ->
|
|
case Authenticator of
|
|
{valid, A} ->
|
|
equals(true, enacl:onetime_auth_verify(A, Msg, Key));
|
|
{invalid, A} ->
|
|
equals(false, enacl:onetime_auth_verify(A, Msg, Key))
|
|
end;
|
|
false ->
|
|
badargs(fun() -> enacl:onetime_auth_verify(Authenticator, Msg, Key) end)
|
|
end)).
|
|
|
|
%% PWHASH
|
|
%% -------------------------------
|
|
%% * pwhash/2
|
|
%% * pwhash_str/1
|
|
%% * pwhash_str_verify/2
|
|
pwhash(Passwd, Salt) ->
|
|
try
|
|
enacl:pwhash(Passwd, Salt)
|
|
catch
|
|
error:badarg -> badarg
|
|
end.
|
|
|
|
pwhash_str(Passwd) ->
|
|
try
|
|
enacl:pwhash_str(Passwd)
|
|
catch
|
|
error:badarg -> badarg
|
|
end.
|
|
|
|
pwhash_str_verify(PasswdHash, Passwd) ->
|
|
try
|
|
enacl:pwhash_str_verify(PasswdHash, Passwd)
|
|
catch
|
|
error:badarg -> badarg
|
|
end.
|
|
|
|
prop_pwhash_str_verify() ->
|
|
?FORALL({Passwd},
|
|
{?FAULT_RATE(1, 40, g_iodata())},
|
|
begin
|
|
case v_iodata(Passwd) of
|
|
true ->
|
|
{ok, Ascii} = enacl:pwhash_str(Passwd),
|
|
S = enacl:pwhash_str_verify(Ascii, Passwd),
|
|
equals(S, true);
|
|
false ->
|
|
badargs(fun() -> enacl:pwhash_str(Passwd) end),
|
|
badargs(fun() -> enacl:pwhash_str_verify("", Passwd) end)
|
|
end
|
|
end).
|
|
|
|
%% SUBTLE HASHING
|
|
%% ---------------------------
|
|
diff_pair() ->
|
|
?SUCHTHAT({X, Y}, {g_iodata(), g_iodata()},
|
|
iolist_to_binary(X) /= iolist_to_binary(Y)).
|
|
|
|
prop_crypto_hash_eq() ->
|
|
?FORALL(X, g_iodata(),
|
|
case v_iodata(X) of
|
|
true -> equals(enacl:hash(X), enacl:hash(X));
|
|
false ->
|
|
try
|
|
enacl:hash(X),
|
|
false
|
|
catch
|
|
error:badarg -> true
|
|
end
|
|
end
|
|
).
|
|
|
|
prop_crypto_hash_neq() ->
|
|
?FORALL({X, Y}, diff_pair(),
|
|
enacl:hash(X) /= enacl:hash(Y)
|
|
).
|
|
|
|
%% STRING COMPARISON
|
|
%% -------------------------
|
|
%% * verify_16/2,
|
|
%% * verify_32/2
|
|
verify_pair_bad(Sz) ->
|
|
?LET(X, elements([fst, snd]),
|
|
case X of
|
|
fst ->
|
|
{?SUCHTHAT(B, binary(), byte_size(B) /= Sz), binary(Sz)};
|
|
snd ->
|
|
{binary(Sz), ?SUCHTHAT(B, binary(), byte_size(B) /= Sz)}
|
|
end).
|
|
|
|
verify_pair_good(Sz) ->
|
|
oneof([
|
|
?LET(Bin, binary(Sz), {Bin, Bin}),
|
|
?SUCHTHAT({X, Y}, {binary(Sz), binary(Sz)}, X /= Y)]).
|
|
|
|
verify_pair(Sz) ->
|
|
?FAULT(verify_pair_bad(Sz), verify_pair_good(Sz)).
|
|
|
|
verify_pair_valid(Sz, X, Y) ->
|
|
byte_size(X) == Sz andalso byte_size(Y) == Sz.
|
|
|
|
prop_verify_16() ->
|
|
?FORALL({X, Y}, verify_pair(16),
|
|
case verify_pair_valid(16, X, Y) of
|
|
true ->
|
|
equals(X == Y, enacl:verify_16(X, Y));
|
|
false ->
|
|
try
|
|
enacl:verify_16(X, Y),
|
|
false
|
|
catch
|
|
error:badarg -> true
|
|
end
|
|
end).
|
|
|
|
prop_verify_32() ->
|
|
?FORALL({X, Y}, verify_pair(32),
|
|
case verify_pair_valid(32, X, Y) of
|
|
true ->
|
|
equals(X == Y, enacl:verify_32(X, Y));
|
|
false ->
|
|
try
|
|
enacl:verify_32(X, Y),
|
|
false
|
|
catch
|
|
error:badarg -> true
|
|
end
|
|
end).
|
|
|
|
%% RANDOMBYTES
|
|
%% ------------------------------------------------------------
|
|
%% * randombytes/1
|
|
prop_randombytes() ->
|
|
?FORALL(X, g_nat(),
|
|
case is_nat(X) of
|
|
true ->
|
|
is_binary(enacl:randombytes(X));
|
|
false ->
|
|
try
|
|
enacl:randombytes(X),
|
|
false
|
|
catch
|
|
error:badarg ->
|
|
true
|
|
end
|
|
end).
|
|
|
|
%% INTERNAL FUNCTIONS
|
|
%% ------------------------------------------------------------
|
|
badargs(Thunk) ->
|
|
try
|
|
Thunk(),
|
|
false
|
|
catch
|
|
error:badarg -> true
|
|
end.
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Joel Test Blobs
|
|
|
|
test_basic_signing() ->
|
|
#{ public := PK0, secret := SK0 } = enacl:sign_keypair(),
|
|
#{ public := PK1, secret := SK1 } = enacl:sign_keypair(),
|
|
MSG0 = <<"This is super s3Kr3t, srsly!">>,
|
|
[
|
|
%% (+) Sign and open using valid keypair
|
|
case enacl:sign_open(enacl:sign(MSG0, SK0), PK0) of
|
|
{ok,MSG1} -> MSG0==MSG1;
|
|
_ -> false
|
|
end
|
|
, %% (-) Sign and open using invalid keypair
|
|
case enacl:sign_open(enacl:sign(MSG0, SK0), PK1) of
|
|
{error,failed_verification} -> true;
|
|
_ -> false
|
|
end
|
|
, %% (+) Detached mode sig and verify
|
|
{ enacl:sign_verify_detached(enacl:sign_detached(MSG0, SK0), MSG0, PK0)
|
|
, enacl:sign_verify_detached(enacl:sign_detached(MSG0, SK1), MSG0, PK1)
|
|
}
|
|
, %% (-) Incorrect sigs/PKs/messages given during verify
|
|
{ false == enacl:sign_verify_detached(enacl:sign_detached(MSG0, SK0), MSG0, PK1)
|
|
, false == enacl:sign_verify_detached(enacl:sign_detached(MSG0, SK1), MSG0, PK0)
|
|
, false == enacl:sign_verify_detached(enacl:sign_detached(MSG0, SK0), <<"bzzt">>, PK0)
|
|
}
|
|
].
|