From d77907128507828316c9a6c06572d7893717e655 Mon Sep 17 00:00:00 2001 From: ECrownofFire Date: Sat, 27 Oct 2018 17:23:06 -0400 Subject: [PATCH 1/4] Add choice of ops and mem limits to pwhash_str It natively checks atoms, which is kinda messy, but it avoids having to export the libsodium pwhash constants, which is nice. --- c_src/enacl_nif.c | 67 +++++++++++++++++++++++++++++++++++++++++++---- src/enacl.erl | 20 +++++++++++++- src/enacl_nif.erl | 4 +-- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 364a47d..f2018f1 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -1259,6 +1259,61 @@ ERL_NIF_TERM enif_scramble_block_16(ErlNifEnv *env, int argc, ERL_NIF_TERM const return enif_make_binary(env, &out); } +static +size_t enacl_pwhash_opslimit(ErlNifEnv *env, ERL_NIF_TERM arg) { + ERL_NIF_TERM a; + size_t r; + + if (enif_is_atom(env, arg)) { + a = enif_make_atom(env, "interactive"); + if (enif_is_identical(a, arg)) { + return crypto_pwhash_OPSLIMIT_INTERACTIVE; + } + + a = enif_make_atom(env, "moderate"); + if (enif_is_identical(a, arg)) { + return crypto_pwhash_OPSLIMIT_MODERATE; + } + + a = enif_make_atom(env, "sensitive"); + if (enif_is_identical(a, arg)) { + return crypto_pwhash_OPSLIMIT_SENSITIVE; + } + } else if (enif_get_ulong(env, arg, &r)) { + return r; + } + + return 0; +} + +static +size_t enacl_pwhash_memlimit(ErlNifEnv *env, ERL_NIF_TERM arg) { + ERL_NIF_TERM a; + size_t r; + + if (enif_is_atom(env, arg)) { + a = enif_make_atom(env, "interactive"); + if (enif_is_identical(a, arg)) { + return crypto_pwhash_MEMLIMIT_INTERACTIVE; + } + + a = enif_make_atom(env, "moderate"); + if (enif_is_identical(a, arg)) { + return crypto_pwhash_MEMLIMIT_MODERATE; + } + + a = enif_make_atom(env, "sensitive"); + if (enif_is_identical(a, arg)) { + return crypto_pwhash_MEMLIMIT_SENSITIVE; + } + } else if (enif_get_ulong(env, arg, &r)) { + return r; + } + + return 0; +} + + static ERL_NIF_TERM enif_crypto_pwhash(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { ErlNifBinary h, p, s; @@ -1296,10 +1351,13 @@ ERL_NIF_TERM enif_crypto_pwhash(ErlNifEnv *env, int argc, ERL_NIF_TERM const arg static ERL_NIF_TERM enif_crypto_pwhash_str(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { ErlNifBinary h, p; + size_t o, m; // Validate the arguments - if( (argc != 1) || - (!enif_inspect_iolist_as_binary(env, argv[0], &p)) ) { + if( (argc != 3) || + (!enif_inspect_iolist_as_binary(env, argv[0], &p)) || + !(o = enacl_pwhash_opslimit(env, argv[1])) || + !(m = enacl_pwhash_memlimit(env, argv[2])) ) { return enif_make_badarg(env); } @@ -1308,8 +1366,7 @@ ERL_NIF_TERM enif_crypto_pwhash_str(ErlNifEnv *env, int argc, ERL_NIF_TERM const return nacl_error_tuple(env, "alloc_failed"); } - if( crypto_pwhash_str((char *)h.data, (char *)p.data, p.size, - crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE) != 0) { + if( crypto_pwhash_str((char *)h.data, (char *)p.data, p.size, o, m) != 0) { /* out of memory */ enif_release_binary(&h); return nacl_error_tuple(env, "out_of_memory"); @@ -1722,7 +1779,7 @@ static ErlNifFunc nif_funcs[] = { {"sodium_memzero", 1, enif_sodium_memzero}, {"crypto_pwhash", 2, enif_crypto_pwhash}, - {"crypto_pwhash_str", 1, enif_crypto_pwhash_str}, + {"crypto_pwhash_str", 3, enif_crypto_pwhash_str}, {"crypto_pwhash_str_verify", 2, enif_crypto_pwhash_str_verify}, erl_nif_dirty_job_cpu_bound_macro("crypto_curve25519_scalarmult", 2, enif_crypto_curve25519_scalarmult), diff --git a/src/enacl.erl b/src/enacl.erl index 15ea2c2..ffcc959 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -101,6 +101,9 @@ shorthash_size/0, shorthash/2, + %% No Tests! + pwhash_str/3, + %% EQC pwhash/2, pwhash_str/1, @@ -336,6 +339,7 @@ generichash_final({hashstate, HashSize, HashState}) -> enacl_nif:crypto_generichash_final(HashSize, HashState). +-type pwhash_limit() :: interactive | moderate | sensitive | pos_integer(). %% @doc pwhash/2 hash a password %% %% This function generates a fixed size salted hash of a user defined password. @@ -347,10 +351,24 @@ pwhash(Password, Salt) -> %% @doc pwhash_str/1 generates a ASCII encoded hash of a password %% %% This function generates a fixed size, salted, ASCII encoded hash of a user defined password. +%% Defaults to interactive/interactive limits. %% @end -spec pwhash_str(iodata()) -> {ok, iodata()} | {error, term()}. pwhash_str(Password) -> - case enacl_nif:crypto_pwhash_str(Password) of + pwhash_str(Password, interactive, interactive). + +%% @doc pwhash_str/3 generates a ASCII encoded hash of a password +%% +%% This function generates a fixed size, salted, ASCII encoded hash of a user defined password +%% given Ops and Mem limits. +%% @end +-spec pwhash_str(Password, Ops, Mem) -> {ok, iodata()} | {error, term()} + when + Password :: iodata(), + Ops :: pwhash_limit(), + Mem :: pwhash_limit(). +pwhash_str(Password, Ops, Mem) -> + case enacl_nif:crypto_pwhash_str(Password, Ops, Mem) of {ok, ASCII} -> {ok, strip_null_terminate(ASCII)}; {error, Reason} -> diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index 798ef53..4e2a50c 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -133,7 +133,7 @@ %% Password Hashing - Argon2 Algorithm -export([ crypto_pwhash/2, - crypto_pwhash_str/1, + crypto_pwhash_str/3, crypto_pwhash_str_verify/2 ]). @@ -189,7 +189,7 @@ crypto_generichash_update(_HashSize, _HashState, _Message) -> erlang:nif_error( crypto_generichash_final(_HashSize, _HashState) -> erlang:nif_error(nif_not_loaded). crypto_pwhash(_Password, _Salt) -> erlang:nif_error(nif_not_loaded). -crypto_pwhash_str(_Password) -> erlang:nif_error(nif_not_loaded). +crypto_pwhash_str(_Password, _Ops, _Mem) -> erlang:nif_error(nif_not_loaded). crypto_pwhash_str_verify(_HashedPassword, _Password) -> erlang:nif_error(nif_not_loaded). crypto_box_NONCEBYTES() -> erlang:nif_error(nif_not_loaded). From 07bcd872940c2788d7c633e627e35ad1799c6199 Mon Sep 17 00:00:00 2001 From: ECrownofFire Date: Sat, 27 Oct 2018 18:00:09 -0400 Subject: [PATCH 2/4] Add choice of ops and mem limits to pwhash --- c_src/enacl_nif.c | 13 ++++++++----- src/enacl.erl | 18 +++++++++++++++++- src/enacl_nif.erl | 4 ++-- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index f2018f1..4291c0a 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -1317,11 +1317,14 @@ size_t enacl_pwhash_memlimit(ErlNifEnv *env, ERL_NIF_TERM arg) { static ERL_NIF_TERM enif_crypto_pwhash(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { ErlNifBinary h, p, s; + size_t o, m; // Validate the arguments - if( (argc != 2) || + if( (argc != 4) || (!enif_inspect_iolist_as_binary(env, argv[0], &p)) || - (!enif_inspect_binary(env, argv[1], &s)) ) { + (!enif_inspect_binary(env, argv[1], &s)) || + !(o = enacl_pwhash_opslimit(env, argv[2])) || + !(m = enacl_pwhash_memlimit(env, argv[3])) ) { return enif_make_badarg(env); } @@ -1335,8 +1338,8 @@ ERL_NIF_TERM enif_crypto_pwhash(ErlNifEnv *env, int argc, ERL_NIF_TERM const arg return nacl_error_tuple(env, "alloc_failed"); } - if( crypto_pwhash(h.data, h.size, (char *)p.data, p.size, s.data, - crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE, crypto_pwhash_ALG_DEFAULT) != 0) { + if( crypto_pwhash(h.data, h.size, (char *)p.data, p.size, s.data, o, m, + crypto_pwhash_ALG_DEFAULT) != 0) { /* out of memory */ enif_release_binary(&h); return nacl_error_tuple(env, "out_of_memory"); @@ -1778,7 +1781,7 @@ static ErlNifFunc nif_funcs[] = { {"crypto_verify_32", 2, enif_crypto_verify_32}, {"sodium_memzero", 1, enif_sodium_memzero}, - {"crypto_pwhash", 2, enif_crypto_pwhash}, + {"crypto_pwhash", 4, enif_crypto_pwhash}, {"crypto_pwhash_str", 3, enif_crypto_pwhash_str}, {"crypto_pwhash_str_verify", 2, enif_crypto_pwhash_str_verify}, diff --git a/src/enacl.erl b/src/enacl.erl index ffcc959..ee4f767 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -102,6 +102,7 @@ shorthash/2, %% No Tests! + pwhash/4, pwhash_str/3, %% EQC @@ -343,10 +344,25 @@ generichash_final({hashstate, HashSize, HashState}) -> %% @doc pwhash/2 hash a password %% %% This function generates a fixed size salted hash of a user defined password. +%% Defaults to interactive/interactive limits. %% @end -spec pwhash(iodata(), binary()) -> {ok, binary()} | {error, term()}. pwhash(Password, Salt) -> - enacl_nif:crypto_pwhash(Password, Salt). + pwhash(Password, Salt, interactive, interactive). + +%% @doc pwhash/4 hash a password +%% +%% This function generates a fixed size salted hash of a user defined password given Ops and Mem +%% limits. +%% @end +-spec pwhash(Password, Salt, Ops, Mem) -> {ok, binary()} | {error, term()} + when + Password :: iodata(), + Salt :: binary(), + Ops :: pwhash_limit(), + Mem :: pwhash_limit(). +pwhash(Password, Salt, Ops, Mem) -> + enacl_nif:crypto_pwhash(Password, Salt, Ops, Mem). %% @doc pwhash_str/1 generates a ASCII encoded hash of a password %% diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index 4e2a50c..0f48852 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -132,7 +132,7 @@ %% Password Hashing - Argon2 Algorithm -export([ - crypto_pwhash/2, + crypto_pwhash/4, crypto_pwhash_str/3, crypto_pwhash_str_verify/2 ]). @@ -188,7 +188,7 @@ crypto_generichash_init(_HashSize, _Key) -> erlang:nif_error(nif_not_loaded). crypto_generichash_update(_HashSize, _HashState, _Message) -> erlang:nif_error(nif_not_loaded). crypto_generichash_final(_HashSize, _HashState) -> erlang:nif_error(nif_not_loaded). -crypto_pwhash(_Password, _Salt) -> erlang:nif_error(nif_not_loaded). +crypto_pwhash(_Password, _Salt, _Ops, _Mem) -> erlang:nif_error(nif_not_loaded). crypto_pwhash_str(_Password, _Ops, _Mem) -> erlang:nif_error(nif_not_loaded). crypto_pwhash_str_verify(_HashedPassword, _Password) -> erlang:nif_error(nif_not_loaded). From 26f4a40eb42e39931f2746a589ad857f81d65be9 Mon Sep 17 00:00:00 2001 From: ECrownofFire Date: Sat, 27 Oct 2018 18:03:12 -0400 Subject: [PATCH 3/4] Mark pwhash functions as CPU bound --- c_src/enacl_nif.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 4291c0a..0ab1e61 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -1781,9 +1781,9 @@ static ErlNifFunc nif_funcs[] = { {"crypto_verify_32", 2, enif_crypto_verify_32}, {"sodium_memzero", 1, enif_sodium_memzero}, - {"crypto_pwhash", 4, enif_crypto_pwhash}, - {"crypto_pwhash_str", 3, enif_crypto_pwhash_str}, - {"crypto_pwhash_str_verify", 2, enif_crypto_pwhash_str_verify}, + erl_nif_dirty_job_cpu_bound_macro("crypto_pwhash", 4, enif_crypto_pwhash), + erl_nif_dirty_job_cpu_bound_macro("crypto_pwhash_str", 3, enif_crypto_pwhash_str), + erl_nif_dirty_job_cpu_bound_macro("crypto_pwhash_str_verify", 2, enif_crypto_pwhash_str_verify), erl_nif_dirty_job_cpu_bound_macro("crypto_curve25519_scalarmult", 2, enif_crypto_curve25519_scalarmult), erl_nif_dirty_job_cpu_bound_macro("crypto_curve25519_scalarmult_base", 1, enif_crypto_curve25519_scalarmult_base), From 4afa6fc0930fe68cdab0ebd0ef1bb34f4b111d8f Mon Sep 17 00:00:00 2001 From: ECrownofFire Date: Sat, 27 Oct 2018 18:10:48 -0400 Subject: [PATCH 4/4] Add checks for ops/mem limit sizes --- c_src/enacl_nif.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 0ab1e61..ac4a13d 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -1328,6 +1328,14 @@ ERL_NIF_TERM enif_crypto_pwhash(ErlNifEnv *env, int argc, ERL_NIF_TERM const arg return enif_make_badarg(env); } + // Check limits + if( (o < crypto_pwhash_OPSLIMIT_MIN) || + (o > crypto_pwhash_OPSLIMIT_MAX) || + (m < crypto_pwhash_MEMLIMIT_MIN) || + (m > crypto_pwhash_MEMLIMIT_MAX) ) { + return enif_make_badarg(env); + } + // Check Salt size if(s.size != crypto_pwhash_SALTBYTES) { return nacl_error_tuple(env, "invalid_salt_size"); @@ -1364,6 +1372,14 @@ ERL_NIF_TERM enif_crypto_pwhash_str(ErlNifEnv *env, int argc, ERL_NIF_TERM const return enif_make_badarg(env); } + // Check limits + if( (o < crypto_pwhash_OPSLIMIT_MIN) || + (o > crypto_pwhash_OPSLIMIT_MAX) || + (m < crypto_pwhash_MEMLIMIT_MIN) || + (m > crypto_pwhash_MEMLIMIT_MAX) ) { + return enif_make_badarg(env); + } + // Allocate memory for return binary if( !enif_alloc_binary(crypto_pwhash_STRBYTES, &h) ) { return nacl_error_tuple(env, "alloc_failed");