From d3471348e289b3eb448c1a30ef0fddff1605ae30 Mon Sep 17 00:00:00 2001 From: Jesper Louis Andersen Date: Sat, 22 Nov 2014 23:26:45 +0100 Subject: [PATCH] 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. --- c_src/enacl_nif.c | 12 +++ eqc_test/enacl_eqc.erl | 186 +++++++++++++++++++++++++++++++++++------ src/enacl.erl | 10 ++- src/enacl_nif.erl | 7 +- 4 files changed, 189 insertions(+), 26 deletions(-) diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 607c262..f5f8ad9 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -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}, diff --git a/eqc_test/enacl_eqc.erl b/eqc_test/enacl_eqc.erl index 3844912..48ecb82 100644 --- a/eqc_test/enacl_eqc.erl +++ b/eqc_test/enacl_eqc.erl @@ -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() -> diff --git a/src/enacl.erl b/src/enacl.erl index 459f517..e1aebd8 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -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). diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index 7b22a44..dede21a 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -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().