Commit d3471348 authored by Jesper Louis Andersen's avatar Jesper Louis Andersen
Browse files

Introduce negative testing.

Negative testing means we inject faulty data into the test now and then. When this happens, we make sure the SUT will
return some kind of badarg error for bad arguments. This means we should make sure things actually work out as they should.

As a side-effect, this can also be used to test for memory leaks. If run for a while, it makes sure there are no leaks in the code base,
and it probably also makes sure there are no ways to crash the server by any means of use of these NIFs. As such, it looks like the
NIFs are fairly stable.
parent c08f83a7
......@@ -44,6 +44,16 @@ ERL_NIF_TERM enif_crypto_box_BOXZEROBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM
return enif_make_int64(env, crypto_box_BOXZEROBYTES);
}
static
ERL_NIF_TERM enif_crypto_box_PUBLICKEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) {
return enif_make_int64(env, crypto_box_PUBLICKEYBYTES);
}
static
ERL_NIF_TERM enif_crypto_box_SECRETKEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) {
return enif_make_int64(env, crypto_box_SECRETKEYBYTES);
}
static
ERL_NIF_TERM enif_crypto_box_keypair(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) {
ErlNifBinary pk, sk;
......@@ -237,6 +247,8 @@ static ErlNifFunc nif_funcs[] = {
{"crypto_box_NONCEBYTES", 0, enif_crypto_box_NONCEBYTES},
{"crypto_box_ZEROBYTES", 0, enif_crypto_box_ZEROBYTES},
{"crypto_box_BOXZEROBYTES", 0, enif_crypto_box_BOXZEROBYTES},
{"crypto_box_PUBLICKEYBYTES", 0, enif_crypto_box_PUBLICKEYBYTES},
{"crypto_box_SECRETKEYBYTES", 0, enif_crypto_box_SECRETKEYBYTES},
{"crypto_box_keypair", 0, enif_crypto_box_keypair},
{"crypto_box", 4, enif_crypto_box, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"crypto_box_open", 4, enif_crypto_box_open, ERL_NIF_DIRTY_JOB_CPU_BOUND},
......
......@@ -2,54 +2,172 @@
-include_lib("eqc/include/eqc.hrl").
-compile(export_all).
nonce() ->
nonce_good() ->
Sz = enacl:box_nonce_size(),
binary(Sz).
nonce_bad() ->
Sz = enacl:box_nonce_size(),
oneof([return(a), nat(), ?SUCHTHAT(B, binary(), byte_size(B) /= Sz)]).
nonce_valid(N) when is_binary(N) ->
Sz = enacl:box_nonce_size(),
byte_size(N) == Sz;
nonce_valid(_) -> false.
nonce() ->
fault(nonce_bad(), nonce_good()).
keypair_good() ->
{ok, PK, SK} = enacl:box_keypair(),
{PK, SK}.
keypair_bad() ->
?LET(X, elements([pk, sk]),
begin
{ok, PK, 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
%% ---------------------------
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(enacl:box_keypair())).
ok_box_keypair(enacl:box_keypair())).
ok_box({ok, _PK, _SK}) -> true;
ok_box(_) -> false.
ok_box_keypair({ok, _PK, _SK}) -> true;
ok_box_keypair(_) -> false.
box(Msg, Nonce , PK, SK) ->
try
enacl:box(Msg, Nonce, 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(_) -> false.
prop_box_correct() ->
?FORALL({Msg, Nonce}, {binary(), nonce()},
?FORALL({Msg, Nonce, {PK1, SK1}, {PK2, SK2}},
{binary(),
fault_rate(1, 40, nonce()),
fault_rate(1, 40, keypair()),
fault_rate(1, 40, keypair())},
begin
{ok, PK1, SK1} = enacl:box_keypair(),
{ok, PK2, SK2} = enacl:box_keypair(),
CipherText = enacl:box(Msg, Nonce, PK2, SK1),
{ok, DecodedMsg} = enacl:box_open(CipherText, Nonce, PK1, SK2),
equals(Msg, DecodedMsg)
case nonce_valid(Nonce) andalso keypair_valid(PK1, SK1) andalso keypair_valid(PK2, SK2) of
true ->
CipherText = enacl:box(Msg, Nonce, PK2, SK1),
{ok, DecodedMsg} = enacl:box_open(CipherText, Nonce, PK1, SK2),
equals(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}, {binary(), nonce()},
?FORALL({Msg, Nonce, {PK1, SK1}, {PK2, SK2}},
{binary(),
fault_rate(1, 40, nonce()),
fault_rate(1, 40, keypair()),
fault_rate(1, 40, keypair())},
begin
{ok, PK1, SK1} = enacl:box_keypair(),
{ok, PK2, SK2} = enacl:box_keypair(),
CipherText = enacl:box(Msg, Nonce, PK2, SK1),
Err = enacl:box_open([<<"x">>, CipherText], Nonce, PK1, SK2),
equals(Err, {error, failed_verification})
case nonce_valid(Nonce)
andalso keypair_valid(PK1, SK1)
andalso keypair_valid(PK2, SK2) of
true ->
CipherText = enacl:box(Msg, Nonce, PK2, SK1),
Err = enacl:box_open([<<"x">>, CipherText], Nonce, PK1, SK2),
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).
%% CRYPTO SECRET BOX
%% -------------------------------
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() ->
Sz = enacl:secretbox_key_size(),
binary(Sz).
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}, {binary(), nonce(), secret_key()},
?FORALL({Msg, Nonce, Key},
{binary(),
fault_rate(1, 40, nonce()),
fault_rate(1, 40, secret_key())},
begin
CipherText = enacl:secretbox(Msg, Nonce, Key),
{ok, DecodedMsg} = enacl:secretbox_open(CipherText, Nonce, Key),
equals(Msg, DecodedMsg)
case 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(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() ->
......@@ -66,10 +184,30 @@ diff_pair(Sz) ->
?SUCHTHAT({X, Y}, {binary(Sz), binary(Sz)},
X /= Y).
data_bad() ->
oneof([return(a), nat()]).
data_good(Sz) -> binary(Sz).
data(Sz) ->
fault(data_bad(), data_good(Sz)).
data_valid(B) when is_binary(B) -> true;
data_valid(_B) -> false.
prop_crypto_hash_eq() ->
?FORALL(Sz, oneof([1, 128, 1024, 1024*4]),
?FORALL(X, binary(Sz),
equals(enacl:hash(X), enacl:hash(X))
?FORALL(X, data(Sz),
case data_valid(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() ->
......
......@@ -5,7 +5,9 @@
box_keypair/0,
box/4,
box_open/4,
box_nonce_size/0
box_nonce_size/0,
box_public_key_bytes/0,
box_secret_key_bytes/0
]).
%% Secret key crypto
......@@ -38,6 +40,12 @@ box_open(CipherText, Nonce, PK, SK) ->
box_nonce_size() ->
enacl_nif:crypto_box_NONCEBYTES().
box_public_key_bytes() ->
enacl_nif:crypto_box_PUBLICKEYBYTES().
box_secret_key_bytes() ->
enacl_nif:crypto_box_SECRETKEYBYTES().
secretbox(Msg, Nonce, Key) ->
enacl_nif:crypto_secretbox([s_zerobytes(), Msg], Nonce, Key).
......
......@@ -9,7 +9,9 @@
crypto_box_open/4,
crypto_box_NONCEBYTES/0,
crypto_box_ZEROBYTES/0,
crypto_box_BOXZEROBYTES/0
crypto_box_BOXZEROBYTES/0,
crypto_box_PUBLICKEYBYTES/0,
crypto_box_SECRETKEYBYTES/0
]).
%% Secret key crypto
......@@ -45,6 +47,9 @@ not_loaded() ->
crypto_box_NONCEBYTES() -> not_loaded().
crypto_box_ZEROBYTES() -> not_loaded().
crypto_box_BOXZEROBYTES() -> not_loaded().
crypto_box_PUBLICKEYBYTES() -> not_loaded().
crypto_box_SECRETKEYBYTES() -> not_loaded().
crypto_box_keypair() -> not_loaded().
crypto_box(_PaddedMsg, _Nonce, _PK, _SK) -> not_loaded().
crypto_box_open(_CipherText, _Nonce, _PK, _SK) -> not_loaded().
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment