diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 77cc1e7..1d084c8 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -77,6 +77,21 @@ ERL_NIF_TERM enif_crypto_verify_32(ErlNifEnv *env, int argc, ERL_NIF_TERM const } } +/* This is very unsafe. It will not affect things that have been binary_copy()'ed + Use this for destroying key material from ram but nothing more. Be careful! */ +static +ERL_NIF_TERM enif_sodium_memzero(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary x; + + if ((argc != 1) || (!enif_inspect_binary(env, argv[0], &x))) { + return enif_make_badarg(env); + } + + sodium_memzero(x.data,x.size); + + return enif_make_atom(env, "ok"); +} + /* Curve 25519 */ static ERL_NIF_TERM enif_crypto_curve25519_scalarmult(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { @@ -637,6 +652,16 @@ ERL_NIF_TERM enif_crypto_secretbox_BOXZEROBYTES(ErlNifEnv *env, int argc, ERL_NI return enif_make_int64(env, crypto_secretbox_BOXZEROBYTES); } +static +ERL_NIF_TERM enif_crypto_stream_chacha20_KEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_stream_chacha20_KEYBYTES); +} + +static +ERL_NIF_TERM enif_crypto_stream_chacha20_NONCEBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_stream_chacha20_NONCEBYTES); +} + static ERL_NIF_TERM enif_crypto_stream_KEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { return enif_make_int64(env, crypto_stream_KEYBYTES); @@ -657,6 +682,16 @@ ERL_NIF_TERM enif_crypto_auth_KEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM co return enif_make_int64(env, crypto_auth_KEYBYTES); } +static +ERL_NIF_TERM enif_crypto_shorthash_BYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_shorthash_BYTES); +} + +static +ERL_NIF_TERM enif_crypto_shorthash_KEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_shorthash_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); @@ -742,6 +777,61 @@ ERL_NIF_TERM enif_crypto_secretbox_open(ErlNifEnv *env, int argc, ERL_NIF_TERM c padded_ciphertext.size - crypto_secretbox_ZEROBYTES); } +static +ERL_NIF_TERM enif_crypto_stream_chacha20(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary c, n, k; + ErlNifUInt64 clen; + + if ( + (argc != 3) || + (!enif_get_uint64(env, argv[0], &clen)) || + (!enif_inspect_binary(env, argv[1], &n)) || + (!enif_inspect_binary(env, argv[2], &k))) { + return enif_make_badarg(env); + } + + if ( + (k.size != crypto_stream_chacha20_KEYBYTES) || + (n.size != crypto_stream_chacha20_NONCEBYTES)) { + return enif_make_badarg(env); + } + + if (!enif_alloc_binary(clen, &c)) { + return nacl_error_tuple(env, "alloc_failed"); + } + + crypto_stream_chacha20(c.data, c.size, n.data, k.data); + + return enif_make_binary(env, &c); +} + +static +ERL_NIF_TERM enif_crypto_stream_chacha20_xor(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary c, m, n, k; + + if ( + (argc != 3) || + (!enif_inspect_iolist_as_binary(env, argv[0], &m)) || + (!enif_inspect_binary(env, argv[1], &n)) || + (!enif_inspect_binary(env, argv[2], &k))) { + return enif_make_badarg(env); + } + + if ( + (k.size != crypto_stream_chacha20_KEYBYTES) || + (n.size != crypto_stream_chacha20_NONCEBYTES)) { + return enif_make_badarg(env); + } + + if (!enif_alloc_binary(m.size, &c)) { + return nacl_error_tuple(env, "alloc_failed"); + } + + crypto_stream_chacha20_xor(c.data, m.data, m.size, n.data, k.data); + + return enif_make_binary(env, &c); +} + static ERL_NIF_TERM enif_crypto_stream(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { ErlNifBinary c, n, k; @@ -846,6 +936,30 @@ ERL_NIF_TERM enif_crypto_auth_verify(ErlNifEnv *env, int argc, ERL_NIF_TERM cons } } +static +ERL_NIF_TERM enif_crypto_shorthash(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary a,m,k; + + if ( + (argc != 2) || + (!enif_inspect_iolist_as_binary(env, argv[0], &m)) || + (!enif_inspect_binary(env, argv[1], &k))) { + return enif_make_badarg(env); + } + + if (k.size != crypto_shorthash_KEYBYTES) { + return enif_make_badarg(env); + } + + if (!enif_alloc_binary(crypto_shorthash_BYTES, &a)) { + return nacl_error_tuple(env, "alloc_failed"); + } + + crypto_shorthash(a.data, m.data, m.size, k.data); + + return enif_make_binary(env, &a); +} + static ERL_NIF_TERM enif_crypto_onetimeauth(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { ErlNifBinary a,m,k; @@ -1033,6 +1147,13 @@ static ErlNifFunc nif_funcs[] = { {"crypto_secretbox_open_b", 3, enif_crypto_secretbox_open}, {"crypto_secretbox_open", 3, enif_crypto_secretbox_open, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"crypto_stream_chacha20_KEYBYTES", 0, enif_crypto_stream_chacha20_KEYBYTES}, + {"crypto_stream_chacha20_NONCEBYTES", 0, enif_crypto_stream_chacha20_NONCEBYTES}, + {"crypto_stream_chacha20_b", 3, enif_crypto_stream_chacha20}, + {"crypto_stream_chacha20", 3, enif_crypto_stream_chacha20, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"crypto_stream_chacha20_xor_b", 3, enif_crypto_stream_chacha20_xor}, + {"crypto_stream_chacha20_xor", 3, enif_crypto_stream_chacha20_xor, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"crypto_stream_KEYBYTES", 0, enif_crypto_stream_KEYBYTES}, {"crypto_stream_NONCEBYTES", 0, enif_crypto_stream_NONCEBYTES}, {"crypto_stream_b", 3, enif_crypto_stream}, @@ -1047,6 +1168,10 @@ static ErlNifFunc nif_funcs[] = { {"crypto_auth_verify_b", 3, enif_crypto_auth_verify}, {"crypto_auth_verify", 3, enif_crypto_auth_verify, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"crypto_shorthash_BYTES", 0, enif_crypto_auth_BYTES}, + {"crypto_shorthash_KEYBYTES", 0, enif_crypto_shorthash_KEYBYTES}, + {"crypto_shorthash", 2, enif_crypto_shorthash}, + {"crypto_onetimeauth_BYTES", 0, enif_crypto_onetimeauth_BYTES}, {"crypto_onetimeauth_KEYBYTES", 0, enif_crypto_onetimeauth_KEYBYTES}, {"crypto_onetimeauth_b", 2, enif_crypto_onetimeauth}, @@ -1058,6 +1183,7 @@ static ErlNifFunc nif_funcs[] = { {"crypto_hash", 1, enif_crypto_hash, ERL_NIF_DIRTY_JOB_CPU_BOUND}, {"crypto_verify_16", 2, enif_crypto_verify_16}, {"crypto_verify_32", 2, enif_crypto_verify_32}, + {"sodium_memzero", 1, enif_sodium_memzero}, {"crypto_curve25519_scalarmult", 2, enif_crypto_curve25519_scalarmult, ERL_NIF_DIRTY_JOB_CPU_BOUND}, diff --git a/rebar.config b/rebar.config index 713e3df..36767fa 100644 --- a/rebar.config +++ b/rebar.config @@ -2,6 +2,8 @@ {pre_hooks, [{"freebsd", compile, "gmake -C c_src"}, {"freebsd", clean, "gmake -C c_src clean"}, + {"netbsd", compile, "gmake -C c_src"}, + {"netbsd", clean, "gmake -C c_src clean"}, {"(linux|darwin|solaris)", compile, "make -C c_src"}, {"(linux|darwin|solaris)", clean, "make -C c_src clean"} ]}. diff --git a/src/enacl.erl b/src/enacl.erl index d09d7db..03ea195 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -49,6 +49,11 @@ secretbox/3, secretbox_open/3, + stream_chacha20_key_size/0, + stream_chacha20_nonce_size/0, + stream_chacha20/3, + stream_chacha20_xor/3, + stream_key_size/0, stream_nonce_size/0, stream/3, @@ -59,6 +64,10 @@ auth/2, auth_verify/3, + shorthash_key_size/0, + shorthash_size/0, + shorthash/2, + onetime_auth_key_size/0, onetime_auth_size/0, onetime_auth/2, @@ -83,7 +92,8 @@ -export([ hash/1, verify_16/2, - verify_32/2 + verify_32/2, + unsafe_memzero/1 ]). %% Libsodium specific functions (which are also part of the "undocumented" interface to NaCl @@ -125,6 +135,8 @@ -define(S_ZEROBYTES, <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>). %% 32 bytes -define(CRYPTO_SECRETBOX_BOXZEROBYTES, 16). -define(S_BOXZEROBYTES, <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>). %% 16 bytes +-define(CRYPTO_STREAM_CHACHA20_KEYBYTES, 32). +-define(CRYPTO_STREAM_CHACHA20_NONCEBYTES, 8). -define(CRYPTO_STREAM_KEYBYTES, 32). -define(CRYPTO_STREAM_NONCEBYTES, 24). @@ -137,6 +149,8 @@ verify() -> ?S_BOXZEROBYTES), Verifiers = [ + {crypto_stream_chacha20_KEYBYTES, ?CRYPTO_STREAM_CHACHA20_KEYBYTES}, + {crypto_stream_chacha20_NONCEBYTES, ?CRYPTO_STREAM_CHACHA20_NONCEBYTES}, {crypto_stream_KEYBYTES, ?CRYPTO_STREAM_KEYBYTES}, {crypto_stream_NONCEBYTES, ?CRYPTO_STREAM_NONCEBYTES}, {crypto_box_ZEROBYTES, ?CRYPTO_BOX_ZEROBYTES}, @@ -201,6 +215,17 @@ verify_16(_, _) -> error(badarg). verify_32(X, Y) when is_binary(X), is_binary(Y) -> enacl_nif:crypto_verify_32(X, Y); verify_32(_, _) -> error(badarg). +%% @doc unsafe_memzero/1 ipmlements guaranteed zero'ing of binary data. +%% +%%
This is verify unsafe. If any copies of the binary have been made they are unaffected. +%% This is intended for use with cryptographic keys where they are only shared within +%% a running process without copies. This allows removing, eg, symmetric session keys.
+%% @end +-spec unsafe_memzero(binary()) -> atom(). +unsafe_memzero(X) when is_binary(X) -> enacl_nif:sodium_memzero(X); +unsafe_memzero(_) -> error(badarg). + %% Public Key Crypto %% --------------------- %% @doc box_keypair/0 creates a new Public/Secret keypair. @@ -497,14 +522,71 @@ secretbox_nonce_size() -> secretbox_key_size() -> enacl_nif:crypto_secretbox_KEYBYTES(). +%% @doc stream_chacha20_nonce_size/0 returns the byte size of the nonce for streams +%% @end +-spec stream_chacha20_nonce_size() -> ?CRYPTO_STREAM_CHACHA20_NONCEBYTES. +stream_chacha20_nonce_size() -> ?CRYPTO_STREAM_CHACHA20_NONCEBYTES. + +%% @doc stream_key_size/0 returns the byte size of the key for streams +%% @end +-spec stream_chacha20_key_size() -> ?CRYPTO_STREAM_CHACHA20_KEYBYTES. +stream_chacha20_key_size() -> ?CRYPTO_STREAM_CHACHA20_KEYBYTES. + +%% @doc stream_chacha20/3 produces a cryptographic stream suitable for secret-key encryption +%% +%%Given a positive `Len' a `Nonce' and a `Key', the stream_chacha20/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.
+%%Note: 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.
+%% @end +-spec stream_chacha20(Len, Nonce, Key) -> CryptoStream + when + Len :: non_neg_integer(), + Nonce :: binary(), + Key :: binary(), + CryptoStream :: binary(). +stream_chacha20(Len, Nonce, Key) when is_integer(Len), Len >= 0, Len =< ?STREAM_SIZE -> + bump(enacl_nif:crypto_stream_chacha20_b(Len, Nonce, Key), + ?STREAM_REDUCTIONS, + ?STREAM_SIZE, + Len); +stream_chacha20(Len, Nonce, Key) when is_integer(Len), Len >= 0 -> + enacl_nif:crypto_stream_chacha20(Len, Nonce, Key); +stream_chacha20(_, _, _) -> error(badarg). + +%% @doc stream_chacha20_xor/3 encrypts a plaintext message into ciphertext +%% +%% The stream_chacha20_xor/3 function works by using the {@link stream_chacha20/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_chacha20_xor(Msg, Nonce, Key) -> CipherText + when + Msg :: iodata(), + Nonce :: binary(), + Key :: binary(), + CipherText :: binary(). +stream_chacha20_xor(Msg, Nonce, Key) -> + case iolist_size(Msg) of + K when K =< ?STREAM_SIZE -> + bump(enacl_nif:crypto_stream_chacha20_xor_b(Msg, Nonce, Key), + ?STREAM_REDUCTIONS, + ?STREAM_SIZE, + K); + _ -> + enacl_nif:crypto_stream_chacha20_xor(Msg, Nonce, Key) + end. + +%% @doc auth_key_size/0 returns the byte-size of the authentication key +%% @end %% @doc stream_nonce_size/0 returns the byte size of the nonce for streams %% @end --spec stream_nonce_size() -> pos_integer(). +-spec stream_nonce_size() -> ?CRYPTO_STREAM_NONCEBYTES. stream_nonce_size() -> ?CRYPTO_STREAM_NONCEBYTES. %% @doc stream_key_size/0 returns the byte size of the key for streams %% @end --spec stream_key_size() -> pos_integer(). +-spec stream_key_size() -> ?CRYPTO_STREAM_KEYBYTES. stream_key_size() -> ?CRYPTO_STREAM_KEYBYTES. %% @doc stream/3 produces a cryptographic stream suitable for secret-key encryption @@ -601,6 +683,29 @@ auth_verify(A, M, K) -> enacl_nif:crypto_auth_verify(A, M, K) end. +%% @doc shorthash_key_size/0 returns the byte-size of the authentication key +%% @end +-spec shorthash_key_size() -> pos_integer(). +shorthash_key_size() -> enacl_nif:crypto_shorthash_KEYBYTES(). + +%% @doc shorthash_size/0 returns the byte-size of the authenticator +%% @end +-spec shorthash_size() -> pos_integer(). +shorthash_size() -> enacl_nif:crypto_shorthash_BYTES(). + +%% @doc shorthash/2 produces a short authenticator (MAC) for a message suitable for hashtables and refs +%% +%% 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 shorthash(Msg, Key) -> Authenticator + when + Msg :: iodata(), + Key :: binary(), + Authenticator :: binary(). +shorthash(Msg, Key) -> + enacl_nif:crypto_shorthash(Msg, Key). + %% @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 diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index 9640f74..917e9d4 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -52,6 +52,14 @@ crypto_secretbox_open/3, crypto_secretbox_open_b/3, + crypto_stream_chacha20_KEYBYTES/0, + crypto_stream_chacha20_NONCEBYTES/0, + + crypto_stream_chacha20/3, + crypto_stream_chacha20_b/3, + crypto_stream_chacha20_xor/3, + crypto_stream_chacha20_xor_b/3, + crypto_stream_KEYBYTES/0, crypto_stream_NONCEBYTES/0, @@ -68,6 +76,11 @@ crypto_auth_verify/3, crypto_auth_verify_b/3, + crypto_shorthash_BYTES/0, + crypto_shorthash_KEYBYTES/0, + + crypto_shorthash/2, + crypto_onetimeauth_BYTES/0, crypto_onetimeauth_KEYBYTES/0, @@ -96,7 +109,8 @@ crypto_hash/1, crypto_hash_b/1, crypto_verify_16/2, - crypto_verify_32/2 + crypto_verify_32/2, + sodium_memzero/1 ]). %% Access to the RNG @@ -163,6 +177,13 @@ crypto_secretbox_b(_Msg, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). crypto_secretbox_open(_Msg, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). crypto_secretbox_open_b(_Msg, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20_KEYBYTES() -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20_NONCEBYTES() -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20(_Bytes, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20_b(_Bytes, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20_xor(_M, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20_xor_b(_M, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). + crypto_stream_KEYBYTES() -> erlang:nif_error(nif_not_loaded). crypto_stream_NONCEBYTES() -> erlang:nif_error(nif_not_loaded). crypto_stream(_Bytes, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). @@ -177,6 +198,10 @@ crypto_auth_b(_Msg, _Key) -> erlang:nif_error(nif_not_loaded). crypto_auth_verify(_Authenticator, _Msg, _Key) -> erlang:nif_error(nif_not_loaded). crypto_auth_verify_b(_Authenticator, _Msg, _Key) -> erlang:nif_error(nif_not_loaded). +crypto_shorthash_BYTES() -> erlang:nif_error(nif_not_loaded). +crypto_shorthash_KEYBYTES() -> erlang:nif_error(nif_not_loaded). +crypto_shorthash(_Msg, _Key) -> erlang:nif_error(nif_not_loaded). + crypto_onetimeauth_BYTES() -> erlang:nif_error(nif_not_loaded). crypto_onetimeauth_KEYBYTES() -> erlang:nif_error(nif_not_loaded). crypto_onetimeauth(_Msg, _Key) -> erlang:nif_error(nif_not_loaded). @@ -196,6 +221,7 @@ crypto_hash(Input) when is_binary(Input) -> erlang:nif_error(nif_not_loaded). crypto_hash_b(Input) when is_binary(Input) -> erlang:nif_error(nif_not_loaded). crypto_verify_16(_X, _Y) -> erlang:nif_error(nif_not_loaded). crypto_verify_32(_X, _Y) -> erlang:nif_error(nif_not_loaded). +sodium_memzero(Input) when is_binary(Input) -> erlang:nif_error(nif_not_loaded). randombytes(_RequestedSize) -> erlang:nif_error(nif_not_loaded).