Handle the Auth tests.

This commit is contained in:
Jesper Louis Andersen 2014-11-26 15:08:14 +01:00
parent 6a707aef57
commit ec0cc1afaa
3 changed files with 184 additions and 35 deletions

View File

@ -11,6 +11,10 @@ This package draws heavy inspiration from "erlang-nacl" by Tony Garnock-Jones.
In addition, I would like to thank Steve Vinoski and Sverker Eriksson for providing the Dirty Scheduler API in the first place.
# TODO
* Write Eunit/Common Test cases which verifies that the byte-output of the functions matches the expected output from the NaCl library.
# Rationale
Doing crypto right in Erlang is not that easy. The obvious way to handle this is by the use of NIF implementations, but most C code will run to its conclusion once set off for processing. This is a major problem for a system which needs to keep its latency in check. The solution taken by this library is to use the new Dirty Scheduler API of Erlang in order to provide a safe way to handle the long-running cryptographic processing. It keeps the cryptographic primitives on the dirty schedulers and thus it avoids the major problem.
@ -25,6 +29,8 @@ Every primitive has been stress-tested through the use of Erlang QuickCheck with
Positive and negative testing refers to Type I and Type II errors in statistical testing. This means false positives—given a *valid* input the function rejects it; as well as false negatives—given an *invalid* input the functions fails to reject that input.
The problem however, is that while we are testing the API level, we can't really test the strength of the cryptographic primitives. We can verify their correctness by trying different standard correctness tests for the primitives, verifying that the output matches the expected one given a specific input. But there is no way we can show that the cryptographic primitive has the strength we want. Thus, we opted to mostly test the API and its invocation for stability.
# Overview
The NaCl cryptographic library provides a number of different cryptographic primitives. In the following, we split up the different generic primitives and explain them briefly.

View File

@ -5,7 +5,7 @@
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)]).
@ -51,7 +51,7 @@ 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.
@ -118,15 +118,34 @@ prop_box_failure_integrity() ->
%% CRYPTO SECRET BOX
%% -------------------------------
%% Note: key sizes are the same in a lot of situations, so we can use the same generator
%% for keys in many locations.
key_sz(Sz) ->
equals(enacl:secretbox_key_size(), Sz).
prop_key_sizes() ->
conjunction([{secret, key_sz(enacl:secretbox_key_size())},
{stream, key_sz(enacl:stream_key_size())},
{auth, key_sz(enacl:auth_key_size())},
{onetimeauth, key_sz(enacl:onetime_auth_key_size())}]).
nonce_sz(Sz) ->
equals(enacl:secretbox_nonce_size(), Sz).
prop_nonce_sizes() ->
conjunction([{secret, nonce_sz(enacl:secretbox_nonce_size())},
{stream, nonce_sz(enacl:stream_nonce_size())}]).
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()).
@ -141,7 +160,7 @@ secretbox(Msg, Nonce, Key) ->
catch
error:badarg -> badarg
end.
secretbox_open(Msg, Nonce, Key) ->
try
enacl:secretbox_open(Msg, Nonce, Key)
@ -151,7 +170,7 @@ secretbox_open(Msg, Nonce, Key) ->
prop_secretbox_correct() ->
?FORALL({Msg, Nonce, Key},
{binary(),
{binary(),
fault_rate(1, 40, nonce()),
fault_rate(1, 40, secret_key())},
begin
@ -168,7 +187,7 @@ prop_secretbox_correct() ->
end
end
end).
prop_secretbox_failure_integrity() ->
?FORALL({Msg, Nonce, Key}, {binary(), nonce(), secret_key()},
begin
@ -177,6 +196,7 @@ prop_secretbox_failure_integrity() ->
equals(Err, {error, failed_verification})
end).
%% CRYPTO STREAM
prop_stream_correct() ->
?FORALL({Len, Nonce, Key},
{int(),
@ -187,12 +207,7 @@ prop_stream_correct() ->
CipherStream = enacl:stream(Len, Nonce, Key),
equals(Len, byte_size(CipherStream));
false ->
try
enacl:stream(Len, Nonce, Key),
false
catch
error:badarg -> true
end
badargs(fun() -> enacl:stream(Len, Nonce, Key) end)
end).
prop_stream_xor_correct() ->
@ -205,14 +220,35 @@ prop_stream_xor_correct() ->
CipherText = enacl:stream_xor(Msg, Nonce, Key),
equals(Msg, enacl:stream_xor(CipherText, Nonce, Key));
false ->
try
enacl:stream_xor(Msg, Nonce, Key),
false
catch
error:badarg -> true
end
badargs(fun() -> enacl:stream_xor(Msg, Nonce, Key) end)
end).
%% CRYPTO AUTH
prop_auth_correct() ->
?FORALL({Msg, Key},
{binary(),
fault_rate(1, 40, secret_key())},
case 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).
%% CRYPTO ONETIME AUTH
prop_onetimeauth_correct() ->
?FORALL({Msg, Key},
{binary(),
fault_rate(1, 40, secret_key())},
case 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).
%% HASHING
%% ---------------------------
diff_pair(Sz) ->
@ -244,7 +280,7 @@ prop_crypto_hash_eq() ->
end
end
)).
prop_crypto_hash_neq() ->
?FORALL(Sz, oneof([1, 128, 1024, 1024*4]),
?FORALL({X, Y}, diff_pair(Sz),
@ -267,7 +303,7 @@ 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)).
@ -287,7 +323,7 @@ prop_verify_16() ->
error:badarg -> true
end
end).
prop_verify_32() ->
?FORALL({X, Y}, verify_pair(32),
case verify_pair_valid(32, X, Y) of
@ -300,4 +336,13 @@ prop_verify_32() ->
catch
error:badarg -> true
end
end).
end).
%% HELPERS
badargs(Thunk) ->
try
Thunk(),
false
catch
error:badarg -> true
end.

