Handle the Auth tests.
This commit is contained in:
parent
6a707aef57
commit
ec0cc1afaa
@ -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.
|
||||
|
@ -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.
|
||||
|
120
src/enacl.erl
120
src/enacl.erl
@ -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()).
|
||||
|
Loading…
x
Reference in New Issue
Block a user