From 57bb93683bd43ae4594299f635c3924d58001594 Mon Sep 17 00:00:00 2001 From: Jesper Louis Andersen Date: Wed, 26 Nov 2014 20:05:59 +0100 Subject: [PATCH] Introduce tests for authentication verification and provide basis for signatures. --- .gitignore | 4 +++ c_src/enacl_nif.c | 12 +++++++ eqc_test/enacl_eqc.erl | 78 ++++++++++++++++++++++++++++++++++++++++++ src/enacl.erl | 52 ++++++++++++++++++++++++++-- src/enacl_nif.erl | 4 +++ 5 files changed, 148 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 2238cdb..4b192af 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ ebin *.eqc *.so eqc_test/.eqc-info +doc/edoc-info +doc/*.html +doc/*.png +doc/*.css diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 1e81e9c..2af2194 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -304,11 +304,21 @@ ERL_NIF_TERM enif_crypto_stream_NONCEBYTES(ErlNifEnv *env, int argc, ERL_NIF_TER return enif_make_int64(env, crypto_stream_NONCEBYTES); } +static +ERL_NIF_TERM enif_crypto_auth_BYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_auth_BYTES); +} + static ERL_NIF_TERM enif_crypto_auth_KEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { return enif_make_int64(env, crypto_auth_KEYBYTES); } +static +ERL_NIF_TERM enif_crypto_onetimeauth_BYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_onetimeauth_BYTES); +} + static ERL_NIF_TERM enif_crypto_onetimeauth_KEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { return enif_make_int64(env, crypto_onetimeauth_KEYBYTES); @@ -571,10 +581,12 @@ static ErlNifFunc nif_funcs[] = { {"crypto_stream", 3, enif_crypto_stream, ERL_NIF_DIRTY_JOB_CPU_BOUND}, {"crypto_stream_xor", 3, enif_crypto_stream_xor, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"crypto_auth_BYTES", 0, enif_crypto_auth_BYTES}, {"crypto_auth_KEYBYTES", 0, enif_crypto_auth_KEYBYTES}, {"crypto_auth", 2, enif_crypto_auth, ERL_NIF_DIRTY_JOB_CPU_BOUND}, {"crypto_auth_verify", 3, enif_crypto_auth_verify, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"crypto_onetimeauth_BYTES", 0, enif_crypto_onetimeauth_BYTES}, {"crypto_onetimeauth_KEYBYTES", 0, enif_crypto_onetimeauth_KEYBYTES}, {"crypto_onetimeauth", 2, enif_crypto_onetimeauth, ERL_NIF_DIRTY_JOB_CPU_BOUND}, {"crypto_onetimeauth_verify", 3, enif_crypto_onetimeauth_verify, ERL_NIF_DIRTY_JOB_CPU_BOUND}, diff --git a/eqc_test/enacl_eqc.erl b/eqc_test/enacl_eqc.erl index 98219d6..b1aa1d6 100644 --- a/eqc_test/enacl_eqc.erl +++ b/eqc_test/enacl_eqc.erl @@ -236,6 +236,45 @@ prop_auth_correct() -> 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 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}, + {binary(), + fault_rate(1, 40, secret_key())}, + ?FORALL(Authenticator, authenticator(Msg, Key), + case 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 prop_onetimeauth_correct() -> ?FORALL({Msg, Key}, @@ -249,6 +288,45 @@ prop_onetimeauth_correct() -> 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 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}, + {binary(), + fault_rate(1, 40, secret_key())}, + ?FORALL(Authenticator, ot_authenticator(Msg, Key), + case 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)). + %% HASHING %% --------------------------- diff_pair(Sz) -> diff --git a/src/enacl.erl b/src/enacl.erl index 95cd27d..64c785e 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -23,7 +23,11 @@ box_open/4, box_nonce_size/0, box_public_key_bytes/0, - box_secret_key_bytes/0 + box_secret_key_bytes/0, + + sign_keypair/0, + sign/2, + sign_open/2 ]). %% Secret key crypto @@ -39,10 +43,12 @@ stream_xor/3, auth_key_size/0, + auth_size/0, auth/2, auth_verify/3, onetime_auth_key_size/0, + onetime_auth_size/0, onetime_auth/2, onetime_auth_verify/3 ]). @@ -139,6 +145,38 @@ box_nonce_size() -> box_public_key_bytes() -> enacl_nif:crypto_box_PUBLICKEYBYTES(). +%% Signatures + +%% @doc sign_keypair/0 returns a signature keypair for signing +%% The returned value is a map in order to make it harder to misuse keys. +%% @end +-spec sign_keypair() -> KeyMap + when KeyMap :: maps:map(atom(), binary()). +sign_keypair() -> + {PK, SK} = enacl_nif:sign_keypair(), + #{ public => PK, secret => SK}. + +%% @doc sign/2 signs a message with a digital signature identified by a secret key. +%% Given a message `M' and a secret key `SK' the function will sign the message and return a signed message `SM'. +%% @end +-spec sign(M, SK) -> SM + when + M :: binary(), + SK :: binary(), + SM :: binary(). +sign(M, SK) -> enacl_nif:sign(M, SK). + +%% @doc sign_open/2 opens a digital signature +%% Given a signed message `SM' and a public key `PK', verify that the message has the right signature. Returns either +%% `{ok, M}' or `{error, failed_verification}' depending on the correctness of the signature. +%% @end +-spec sign_open(SM, PK) -> {ok, M} | {error, failed_verification} + when + SM :: binary(), + PK :: binary(), + M :: binary(). +sign_open(SM, PK) -> enacl_nif:sign_open(SM, PK). + %% @private -spec box_secret_key_bytes() -> pos_integer(). box_secret_key_bytes() -> @@ -204,6 +242,11 @@ stream_xor(Msg, Nonce, Key) -> -spec auth_key_size() -> pos_integer(). auth_key_size() -> enacl_nif:crypto_auth_KEYBYTES(). +%% @doc auth_size/0 returns the byte-size of the authenticator +%% @end +-spec auth_size() -> pos_integer(). +auth_size() -> enacl_nif:crypto_auth_BYTES(). + %% @doc auth/2 produces an authenticator (MAC) for a message %% Given a `Msg' and a `Key' produce a MAC/Authenticator for that message. The key can be reused for several such Msg/Authenticator pairs. %% An eavesdropper will not learn anything extra about the message structure. @@ -239,7 +282,7 @@ onetime_auth(Msg, Key) -> enacl_nif:crypto_onetimeauth(Msg, Key). %% @doc onetime_auth_verify/3 verifies an ONE-TIME authenticator for a message %% Given an `Authenticator', a `Msg' and a `Key'; verify that the MAC for the pair `{Msg, Key}' is really `Authenticator'. Returns -%% the value `true' if the verfication passes. Upon failure, the function returns `false'. Note the caveat from {@link onetime_auth/2} +%% the value `true' if the verification passes. Upon failure, the function returns `false'. Note the caveat from {@link onetime_auth/2} %% applies: you are not allowed to ever use the same key again for another message. %% @end -spec onetime_auth_verify(Authenticator, Msg, Key) -> boolean() @@ -249,6 +292,11 @@ onetime_auth(Msg, Key) -> enacl_nif:crypto_onetimeauth(Msg, Key). Key :: binary(). onetime_auth_verify(A, M, K) -> enacl_nif:crypto_onetimeauth_verify(A, M, K). +%% @doc onetime_auth_size/0 returns the number of bytes of the one-time authenticator +%% @end +-spec onetime_auth_size() -> pos_integer(). +onetime_auth_size() -> enacl_nif:crypto_onetimeauth_BYTES(). + %% @doc onetime_auth_key_size/0 returns the byte-size of the onetime authentication key %% @end -spec onetime_auth_key_size() -> pos_integer(). diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index eb0fe9f..c878d85 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -35,10 +35,12 @@ crypto_stream/3, crypto_stream_xor/3, + crypto_auth_BYTES/0, crypto_auth_KEYBYTES/0, crypto_auth/2, crypto_auth_verify/3, + crypto_onetimeauth_BYTES/0, crypto_onetimeauth_KEYBYTES/0, crypto_onetimeauth/2, crypto_onetimeauth_verify/3 @@ -93,10 +95,12 @@ crypto_stream_NONCEBYTES() -> not_loaded(). crypto_stream(_Bytes, _Nonce, _Key) -> not_loaded(). crypto_stream_xor(_M, _Nonce, _Key) -> not_loaded(). +crypto_auth_BYTES() -> not_loaded(). crypto_auth_KEYBYTES() -> not_loaded(). crypto_auth(_Msg, _Key) -> not_loaded(). crypto_auth_verify(_Authenticator, _Msg, _Key) -> not_loaded(). +crypto_onetimeauth_BYTES() -> not_loaded(). crypto_onetimeauth_KEYBYTES() -> not_loaded(). crypto_onetimeauth(_Msg, _Key) -> not_loaded(). crypto_onetimeauth_verify(_Authenticator, _Msg, _Key) -> not_loaded().