From ba640b06595d8b7d02fc35c828cc7ed87dd82d28 Mon Sep 17 00:00:00 2001 From: Venkatakumar Srinivasan Date: Tue, 9 May 2017 12:31:20 -0500 Subject: [PATCH] Added generic hash NIF --- c_src/enacl_nif.c | 253 ++++++++++++++++++++++++++++++++++++++++++++-- src/enacl.erl | 52 +++++++++- src/enacl_nif.erl | 30 ++++++ 3 files changed, 327 insertions(+), 8 deletions(-) diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 71ebda2..e2d9149 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -9,6 +9,8 @@ #define ATOM_TRUE "true" #define ATOM_FALSE "false" +#define CRYPTO_GENERICHASH_STATE_RESOURCE "crypto_generichash_state" + #ifdef ERL_NIF_DIRTY_JOB_CPU_BOUND #define erl_nif_dirty_job_cpu_bound_macro(a,b,c) {a,b,c,ERL_NIF_DIRTY_JOB_CPU_BOUND} #else @@ -17,15 +19,24 @@ //{"crypto_box_keypair", 0, enif_crypto_box_keypair, ERL_NIF_DIRTY_JOB_CPU_BOUND} /* Errors */ + +/* This is a global variable for resource type */ +static ErlNifResourceType *generichash_state_type = NULL; + static ERL_NIF_TERM nacl_error_tuple(ErlNifEnv *env, char *error_atom) { - return enif_make_tuple2(env, enif_make_atom(env, "error"), enif_make_atom(env, error_atom)); + return enif_make_tuple2(env, enif_make_atom(env, "error"), enif_make_atom(env, error_atom)); } /* Initialization */ static int enif_crypto_load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) { - return sodium_init(); + // Create a new resource type for crypto_generichash_state + if( !(generichash_state_type = enif_open_resource_type(env, NULL, CRYPTO_GENERICHASH_STATE_RESOURCE, NULL, ERL_NIF_RT_CREATE, NULL)) ) { + return -1; + } + + return sodium_init(); } /* Low-level functions (Hashing, String Equality, ...) */ @@ -293,7 +304,9 @@ ERL_NIF_TERM enif_crypto_box(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[] return nacl_error_tuple(env, "alloc_failed"); } - crypto_box(result.data, padded_msg.data, padded_msg.size, nonce.data, pk.data, sk.data); + if( 0 != crypto_box(result.data, padded_msg.data, padded_msg.size, nonce.data, pk.data, sk.data) ) { + return nacl_error_tuple(env, "box_error"); + } return enif_make_sub_binary( env, @@ -358,7 +371,10 @@ ERL_NIF_TERM enif_crypto_box_beforenm(ErlNifEnv *env, int argc, ERL_NIF_TERM con return nacl_error_tuple(env, "alloc_failed"); } - crypto_box_beforenm(k.data, pk.data, sk.data); + if( 0 != crypto_box_beforenm(k.data, pk.data, sk.data) ) { + // error + return nacl_error_tuple(env, "error_gen_shared_secret"); + } return enif_make_binary(env, &k); } @@ -1101,7 +1117,10 @@ ERL_NIF_TERM enif_crypto_kx_server_session_keys(ErlNifEnv *env, int argc, ERL_NI return nacl_error_tuple(env, "alloc_failed"); } - crypto_kx_server_session_keys(rx.data, tx.data, server_pk.data, server_sk.data, client_pk.data); + if( 0 != crypto_kx_server_session_keys(rx.data, tx.data, server_pk.data, server_sk.data, client_pk.data) ) { + // suspicious client public key + return nacl_error_tuple(env, "invalid_client_public_key"); + } return enif_make_tuple2(env, enif_make_binary(env, &rx), enif_make_binary(env, &tx)); } @@ -1129,7 +1148,10 @@ ERL_NIF_TERM enif_crypto_kx_client_session_keys(ErlNifEnv *env, int argc, ERL_NI return nacl_error_tuple(env, "alloc_failed"); } - crypto_kx_client_session_keys(rx.data, tx.data, client_pk.data, client_sk.data, server_pk.data); + if( 0 != crypto_kx_client_session_keys(rx.data, tx.data, client_pk.data, client_sk.data, server_pk.data) ) { + // suspicious server public key + return nacl_error_tuple(env, "invalid_server_public_key"); + } return enif_make_tuple2(env, enif_make_binary(env, &rx), enif_make_binary(env, &tx)); } @@ -1291,6 +1313,211 @@ ERL_NIF_TERM enif_crypto_pwhash_str_verify(ErlNifEnv *env, int argc, ERL_NIF_TER return retVal; } +/* + * Generic hash + */ +static +ERL_NIF_TERM enif_crypto_generichash_BYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_generichash_BYTES); +} + +static +ERL_NIF_TERM enif_crypto_generichash_BYTES_MIN(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_generichash_BYTES_MIN); +} + +static +ERL_NIF_TERM enif_crypto_generichash_BYTES_MAX(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_generichash_BYTES_MAX); +} + +static +ERL_NIF_TERM enif_crypto_generichash_KEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_generichash_KEYBYTES); +} + +static +ERL_NIF_TERM enif_crypto_generichash_KEYBYTES_MIN(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_generichash_KEYBYTES_MIN); +} + +static +ERL_NIF_TERM enif_crypto_generichash_KEYBYTES_MAX(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_generichash_KEYBYTES_MAX); +} + +static +ERL_NIF_TERM enif_crypto_generichash(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary hash, message, key; + + size_t hashSize; + + // Validate the arguments + if( (argc != 3) || + (!enif_get_uint64(env, argv[0], &hashSize)) || + (!enif_inspect_binary(env, argv[1], &message)) || + (!enif_inspect_binary(env, argv[2], &key)) ) { + return enif_make_badarg(env); + } + + // Verify that hash size is crypto_generichash_BYTES/crypto_generichash_BYTES_MIN/crypto_generichash_BYTES_MAX + if( (hashSize < crypto_generichash_BYTES_MIN) || + (hashSize > crypto_generichash_BYTES_MAX) ) { + return nacl_error_tuple(env, "invalid_hash_size"); + } + + // validate key size + unsigned char *k = key.data; + if( 0 == key.size ) { + k = NULL; + } else if( key.size < crypto_generichash_KEYBYTES_MIN || key.size > crypto_generichash_KEYBYTES_MAX ) { + return nacl_error_tuple(env, "invalid_key_size"); + } + + // allocate memory for hash + if( !enif_alloc_binary(hashSize, &hash) ) { + return nacl_error_tuple(env, "alloc_failed"); + } + + // calculate hash + if( 0 != crypto_generichash(hash.data, hash.size, message.data, message.size, k, key.size) ) { + enif_release_binary(&hash); + return nacl_error_tuple(env, "hash_error"); + } + + ERL_NIF_TERM ok = enif_make_atom(env, ATOM_OK); + ERL_NIF_TERM ret = enif_make_binary(env, &hash); + + return enif_make_tuple2(env, ok, ret); +} + +static +ERL_NIF_TERM enif_crypto_generichash_init(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary key; + + size_t hashSize; + + // Validate the arguments + if( (argc != 2) || + (!enif_get_uint64(env, argv[0], &hashSize)) || + (!enif_inspect_binary(env, argv[1], &key)) ) { + return enif_make_badarg(env); + } + + // Verify that hash size is crypto_generichash_BYTES/crypto_generichash_BYTES_MIN/crypto_generichash_BYTES_MAX + if( (hashSize < crypto_generichash_BYTES_MIN) || + (hashSize > crypto_generichash_BYTES_MAX) ) { + return nacl_error_tuple(env, "invalid_hash_size"); + } + + // validate key size + unsigned char *k = key.data; + if( 0 == key.size ) { + k = NULL; + } else if( key.size < crypto_generichash_KEYBYTES_MIN || key.size > crypto_generichash_KEYBYTES_MAX ) { + return nacl_error_tuple(env, "invalid_key_size"); + } + + // Create a resource for hash state + crypto_generichash_state *state = (crypto_generichash_state *)enif_alloc_resource(generichash_state_type, crypto_generichash_statebytes()); + if( !state ) { + return nacl_error_tuple(env, "alloc_failed"); + } + + // Call the library function + if( 0 != crypto_generichash_init(state, k, key.size, hashSize) ) { + return nacl_error_tuple(env, "hash_init_error"); + } + + + // Create return values + ERL_NIF_TERM e1 = enif_make_atom(env, "hashstate"); + ERL_NIF_TERM e2 = argv[0]; + ERL_NIF_TERM e3 = enif_make_resource(env, state); + + + // release dynamically allocated memory to erlang to mange + enif_release_resource(state); + + // return a tuple + return enif_make_tuple3(env, e1, e2, e3); +} + +static +ERL_NIF_TERM enif_crypto_generichash_update(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary message; + + size_t hashSize; + + crypto_generichash_state *state; + + // Validate the arguments + if( (argc != 3) || + (!enif_get_uint64(env, argv[0], &hashSize)) || + (!enif_get_resource(env, argv[1], generichash_state_type, (void **)&state)) || + (!enif_inspect_binary(env, argv[2], &message)) ) { + return enif_make_badarg(env); + } + + // Verify that hash size is crypto_generichash_BYTES/crypto_generichash_BYTES_MIN/crypto_generichash_BYTES_MAX + if( (hashSize < crypto_generichash_BYTES_MIN) || + (hashSize > crypto_generichash_BYTES_MAX) ) { + return nacl_error_tuple(env, "invalid_hash_size"); + } + + // Update hash state + if( 0 != crypto_generichash_update(state, message.data, message.size) ) { + return nacl_error_tuple(env, "hash_update_error"); + } + + + // Generate return value + ERL_NIF_TERM e1 = enif_make_atom(env, "hashstate"); + ERL_NIF_TERM e2 = argv[0]; + ERL_NIF_TERM e3 = enif_make_resource(env, state); + + // return a tuple + return enif_make_tuple3(env, e1, e2, e3); +} + +static +ERL_NIF_TERM enif_crypto_generichash_final(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary hash; + + size_t hashSize; + + crypto_generichash_state *state; + + // Validate the arguments + if( (argc != 2) || + (!enif_get_uint64(env, argv[0], &hashSize)) || + (!enif_get_resource(env, argv[1], generichash_state_type, (void **)&state)) ) { + return enif_make_badarg(env); + } + + // Verify that hash size is crypto_generichash_BYTES/crypto_generichash_BYTES_MIN/crypto_generichash_BYTES_MAX + if( (hashSize < crypto_generichash_BYTES_MIN) || + (hashSize > crypto_generichash_BYTES_MAX) ) { + return nacl_error_tuple(env, "invalid_hash_size"); + } + + // allocate memory for hash + if( !enif_alloc_binary(hashSize, &hash) ) { + return nacl_error_tuple(env, "alloc_failed"); + } + + // calculate hash + if( 0 != crypto_generichash_final(state, hash.data, hash.size) ) { + enif_release_binary(&hash); + return nacl_error_tuple(env, "hash_error"); + } + + ERL_NIF_TERM ok = enif_make_atom(env, ATOM_OK); + ERL_NIF_TERM ret = enif_make_binary(env, &hash); + + return enif_make_tuple2(env, ok, ret); +} + /* Tie the knot to the Erlang world */ static ErlNifFunc nif_funcs[] = { {"crypto_box_NONCEBYTES", 0, enif_crypto_box_NONCEBYTES}, @@ -1395,7 +1622,19 @@ static ErlNifFunc nif_funcs[] = { {"crypto_kx_SECRETKEYBYTES", 0, enif_crypto_kx_SECRETKEYBYTES}, {"crypto_kx_SESSIONKEYBYTES", 0, enif_crypto_kx_SESSIONKEYBYTES}, - {"scramble_block_16", 2, enif_scramble_block_16} + {"scramble_block_16", 2, enif_scramble_block_16}, + + {"crypto_generichash_BYTES", 0, enif_crypto_generichash_BYTES}, + {"crypto_generichash_BYTES_MIN", 0, enif_crypto_generichash_BYTES_MIN}, + {"crypto_generichash_BYTES_MAX", 0, enif_crypto_generichash_BYTES_MAX}, + {"crypto_generichash_KEYBYTES", 0, enif_crypto_generichash_KEYBYTES}, + {"crypto_generichash_KEYBYTES_MIN", 0, enif_crypto_generichash_KEYBYTES_MIN}, + {"crypto_generichash_KEYBYTES_MAX", 0, enif_crypto_generichash_KEYBYTES_MAX}, + {"crypto_generichash", 3, enif_crypto_generichash}, + {"crypto_generichash_init", 2, enif_crypto_generichash_init}, + {"crypto_generichash_update", 3, enif_crypto_generichash_update}, + {"crypto_generichash_final", 2, enif_crypto_generichash_final} + }; ERL_NIF_INIT(enacl_nif, nif_funcs, enif_crypto_load, NULL, NULL, NULL); diff --git a/src/enacl.erl b/src/enacl.erl index eb45248..b19ae55 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -113,6 +113,16 @@ pwhash_str_verify/2 ]). +%% Generic hash functions +-export([ + generichash/3, + generichash/2, + + generichash_init/2, + generichash_update/2, + generichash_final/1 +]). + %% Libsodium specific functions (which are also part of the "undocumented" interface to NaCl -export([ randombytes/1 @@ -160,6 +170,13 @@ -define(CRYPTO_KX_SECRETKEYBYTES, 32). -define(CRYPTO_KX_SESSIONKEYBYTES, 32). +-define(CRYPTO_GENERICHASH_BYTES_MIN, 16). +-define(CRYPTO_GENERICHASH_BYTES_MAX, 64). +-define(CRYPTO_GENERICHASH_BYTES, 32). +-define(CRYPTO_GENERICHASH_KEYBYTES_MIN, 16). +-define(CRYPTO_GENERICHASH_KEYBYTES_MAX, 64). +-define(CRYPTO_GENERICHASH_KEYBYTES, 32). + %% @doc Verify makes sure the constants defined in libsodium matches ours verify() -> true = equals(binary:copy(<<0>>, enacl_nif:crypto_box_ZEROBYTES()), ?P_ZEROBYTES), @@ -180,7 +197,13 @@ verify() -> {crypto_secretbox_BOXZEROBYTES, ?CRYPTO_SECRETBOX_BOXZEROBYTES}, {crypto_kx_SESSIONKEYBYTES, ?CRYPTO_KX_SESSIONKEYBYTES}, {crypto_kx_PUBLICKEYBYTES, ?CRYPTO_KX_PUBLICKEYBYTES}, - {crypto_kx_SECRETKEYBYTES, ?CRYPTO_KX_SECRETKEYBYTES} + {crypto_kx_SECRETKEYBYTES, ?CRYPTO_KX_SECRETKEYBYTES}, + {crypto_generichash_BYTES, ?CRYPTO_GENERICHASH_BYTES}, + {crypto_generichash_BYTES_MIN, ?CRYPTO_GENERICHASH_BYTES_MIN}, + {crypto_generichash_BYTES_MAX, ?CRYPTO_GENERICHASH_BYTES_MAX}, + {crypto_generichash_KEYBYTES, ?CRYPTO_GENERICHASH_KEYBYTES}, + {crypto_generichash_KEYBYTES_MIN, ?CRYPTO_GENERICHASH_KEYBYTES_MIN}, + {crypto_generichash_KEYBYTES_MAX, ?CRYPTO_GENERICHASH_KEYBYTES_MAX} ], run_verifiers(Verifiers). @@ -257,6 +280,33 @@ unsafe_memzero(_) -> error(badarg). +%% @doc generichash/3 creates a hash of the message using a key. +%% +%% This function generates a hash of the message using a key. The hash size is +%% either 16, 32 or 64 bytes +%% @end +-spec generichash(iodata(), binary()) -> {ok, binary()} | {error, term()}. +generichash(HashSize, Message, Key) -> + enacl_nif:crypto_generichash(HashSize, Message, Key). + +%% @doc generichash/2 creates a hash of the message. +%% +%% This function generates a hash of the message. The hash size is +%% either 16, 32 or 64 bytes +%% @end +generichash(HashSize, Message) -> + enacl_nif:crypto_generichash(HashSize, Message, <<>>). + +generichash_init(HashSize, Key) -> + enacl_nif:crypto_generichash_init(HashSize, Key). + +generichash_update({hashstate, HashSize, HashState}, Message) -> + enacl_nif:crypto_generichash_update(HashSize, HashState, Message). + +generichash_final({hashstate, HashSize, HashState}) -> + enacl_nif:crypto_generichash_final(HashSize, HashState). + + %% @doc pwhash/2 hash a password %% %% This function generates a fixed size salted hash of a user defined password. diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index be82df6..44196ba 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -129,6 +129,21 @@ crypto_pwhash_str_verify/2 ]). +%% Generic hash +-export([ + crypto_generichash_BYTES/0, + crypto_generichash_BYTES_MIN/0, + crypto_generichash_BYTES_MAX/0, + crypto_generichash_KEYBYTES/0, + crypto_generichash_KEYBYTES_MIN/0, + crypto_generichash_KEYBYTES_MAX/0, + + crypto_generichash/3, + crypto_generichash_init/2, + crypto_generichash_update/3, + crypto_generichash_final/2 +]). + %% Access to the RNG -export([ randombytes/1 @@ -153,6 +168,21 @@ init() -> SoName = filename:join(Dir, atom_to_list(?MODULE)), erlang:load_nif(SoName, 0). +crypto_generichash_BYTES() -> erlang:nif_error(nif_not_loaded). +crypto_generichash_BYTES_MIN() -> erlang:nif_error(nif_not_loaded). +crypto_generichash_BYTES_MAX() -> erlang:nif_error(nif_not_loaded). +crypto_generichash_KEYBYTES() -> erlang:nif_error(nif_not_loaded). +crypto_generichash_KEYBYTES_MIN() -> erlang:nif_error(nif_not_loaded). +crypto_generichash_KEYBYTES_MAX() -> erlang:nif_error(nif_not_loaded). + +crypto_generichash(_HashSize, _Message, _Key) -> erlang:nif_error(nif_not_loaded). + +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_str(_Password) -> erlang:nif_error(nif_not_loaded). crypto_pwhash_str_verify(_HashedPassword, _Password) -> erlang:nif_error(nif_not_loaded).