View File

@ -28,13 +28,23 @@
%% Secret key crypto
-export([
secretbox_key_size/0,
secretbox_nonce_size/0,
secretbox/3,
secretbox_open/3,
secretbox_nonce_size/0,
secretbox_key_size/0,
stream_key_size/0,
stream_nonce_size/0,
stream/3,
stream_xor/3
stream_xor/3,
auth_key_size/0,
auth/2,
auth_verify/3,
onetime_auth_key_size/0,
onetime_auth/2,
onetime_auth_verify/3
]).
%% Low-level functions
@ -100,7 +110,7 @@ box_keypair() ->
CipherText :: binary().
box(Msg, Nonce, PK, SK) ->
enacl_nif:crypto_box([p_zerobytes(), Msg], Nonce, PK, SK).
%% @doc box_open/4 decrypts+verifies a message from another party.
%% Decrypt a `CipherText` into a `Msg` given the other partys public key `PK` and your secret
%% key `SK`. Also requires the same nonce as was used by the other party. Returns the plaintext
@ -128,7 +138,7 @@ box_nonce_size() ->
-spec box_public_key_bytes() -> pos_integer().
box_public_key_bytes() ->
enacl_nif:crypto_box_PUBLICKEYBYTES().
%% @private
-spec box_secret_key_bytes() -> pos_integer().
box_secret_key_bytes() ->
@ -136,7 +146,7 @@ box_secret_key_bytes() ->
secretbox(Msg, Nonce, Key) ->
enacl_nif:crypto_secretbox([s_zerobytes(), Msg], Nonce, Key).
secretbox_open(CipherText, Nonce, Key) ->
case enacl_nif:crypto_secretbox_open([s_box_zerobytes(), CipherText], Nonce, Key) of
{error, Err} -> {error, Err};
@ -145,26 +155,114 @@ secretbox_open(CipherText, Nonce, Key) ->
secretbox_nonce_size() ->
enacl_nif:crypto_secretbox_NONCEBYTES().
secretbox_key_size() ->
enacl_nif:crypto_secretbox_KEYBYTES().
%% @doc stream_nonce_size/0 returns the byte size of the nonce for streams
%% @end
-spec stream_nonce_size() -> pos_integer().
stream_nonce_size() -> enacl_nif:crypto_stream_NONCEBYTES().
%% @doc stream_key_size/0 returns the byte size of the key for streams
%% @end
-spec stream_key_size() -> pos_integer().
stream_key_size() -> enacl_nif:crypto_stream_KEYBYTES().
%% @doc stream/3 produces a cryptographic stream suitable for secret-key encryption
%% <p>Given a positive `Len' a `Nonce' and a `Key', the stream/3 function will return an unpredictable cryptographic stream of bytes
%% based on this output. In other words, the produced stream is indistinguishable from a random stream. Using this stream one
%% can XOR it with a message in order to produce a encrypted message.</p>
%% <p><b>Note:</b> You need to use different Nonce values for different messages. Otherwise the same stream is produced and thus
%% the messages will have predictability which in turn makes the encryption scheme fail.</p>
%% @end
-spec stream(Len, Nonce, Key) -> CryptoStream
when
Len :: non_neg_integer(),
Nonce :: binary(),
Key :: binary(),
CryptoStream :: binary().
stream(Len, Nonce, Key) when is_integer(Len), Len >= 0 ->
enacl_nif:crypto_stream(Len, Nonce, Key);
stream(_, _, _) -> error(badarg).
%% @doc stream_xor/3 encrypts a plaintext message into ciphertext
%% The stream_xor/3 function works by using the {@link stream/3} api to XOR a message with the cryptographic stream. The same
%% caveat applies: the nonce must be new for each sent message or the system fails to work.
%% @end
-spec stream_xor(Msg, Nonce, Key) -> CipherText
when
Msg :: binary(),
Nonce :: binary(),
Key :: binary(),
CipherText :: binary().
stream_xor(Msg, Nonce, Key) ->
enacl_nif:crypto_stream_xor(Msg, Nonce, Key).
%% @doc auth_key_size/0 returns the byte-size of the authentication key
%% @end
-spec auth_key_size() -> pos_integer().
auth_key_size() -> enacl_nif:crypto_auth_KEYBYTES().
%% @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.
%% @end
-spec auth(Msg, Key) -> Authenticator
when
Msg :: binary(),
Key :: binary(),
Authenticator :: binary().
auth(Msg, Key) -> enacl_nif:crypto_auth(Msg, Key).
%% @doc auth_verify/3 verifies an 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'.
%% @end
-spec auth_verify(Authenticator, Msg, Key) -> boolean()
when
Authenticator :: binary(),
Msg :: binary(),
Key :: binary().
auth_verify(A, M, K) -> enacl_nif:crypto_auth_verify(A, M, K).
%% @doc onetime_auth/2 produces a ONE-TIME authenticator for a message
%% This function works like {@link auth/2} except that the key must not be used again for subsequent messages. That is, the pair
%% `{Msg, Key}' is unique and only to be used once. The advantage is primarily faster execution.
%% @end
-spec onetime_auth(Msg, Key) -> Authenticator
when
Msg :: binary(),
Key :: binary(),
Authenticator :: binary().
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}
%% applies: you are not allowed to ever use the same key again for another message.
%% @end
-spec onetime_auth_verify(Authenticator, Msg, Key) -> boolean()
when
Authenticator :: binary(),
Msg :: binary(),
Key :: binary().
onetime_auth_verify(A, M, K) -> enacl_nif:crypto_onetimeauth_verify(A, M, K).
%% @doc onetime_auth_key_size/0 returns the byte-size of the onetime authentication key
%% @end
-spec onetime_auth_key_size() -> pos_integer().
onetime_auth_key_size() -> enacl_nif:crypto_onetimeauth_KEYBYTES().
%% Helpers
p_zerobytes() ->
binary:copy(<<0>>, enacl_nif:crypto_box_ZEROBYTES()).
p_box_zerobytes() ->
binary:copy(<<0>>, enacl_nif:crypto_box_BOXZEROBYTES()).
s_zerobytes() ->
binary:copy(<<0>>, enacl_nif:crypto_secretbox_ZEROBYTES()).
s_box_zerobytes() ->
binary:copy(<<0>>, enacl_nif:crypto_secretbox_BOXZEROBYTES()).