enacl/c_src/generichash.c
Jesper Louis Andersen 7d8fdf69c0 Protect generichash by a mutex
While sodium is thread-safe, our
resources are not. Furthermore,
we might have an update call going
when someone decides to call
finalize and so on. It is not clever
to do so, but on the other hand
I want to protect against this.

While here, mark the mutexed
calls as dirty CPU. This avoids them
blocking the main scheduler and
only messes with the background
dirty threads, which is somewhat
more safe.

The consequence is that order
access to the resource is now
serialized. I don't think you should
do it, but it is now possible.
2020-01-24 14:48:21 +01:00

314 lines
8.4 KiB
C

#include <sodium.h>
#include <erl_nif.h>
#include "enacl.h"
#include "generichash.h"
typedef struct enacl_generichash_ctx {
ErlNifMutex *mtx;
crypto_generichash_state *ctx; // Underlying hash state from sodium
int alive; // Is the context still valid for updates/finalizes?
int outlen; // Final size of the hash
} enacl_generichash_ctx;
static ErlNifResourceType *enacl_generic_hash_ctx_rtype;
static void enacl_generic_hash_ctx_dtor(ErlNifEnv *env,
enacl_generichash_ctx *);
int enacl_init_generic_hash_ctx(ErlNifEnv *env) {
enacl_generic_hash_ctx_rtype =
enif_open_resource_type(env, NULL, "enacl_generichash_context",
(ErlNifResourceDtor *)enacl_generic_hash_ctx_dtor,
ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, NULL);
if (enacl_generic_hash_ctx_rtype == NULL)
return 0;
return 1;
}
static void enacl_generic_hash_ctx_dtor(ErlNifEnv *env,
enacl_generichash_ctx *obj) {
if (!obj->alive) {
return;
}
if (obj->ctx)
sodium_free(obj->ctx);
if (obj->mtx != NULL)
enif_mutex_destroy(obj->mtx);
return;
}
/*
* Generic hash
*/
ERL_NIF_TERM enacl_crypto_generichash_BYTES(ErlNifEnv *env, int argc,
ERL_NIF_TERM const argv[]) {
return enif_make_int64(env, crypto_generichash_BYTES);
}
ERL_NIF_TERM enacl_crypto_generichash_BYTES_MIN(ErlNifEnv *env, int argc,
ERL_NIF_TERM const argv[]) {
return enif_make_int64(env, crypto_generichash_BYTES_MIN);
}
ERL_NIF_TERM enacl_crypto_generichash_BYTES_MAX(ErlNifEnv *env, int argc,
ERL_NIF_TERM const argv[]) {
return enif_make_int64(env, crypto_generichash_BYTES_MAX);
}
ERL_NIF_TERM enacl_crypto_generichash_KEYBYTES(ErlNifEnv *env, int argc,
ERL_NIF_TERM const argv[]) {
return enif_make_int64(env, crypto_generichash_KEYBYTES);
}
ERL_NIF_TERM enacl_crypto_generichash_KEYBYTES_MIN(ErlNifEnv *env, int argc,
ERL_NIF_TERM const argv[]) {
return enif_make_int64(env, crypto_generichash_KEYBYTES_MIN);
}
ERL_NIF_TERM enacl_crypto_generichash_KEYBYTES_MAX(ErlNifEnv *env, int argc,
ERL_NIF_TERM const argv[]) {
return enif_make_int64(env, crypto_generichash_KEYBYTES_MAX);
}
ERL_NIF_TERM enacl_crypto_generichash(ErlNifEnv *env, int argc,
ERL_NIF_TERM const argv[]) {
ErlNifBinary hash, message, key;
unsigned hash_size;
ERL_NIF_TERM ret;
// Validate the arguments
if (argc != 3)
goto bad_arg;
if (!enif_get_uint(env, argv[0], &hash_size))
goto bad_arg;
if (!enif_inspect_binary(env, argv[1], &message))
goto bad_arg;
if (!enif_inspect_binary(env, argv[2], &key))
goto bad_arg;
// Verify that hash size is
// crypto_generichash_BYTES/crypto_generichash_BYTES_MIN/crypto_generichash_BYTES_MAX
if ((hash_size <= crypto_generichash_BYTES_MIN) ||
(hash_size >= crypto_generichash_BYTES_MAX)) {
ret = enacl_error_tuple(env, "invalid_hash_size");
goto done;
}
// 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) {
ret = enacl_error_tuple(env, "invalid_key_size");
goto done;
}
// allocate memory for hash
if (!enif_alloc_binary(hash_size, &hash)) {
ret = enacl_error_tuple(env, "alloc_failed");
goto done;
}
// calculate hash
if (0 != crypto_generichash(hash.data, hash.size, message.data, message.size,
k, key.size)) {
ret = enacl_error_tuple(env, "hash_error");
goto release;
}
ERL_NIF_TERM ok = enif_make_atom(env, ATOM_OK);
ERL_NIF_TERM ret_hash = enif_make_binary(env, &hash);
ret = enif_make_tuple2(env, ok, ret_hash);
goto done;
bad_arg:
return enif_make_badarg(env);
release:
enif_release_binary(&hash);
done:
return ret;
}
ERL_NIF_TERM enacl_crypto_generichash_init(ErlNifEnv *env, int argc,
ERL_NIF_TERM const argv[]) {
ErlNifBinary key;
unsigned hash_size;
enacl_generichash_ctx *obj = NULL;
ERL_NIF_TERM ret;
// Validate the arguments
if (argc != 2)
goto bad_arg;
if (!enif_get_uint(env, argv[0], &hash_size))
goto bad_arg;
if (!enif_inspect_binary(env, argv[1], &key))
goto bad_arg;
// Verify that hash size is valid
if ((hash_size <= crypto_generichash_BYTES_MIN) ||
(hash_size >= crypto_generichash_BYTES_MAX)) {
ret = enacl_error_tuple(env, "invalid_hash_size");
goto done;
}
// 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) {
ret = enacl_error_tuple(env, "invalid_key_size");
goto done;
}
// Create the resource
if ((obj = enif_alloc_resource(enacl_generic_hash_ctx_rtype,
sizeof(enacl_generichash_ctx))) == NULL) {
goto err;
}
// Allocate the state context via libsodium
// Note that this ensures a 64byte alignment for the resource
// And also protects the resource via guardpages
obj->mtx = NULL;
obj->ctx = NULL;
obj->alive = 0;
obj->outlen = 0;
obj->ctx = (crypto_generichash_state *)sodium_malloc(
crypto_generichash_statebytes());
if (obj->ctx == NULL) {
goto err;
}
obj->alive = 1;
obj->outlen = hash_size;
if ((obj->mtx = enif_mutex_create("enacl.generichash")) == NULL) {
ret = enacl_error_tuple(env, "generichash_mutex_error");
goto err;
}
// Call the library function
if (0 != crypto_generichash_init(obj->ctx, k, key.size, obj->outlen)) {
ret = enacl_error_tuple(env, "hash_init_error");
goto err;
}
ret = enif_make_resource(env, obj);
goto done;
bad_arg:
return enif_make_badarg(env);
err:
ret = enacl_error_tuple(env, "internal_error");
if (obj != NULL) {
if (obj->alive) {
sodium_free(obj->ctx);
obj->alive = 0; // Maintain the invariant consistently
}
}
done:
if (obj != NULL) {
enif_release_resource(obj);
}
return ret;
}
ERL_NIF_TERM enacl_crypto_generichash_update(ErlNifEnv *env, int argc,
ERL_NIF_TERM const argv[]) {
ERL_NIF_TERM ret;
ErlNifBinary data;
unsigned int data_size;
enacl_generichash_ctx *obj = NULL;
// Validate the arguments
if (argc != 2)
goto bad_arg;
if (!enif_get_resource(env, argv[0],
(ErlNifResourceType *)enacl_generic_hash_ctx_rtype,
(void **)&obj))
goto bad_arg;
if (!enif_inspect_binary(env, argv[1], &data))
goto bad_arg;
enif_mutex_lock(obj->mtx);
if (!obj->alive) {
ret = enacl_error_tuple(env, "finalized");
goto done;
}
// Update hash state
if (0 != crypto_generichash_update(obj->ctx, data.data, data.size)) {
ret = enacl_error_tuple(env, "hash_update_error");
goto done;
}
ret = argv[0];
goto done;
bad_arg:
return enif_make_badarg(env);
done:
enif_mutex_unlock(obj->mtx);
return ret;
}
ERL_NIF_TERM enacl_crypto_generichash_final(ErlNifEnv *env, int argc,
ERL_NIF_TERM const argv[]) {
ERL_NIF_TERM ret;
ErlNifBinary hash;
enacl_generichash_ctx *obj = NULL;
if (argc != 1)
goto bad_arg;
if (!enif_get_resource(env, argv[0], enacl_generic_hash_ctx_rtype,
(void **)&obj))
goto bad_arg;
enif_mutex_lock(obj->mtx);
if (!obj->alive) {
ret = enacl_error_tuple(env, "finalized");
goto done;
}
if (!enif_alloc_binary(obj->outlen, &hash)) {
ret = enacl_error_tuple(env, "alloc_failed");
goto done;
}
if (0 != crypto_generichash_final(obj->ctx, hash.data, hash.size)) {
ret = enacl_error_tuple(env, "hash_error");
goto release;
}
// Finalize the object such that it cannot be reused by accident
if (obj->ctx)
sodium_free(obj->ctx);
obj->alive = 0;
ERL_NIF_TERM ok = enif_make_atom(env, ATOM_OK);
ERL_NIF_TERM h = enif_make_binary(env, &hash);
ret = enif_make_tuple2(env, ok, h);
goto done;
bad_arg:
return enif_make_badarg(env);
release:
enif_release_binary(&hash);
done:
enif_mutex_unlock(obj->mtx);
return ret;
}