commit 2982515f2ed87ab5bf92de2809e50e136f456932 Author: Aydin Mercan Date: Wed May 31 14:31:03 2023 +0300 initial commit diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc8d555 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +saltywitch-*.tar + +# Temporary files, for example, from tests. +/tmp/ + +*.o diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1e63dd0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.26) + +project(saltywitch) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE) +endif() + +set(CMAKE_C_STANDARD 99) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_LINK_WHAT_YOU_USE ON) + +if(CMAKE_C_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") + set(CMAKE_C_FLAGS "-O3 -shared ${CMAKE_C_FLAGS}") +endif() + +check_ipo_supported(RESULT has_ipo OUTPUT error) +if(has_ipo) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) +endif() + +set(SALTYWITCH_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/c_src/saltywitch.c + ${CMAKE_CURRENT_SOURCE_DIR}/c_src/atoms.c + ${CMAKE_CURRENT_SOURCE_DIR}/c_src/auth.c + ${CMAKE_CURRENT_SOURCE_DIR}/c_src/generichash.c + ${CMAKE_CURRENT_SOURCE_DIR}/c_src/pwhash.c + ${CMAKE_CURRENT_SOURCE_DIR}/c_src/saltywitch.c + ${CMAKE_CURRENT_SOURCE_DIR}/c_src/saltywitch.h + ${CMAKE_CURRENT_SOURCE_DIR}/c_src/secretbox.c + ${CMAKE_CURRENT_SOURCE_DIR}/c_src/shorthash.c +) + +set(INCLUDE_DIRS + ${ERTS_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/c_src +) + +find_package(libsodium REQUIRED) + +add_library(saltywitch SHARED ${SALTYWITCH_SOURCES}) + +target_compile_definitions(saltywitch PUBLIC "$<$:NDEBUG>") +target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIRS}) + +target_link_libraries(saltywitch PRIVATE libsodium) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..00a65d2 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +.POSIX: + +build: + busybox sh ./build.sh + +clean: + sh ./clean.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..cbe9bf5 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# SaltyWitch + +Another NIF library for libsodium. + +## Roadmap + +Easy cross-compiling the NIF. + +## Building + +On UNIX-like systems: +- Shell (tested on bash and busybox ash) +- Make +- A GCC-like C99 compiler. +- `meson` _(optional but **recommended**)_ +- `pkgconf` *(if directly building)* + +`SALTYWITCH_BUILD_SYSTEM`: By default `direct` is used for UNIX systems. + - **meson** _(recommended)_: Use meson to build the NIF. + - **cmake**: Use CMake to build the NIF + - **direct**: Build the NIF in a single compiler invocation like a lunatic. + +`SALTYWITCH_MESON_ARGS`: extra arguments for meson + - do not specify `--prefix` + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `saltywitch` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:saltywitch, "~> 0.0.1"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . diff --git a/STYLE.md b/STYLE.md new file mode 100644 index 0000000..a4bf473 --- /dev/null +++ b/STYLE.md @@ -0,0 +1,17 @@ +# C Style used + + +- Macros are to be avoided. +- Any macro defined must be defined before its used and `undef`'d after done. + +Exception part must be as following at bare minimum: + + +If there are more than 1 points of exception, they all must be deduplicated through `goto`'s. +The variables `err` and `reason` must be used with the following initial values. + +```c +ERL_NIF_TERM err = atom_badarg; +ERL_NIF_TERM reason = atom_err_opaque; +``` + diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..2acf5fd --- /dev/null +++ b/build.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env sh + +if [ -z $SALTYWITCH_BUILD_SYSTEM ]; then + printf "[build.sh] trying to detect better build systems... " + +# if [ -x "$(command -v cmake)" ]; then +# printf "cmake " +# SALTYWITCH_BUILD_SYSTEM="cmake" +# fi + + if [ -x "$(command -v meson)" ]; then + printf "meson " + SALTYWITCH_BUILD_SYSTEM="meson" + fi + + printf "\n" +fi + +case ${SALTYWITCH_BUILD_SYSTEM:-direct} in + meson) + echo "[build.sh] using meson to build nif" + if ! [ -d "./build-meson" ]; then + meson setup build-meson \ + --prefix=$MIX_APP_PATH \ + -Dmix_target=$MIX_TARGET \ + -Derts_include_dir=$ERTS_INCLUDE_DIR \ + -Derl_interface_lib_dir=$ERL_INTERFACE_LIB_DIR \ + -Derl_interface_include_dir=$ERL_INTERFACE_INCLUDE_DIR \ + $SALTYWITCH_MESON_ARGS + fi + + meson compile -C build-meson + meson install -C build-meson + ;; + +# cmake) +# echo "[build.sh] using cmake to build nif" +# ;; + + direct) + echo "[build.sh] directly building the nif" + ${CC:-cc} c_src/*.c \ + -shared \ + ${CFLAGS:--O3 -fPIC -fstack-protector -fvisibility=hidden} \ + $LDFLAGS \ + -I $ERTS_INCLUDE_DIR \ + -I $ERL_EI_INCLUDE_DIR \ + -I $ERL_INTERFACE_INCLUDE_DIR \ + -L $ERL_EI_LIBDIR \ + -lei \ + $(pkgconf --libs --cflags libsodium) \ + -o $MIX_APP_PATH/priv/saltywitch.so + ;; + + *) + echo "[build.sh] invalid build backend" + exit 1 + ;; +esac diff --git a/c_src/atoms.c b/c_src/atoms.c new file mode 100644 index 0000000..d5e0ead --- /dev/null +++ b/c_src/atoms.c @@ -0,0 +1,66 @@ +#include "saltywitch.h" + +ERL_NIF_TERM atom_badarg; +ERL_NIF_TERM atom_error; +ERL_NIF_TERM atom_ok; + +ERL_NIF_TERM atom_err_opaque; + +ERL_NIF_TERM atom_err_invalid_type; +ERL_NIF_TERM atom_err_nif_alloc; + +ERL_NIF_TERM atom_err_invalid_key_size; +ERL_NIF_TERM atom_err_invalid_nonce_size; +ERL_NIF_TERM atom_err_invalid_salt_size; +ERL_NIF_TERM atom_err_invalid_seed_size; +ERL_NIF_TERM atom_err_invalid_tag_size; +ERL_NIF_TERM atom_err_verification_failed; + +ERL_NIF_TERM atom_err_ciphertext_too_small; +ERL_NIF_TERM atom_err_key_too_large; +ERL_NIF_TERM atom_err_key_too_small; +ERL_NIF_TERM atom_err_output_too_large; +ERL_NIF_TERM atom_err_output_too_small; + +ERL_NIF_TERM atom_err_pwhash_too_long; +ERL_NIF_TERM atom_err_pwhash_needs_rehash; +ERL_NIF_TERM atom_err_pwhash_mem_too_large; +ERL_NIF_TERM atom_err_pwhash_mem_too_small; +ERL_NIF_TERM atom_err_pwhash_ops_too_large; +ERL_NIF_TERM atom_err_pwhash_ops_too_small; + +#define DEFERROR(name) atom_err_##name = enif_make_atom(env, #name) + +void saltywitch_nif_init_atoms(ErlNifEnv *env) +{ + atom_error = enif_make_atom(env, "error"); + atom_badarg = enif_make_atom(env, "badarg"); + atom_ok = enif_make_atom(env, "ok"); + + DEFERROR(opaque); + + DEFERROR(invalid_type); + DEFERROR(nif_alloc); + + DEFERROR(invalid_key_size); + DEFERROR(invalid_nonce_size); + DEFERROR(invalid_salt_size); + DEFERROR(invalid_seed_size); + DEFERROR(invalid_tag_size); + DEFERROR(verification_failed); + + DEFERROR(ciphertext_too_small); + DEFERROR(key_too_large); + DEFERROR(key_too_small); + DEFERROR(output_too_large); + DEFERROR(output_too_small); + + DEFERROR(pwhash_too_long); + DEFERROR(pwhash_needs_rehash); + DEFERROR(pwhash_mem_too_large); + DEFERROR(pwhash_mem_too_small); + DEFERROR(pwhash_ops_too_large); + DEFERROR(pwhash_ops_too_small); +} + +#undef DEFERROR diff --git a/c_src/auth.c b/c_src/auth.c new file mode 100644 index 0000000..9c17d5e --- /dev/null +++ b/c_src/auth.c @@ -0,0 +1,102 @@ +#include "saltywitch.h" + +/* auth */ +ERL_NIF_TERM saltywitch_auth_keygen(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM term; + + unsigned char *key = enif_make_new_binary(env, crypto_auth_KEYBYTES, &term); + if (key == NULL) { + return saltywitch_exception(env, atom_error, atom_err_nif_alloc); + } + + crypto_auth_keygen(key); + + return term; +} + +ERL_NIF_TERM saltywitch_auth(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary message, key; + ERL_NIF_TERM term; + + unsigned char *mac; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + if (enif_inspect_binary(env, argv[0], &message) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[1], &key) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (key.size != crypto_auth_KEYBYTES) { + reason = atom_err_invalid_key_size; + goto badarg_FAULT; + } + + mac = enif_make_new_binary(env, crypto_auth_BYTES, &term); + if (mac == NULL) { + reason = atom_err_nif_alloc; + goto error_FAULT; + } + + crypto_auth(mac, message.data, message.size, key.data); + + return term; + +error_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} + +ERL_NIF_TERM saltywitch_auth_verify(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary tag, message, key; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + if (enif_inspect_binary(env, argv[0], &tag) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (tag.size != crypto_auth_BYTES) { + reason = atom_err_invalid_tag_size; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[1], &message) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[2], &key) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (key.size != crypto_auth_KEYBYTES) { + reason = atom_err_invalid_key_size; + goto badarg_FAULT; + } + + if (crypto_auth_verify(tag.data, message.data, message.size, key.data) != 0) { + reason = atom_err_verification_failed; + goto error_FAULT; + } + + return atom_ok; + +error_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} diff --git a/c_src/generichash.c b/c_src/generichash.c new file mode 100644 index 0000000..0baaea1 --- /dev/null +++ b/c_src/generichash.c @@ -0,0 +1,262 @@ +#include "saltywitch.h" + +struct saltywitch_generichash_context { + uint64_t outlen; + ErlNifMutex *mutex; + crypto_generichash_state *state; +}; + +static ErlNifResourceType *_generichash_context_rtype = NULL; + +static void _generichash_dtor(ErlNifEnv *env, void *context) +{ + struct saltywitch_generichash_context *ctx = context; + + if (ctx == NULL) { + return; + } + + if (ctx->state != NULL) { + sodium_free(ctx->state); + ctx->state = NULL; + } + + if (ctx->mutex != NULL) { + enif_mutex_destroy(ctx->mutex); + ctx->mutex = NULL; + } +} + +int saltywitch_nif_init_generichash(ErlNifEnv *env) +{ + _generichash_context_rtype = enif_open_resource_type(env, + NULL, + "generichash_context", + _generichash_dtor, + ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, + NULL); + + if (_generichash_context_rtype == NULL) { + return -1; + } + + return 0; +} + +ERL_NIF_TERM saltywitch_generichash_keygen(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM term; + + unsigned char *key = enif_make_new_binary(env, crypto_generichash_KEYBYTES, &term); + if (key == NULL) { + return saltywitch_exception(env, atom_error, atom_err_nif_alloc); + } + + crypto_generichash_keygen(key); + + return term; +} + +ERL_NIF_TERM saltywitch_generichash(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + unsigned char *hash = NULL; + ERL_NIF_TERM term; + ErlNifBinary bin; + + ErlNifBinary key = { + .size = 0, + .data = NULL, + }; + + uint64_t len = crypto_generichash_BYTES; + + if (enif_inspect_binary(env, argv[0], &bin) == false) { + return enif_make_badarg(env); + } + + switch (argc) { + case 3: + if (enif_get_uint64(env, argv[2], &len) == false) { + return enif_make_badarg(env); + } + /* fallthrough */ + case 2: + if (enif_inspect_binary(env, argv[1], &key) == false) { + return enif_make_badarg(env); + } + } + + hash = enif_make_new_binary(env, len, &term); + if (hash == NULL) { + return enif_raise_exception(env, enif_make_atom(env, "opaque")); + } + + crypto_generichash(hash, len, bin.data, bin.size, key.data, key.size); + + return term; +} + +ERL_NIF_TERM saltywitch_generichash_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + struct saltywitch_generichash_context *ctx; + ERL_NIF_TERM term; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + ErlNifBinary key = { + .size = 0, + .data = NULL, + }; + + uint64_t len = crypto_generichash_BYTES; + + switch (argc) { + case 2: + if (enif_get_uint64(env, argv[1], &len) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (len < crypto_generichash_BYTES_MIN) { + reason = atom_err_output_too_small; + goto badarg_FAULT; + } + + if (len > crypto_generichash_BYTES_MAX) { + reason = atom_err_output_too_large; + goto badarg_FAULT; + } + /* fallthrough */ + case 1: + if (enif_inspect_binary(env, argv[0], &key) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (key.size < crypto_generichash_KEYBYTES_MIN) { + reason = atom_err_key_too_small; + goto badarg_FAULT; + } + + if (key.size > crypto_generichash_KEYBYTES_MAX) { + reason = atom_err_key_too_large; + goto badarg_FAULT; + } + } + + ctx = enif_alloc_resource(_generichash_context_rtype, sizeof(struct saltywitch_generichash_context)); + if (ctx == NULL) { + reason = atom_err_nif_alloc; + goto resource_alloc_FAULT; + } + + ctx->outlen = len; + + ctx->mutex = enif_mutex_create("saltywitch.generichash"); + if (ctx->mutex == NULL) { + reason = atom_err_nif_alloc; + goto mutex_FAULT; + } + + ctx->state = sodium_malloc(sizeof(crypto_generichash_state)); + if (ctx->state == NULL) { + goto state_alloc_FAULT; + } + + crypto_generichash_init(ctx->state, key.data, key.size, len); + + term = enif_make_resource(env, ctx); + + enif_release_resource(ctx); + + return term; + +state_alloc_FAULT: + enif_mutex_destroy(ctx->mutex); +mutex_FAULT: + enif_release_resource(ctx); +resource_alloc_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} + +ERL_NIF_TERM saltywitch_generichash_update(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + struct saltywitch_generichash_context *ctx = NULL; + ErlNifBinary message; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + if (enif_get_resource(env, argv[0], _generichash_context_rtype, (void **) &ctx) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (ctx == NULL) { + goto badarg_FAULT; + } + + if (ctx->mutex == NULL || ctx->state == NULL) { + goto badarg_FAULT; + } + + if (enif_inspect_iolist_as_binary(env, argv[1], &message) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + enif_mutex_lock(ctx->mutex); + crypto_generichash_update(ctx->state, message.data, message.size); + enif_mutex_unlock(ctx->mutex); + + return atom_ok; + +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} + +ERL_NIF_TERM saltywitch_generichash_final(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + struct saltywitch_generichash_context *ctx = NULL; + unsigned char *hash; + ERL_NIF_TERM term; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + if (enif_get_resource(env, argv[0], _generichash_context_rtype, (void **) &ctx) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (ctx == NULL) { + goto badarg_FAULT; + } + + if (ctx->mutex == NULL || ctx->state == NULL) { + goto badarg_FAULT; + } + + hash = enif_make_new_binary(env, ctx->outlen, &term); + if (hash == NULL) { + reason = atom_err_nif_alloc; + goto error_FAULT; + } + + enif_mutex_lock(ctx->mutex); + crypto_generichash_final(ctx->state, hash, ctx->outlen); + sodium_free(ctx->state); + ctx->state = NULL; + ctx->outlen = 0; + enif_mutex_unlock(ctx->mutex); + + return term; + +error_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} diff --git a/c_src/kx.c b/c_src/kx.c new file mode 100644 index 0000000..8948669 --- /dev/null +++ b/c_src/kx.c @@ -0,0 +1 @@ +#include "saltywitch.h" diff --git a/c_src/pwhash.c b/c_src/pwhash.c new file mode 100644 index 0000000..9ecede3 --- /dev/null +++ b/c_src/pwhash.c @@ -0,0 +1,199 @@ +#include "saltywitch.h" + +ERL_NIF_TERM saltywitch_pwhash(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary pass, salt; + ERL_NIF_TERM term; + + uint64_t len, ops, mem; + unsigned char *out; + int alg; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + if (enif_get_uint64(env, argv[0], &len) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (enif_inspect_iolist_as_binary(env, argv[1], &pass) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[2], &salt) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (salt.size != crypto_pwhash_SALTBYTES) { + reason = atom_err_invalid_salt_size; + goto badarg_FAULT; + } + + if (enif_get_uint64(env, argv[3], &ops) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (ops > crypto_pwhash_OPSLIMIT_MAX) { + reason = atom_err_pwhash_ops_too_large; + goto badarg_FAULT; + } else if (ops < crypto_pwhash_OPSLIMIT_MIN) { + reason = atom_err_pwhash_ops_too_small; + goto badarg_FAULT; + } + + if (enif_get_uint64(env, argv[4], &mem) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (mem > crypto_pwhash_OPSLIMIT_MAX) { + reason = atom_err_pwhash_mem_too_large; + goto badarg_FAULT; + } else if (mem < crypto_pwhash_OPSLIMIT_MIN) { + reason = atom_err_pwhash_mem_too_small; + goto badarg_FAULT; + } + + if (enif_get_int(env, argv[5], &alg) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + out = enif_make_new_binary(env, len, &term); + if (out == NULL) { + reason = atom_err_nif_alloc; + goto error_FAULT; + } + + if (crypto_pwhash(out, len, (char *) pass.data, pass.size, salt.data, ops, mem, alg) != 0) { + goto error_FAULT; + } + + return term; + +error_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} + +ERL_NIF_TERM saltywitch_pwhash_str(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + char out[crypto_pwhash_STRBYTES]; + uint64_t ops, mem; + ErlNifBinary pass; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + (void) argc; + + if (enif_inspect_iolist_as_binary(env, argv[0], &pass) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (enif_get_uint64(env, argv[1], &ops) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (enif_get_uint64(env, argv[2], &mem) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (crypto_pwhash_str(out, (char *) pass.data, pass.size, ops, mem) != 0) { + goto error_FAULT; + } + + return enif_make_string(env, out, ERL_NIF_LATIN1); + +error_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} + +#include +ERL_NIF_TERM saltywitch_pwhash_str_verify(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary hash, pass; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + (void) argc; + + if (enif_inspect_iolist_as_binary(env, argv[0], &hash) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (hash.size > crypto_pwhash_STRBYTES) { + reason = atom_err_pwhash_too_long; + goto badarg_FAULT; + } + + if (enif_inspect_iolist_as_binary(env, argv[1], &pass) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (crypto_pwhash_str_verify((char *) hash.data, (char *) pass.data, pass.size) != 0) { + reason = atom_err_verification_failed; + goto error_FAULT; + } + + return atom_ok; + +error_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} + +ERL_NIF_TERM saltywitch_pwhash_str_needs_rehash(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary hash; + uint64_t ops, mem; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + if (enif_inspect_binary(env, argv[0], &hash) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (hash.size > crypto_pwhash_STRBYTES) { + reason = atom_err_pwhash_too_long; + goto badarg_FAULT; + } + + if (enif_get_uint64(env, argv[1], &ops) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (enif_get_uint64(env, argv[2], &mem) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + switch (crypto_pwhash_str_needs_rehash((char *) hash.data, ops, mem)) { + case 0: + return atom_ok; + case 1: + reason = atom_err_pwhash_needs_rehash; + } + + err = atom_error; + +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} diff --git a/c_src/random.c b/c_src/random.c new file mode 100644 index 0000000..e4b5035 --- /dev/null +++ b/c_src/random.c @@ -0,0 +1,97 @@ +#include "saltywitch.h" + +_Static_assert(sizeof(unsigned int) >= sizeof(uint32_t), "unsigned int must be greated than 32 bits"); + +ERL_NIF_TERM saltywitch_randombytes_random(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + (void) argc; + (void) argv; + + return enif_make_uint(env, randombytes_random()); +} + +ERL_NIF_TERM saltywitch_randombytes_uniform(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + uint32_t upper; + + (void) argc; + + if (enif_get_uint(env, argv[0], &upper) == false) { + return saltywitch_exception(env, atom_badarg, atom_err_invalid_type); + } + + return enif_make_uint(env, randombytes_uniform(upper)); +} + +ERL_NIF_TERM saltywitch_randombytes_buf(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + unsigned char *buf; + ERL_NIF_TERM term; + uint64_t len; + + (void) argc; + + if (enif_get_uint64(env, argv[0], &len) == false) { + return saltywitch_exception(env, atom_badarg, atom_err_invalid_type); + } + + buf = enif_make_new_binary(env, len, &term); + if (buf == NULL) { + return saltywitch_exception(env, atom_error, atom_err_nif_alloc); + } + + randombytes_buf(buf, len); + + return term; +} + +ERL_NIF_TERM saltywitch_randombytes_buf_deterministic(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + unsigned char *buf; + ErlNifBinary seed; + ERL_NIF_TERM term; + uint64_t len; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + (void) argc; + + if (enif_get_uint64(env, argv[0], &len) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[1], &seed) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (seed.size != randombytes_SEEDBYTES) { + reason = atom_err_invalid_seed_size; + goto badarg_FAULT; + } + + buf = enif_make_new_binary(env, len, &term); + if (buf == NULL) { + reason = atom_err_nif_alloc; + goto error_FAULT; + } + + randombytes_buf_deterministic(buf, len, seed.data); + + return term; + +error_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} + +ERL_NIF_TERM saltywitch_randombytes_seedbytes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + (void) argc; + (void) argv; + + return enif_make_uint64(env, randombytes_SEEDBYTES); +} diff --git a/c_src/saltywitch.c b/c_src/saltywitch.c new file mode 100644 index 0000000..4a0416e --- /dev/null +++ b/c_src/saltywitch.c @@ -0,0 +1,93 @@ +#include "saltywitch.h" + +_Static_assert((sizeof(size_t) >= sizeof(uint64_t)), "size_t must be at least 64-bits"); + +ERL_NIF_TERM saltywitch_exception(ErlNifEnv *env, ERL_NIF_TERM type, ERL_NIF_TERM reason) +{ + ERL_NIF_TERM exception = enif_make_tuple2(env, type, reason); + + return enif_raise_exception(env, exception); +} + +static int saltywitch_load(ErlNifEnv *env, void **data, ERL_NIF_TERM info) +{ + (void) data; + (void) info; + + if (sodium_init() < 0) { + return -1; + } + + saltywitch_nif_init_atoms(env); + + if (saltywitch_nif_init_generichash(env) != 0) { + return -1; + } + + return 0; +} + +static ERL_NIF_TERM saltywitch_info(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + (void) argc; + (void) argv; + + return enif_make_tuple3(env, + enif_make_string(env, SODIUM_VERSION_STRING, ERL_NIF_LATIN1), + enif_make_int(env, SODIUM_LIBRARY_VERSION_MAJOR), + enif_make_int(env, SODIUM_LIBRARY_VERSION_MINOR)); +} + +static ErlNifFunc nif_funcs[] = { + {"info", 0, saltywitch_info}, + + /* random data */ + {"randombytes_random", 0, saltywitch_randombytes_random}, + {"randombytes_uniform", 1, saltywitch_randombytes_uniform}, + {"randombytes_buf", 1, saltywitch_randombytes_buf}, + {"randombytes_buf_deterministic", 2, saltywitch_randombytes_buf_deterministic}, + {"randombytes_seedbytes", 0, saltywitch_randombytes_seedbytes}, + + /* secretstream */ + /* + {"secretstream_xchacha20poly1305_keygen", 0, saltywitch_secretstream_xchacha20poly1305_keygen}, + {"secretstream_xchacha20poly1305_init_push", 1, saltywitch_secretstream_xchacha20poly1305_init_push}, + */ + + /* secretbox */ + {"secretbox_keygen", 0, saltywitch_secretbox_keygen}, + + {"secretbox_easy", 3, saltywitch_secretbox_easy}, + {"secretbox_open_easy", 3, saltywitch_secretbox_open_easy}, + + {"secretbox_detached", 3, saltywitch_secretbox_detached}, + {"secretbox_open_detached", 4, saltywitch_secretbox_open_detached}, + + {"secretbox_keybytes", 0, saltywitch_secretbox_keybytes}, + {"secretbox_macbytes", 0, saltywitch_secretbox_macbytes}, + {"secretbox_noncebytes", 0, saltywitch_secretbox_noncebytes}, + + /* generichash */ + {"generichash", 1, saltywitch_generichash}, + {"generichash", 2, saltywitch_generichash}, + + {"generichash_keygen", 0, saltywitch_generichash_keygen}, + + {"generichash_init", 0, saltywitch_generichash_init}, + {"generichash_init", 1, saltywitch_generichash_init}, + {"generichash_init", 2, saltywitch_generichash_init}, + {"generichash_update", 2, saltywitch_generichash_update, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"generichash_final", 1, saltywitch_generichash_final}, + + /* shorthash */ + {"shorthash_keygen", 0, saltywitch_shorthash_keygen}, + {"shorthash", 2, saltywitch_shorthash}, + + /* pwhash */ + //{"pwhash", 6, saltywitch_pwhash, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"pwhash_str", 3, saltywitch_pwhash_str, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"pwhash_str_verify", 2, saltywitch_pwhash_str_verify, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"pwhash_str_needs_rehash", 3, saltywitch_pwhash_str_needs_rehash}, +}; + +ERL_NIF_INIT(Elixir.SaltyWitch.NIF, nif_funcs, saltywitch_load, NULL, NULL, NULL) diff --git a/c_src/saltywitch.h b/c_src/saltywitch.h new file mode 100644 index 0000000..9a284f1 --- /dev/null +++ b/c_src/saltywitch.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include + +#if __STDC_VERSION__ < 202300L +#include +#endif + +extern ERL_NIF_TERM atom_badarg; +extern ERL_NIF_TERM atom_error; +extern ERL_NIF_TERM atom_ok; + +extern ERL_NIF_TERM atom_err_opaque; + +extern ERL_NIF_TERM atom_err_invalid_type; +extern ERL_NIF_TERM atom_err_nif_alloc; + +extern ERL_NIF_TERM atom_err_invalid_key_size; +extern ERL_NIF_TERM atom_err_invalid_nonce_size; +extern ERL_NIF_TERM atom_err_invalid_salt_size; +extern ERL_NIF_TERM atom_err_invalid_seed_size; +extern ERL_NIF_TERM atom_err_invalid_tag_size; +extern ERL_NIF_TERM atom_err_verification_failed; + +extern ERL_NIF_TERM atom_err_ciphertext_too_small; +extern ERL_NIF_TERM atom_err_key_too_large; +extern ERL_NIF_TERM atom_err_key_too_small; +extern ERL_NIF_TERM atom_err_output_too_large; +extern ERL_NIF_TERM atom_err_output_too_small; + +extern ERL_NIF_TERM atom_err_pwhash_too_long; +extern ERL_NIF_TERM atom_err_pwhash_needs_rehash; +extern ERL_NIF_TERM atom_err_pwhash_mem_too_large; +extern ERL_NIF_TERM atom_err_pwhash_mem_too_small; +extern ERL_NIF_TERM atom_err_pwhash_ops_too_large; +extern ERL_NIF_TERM atom_err_pwhash_ops_too_small; + +/* init */ +void saltywitch_nif_init_atoms(ErlNifEnv *env); +int saltywitch_nif_init_generichash(ErlNifEnv *env); + +/* helpers */ +ERL_NIF_TERM saltywitch_exception(ErlNifEnv *env, ERL_NIF_TERM type, ERL_NIF_TERM reason); + +/* random */ +ERL_NIF_TERM saltywitch_randombytes_random(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_randombytes_uniform(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_randombytes_buf(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_randombytes_buf_deterministic(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_randombytes_seedbytes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +/* generichash */ +ERL_NIF_TERM saltywitch_generichash(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +ERL_NIF_TERM saltywitch_generichash_keygen(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +ERL_NIF_TERM saltywitch_generichash_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_generichash_update(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_generichash_final(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +/* secretbox*/ +ERL_NIF_TERM saltywitch_secretbox_keygen(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +ERL_NIF_TERM saltywitch_secretbox_easy(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_secretbox_open_easy(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +ERL_NIF_TERM saltywitch_secretbox_detached(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_secretbox_open_detached(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +ERL_NIF_TERM saltywitch_secretbox_keybytes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_secretbox_macbytes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_secretbox_noncebytes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +/* auth */ +ERL_NIF_TERM saltywitch_auth_keygen(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_auth(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_auth_verify(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +ERL_NIF_TERM saltywitch_auth_hmacsha256_keygen(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_auth_hmacsha256(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_auth_hmacsha256_verify(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +ERL_NIF_TERM saltywitch_auth_hmacsha512_keygen(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_auth_hmacsha512(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_auth_hmacsha512_verify(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +ERL_NIF_TERM saltywitch_auth_hmacsha512256_keygen(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_auth_hmacsha512256(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_auth_hmacsha512256_verify(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +/* pwhash */ +ERL_NIF_TERM saltywitch_pwhash(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_pwhash_str(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_pwhash_str_verify(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_pwhash_str_needs_rehash(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +/* shorthash */ +ERL_NIF_TERM saltywitch_shorthash_keygen(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_shorthash(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + +/* key exchange */ +/* +ERL_NIF_TERM saltywitch_kx_keypair(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_kx_seed_keypair(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_kx_client_session_keys(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_kx_server_session_keys(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + + +ERL_NIF_TERM saltywitch_kx_publickeybytes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM saltywitch_kx_publickeybytes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +*/ diff --git a/c_src/secretbox.c b/c_src/secretbox.c new file mode 100644 index 0000000..bb5dac5 --- /dev/null +++ b/c_src/secretbox.c @@ -0,0 +1,260 @@ +#include "saltywitch.h" + +ERL_NIF_TERM saltywitch_secretbox_keygen(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM term; + + unsigned char *key = enif_make_new_binary(env, crypto_secretbox_KEYBYTES, &term); + if (key == NULL) { + return saltywitch_exception(env, atom_error, atom_err_nif_alloc); + } + + crypto_secretbox_keygen(key); + + return term; +} + +ERL_NIF_TERM saltywitch_secretbox_easy(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary key, nonce, message; + unsigned char *cipher; + ERL_NIF_TERM term; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + if (enif_inspect_binary(env, argv[0], &key) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (key.size != crypto_secretbox_KEYBYTES) { + reason = atom_err_invalid_key_size; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[1], &nonce) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (nonce.size != crypto_secretbox_NONCEBYTES) { + reason = atom_err_invalid_nonce_size; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[2], &message) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + cipher = enif_make_new_binary(env, message.size + crypto_secretbox_MACBYTES, &term); + if (cipher == NULL) { + reason = atom_err_nif_alloc; + goto error_FAULT; + } + + crypto_secretbox_easy(cipher, message.data, message.size, nonce.data, key.data); + + return term; + +error_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} + +ERL_NIF_TERM saltywitch_secretbox_open_easy(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary key, nonce, cipher; + unsigned char *message; + ERL_NIF_TERM term; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + (void) argc; + + if (enif_inspect_binary(env, argv[0], &key) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (key.size != crypto_secretbox_KEYBYTES) { + reason = atom_err_invalid_key_size; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[1], &nonce) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (nonce.size != crypto_secretbox_NONCEBYTES) { + reason = atom_err_invalid_nonce_size; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[2], &cipher) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (cipher.size <= crypto_secretbox_MACBYTES) { + reason = atom_err_ciphertext_too_small; + goto badarg_FAULT; + } + + message = enif_make_new_binary(env, cipher.size - crypto_secretbox_MACBYTES, &term); + if (message == NULL) { + reason = atom_err_nif_alloc; + goto error_FAULT; + } + + if (crypto_secretbox_open_easy(message, cipher.data, cipher.size, nonce.data, key.data) != 0) { + reason = atom_err_invalid_type; + goto error_FAULT; + } + + return term; + +error_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} + +ERL_NIF_TERM saltywitch_secretbox_detached(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM cipher_term, mac_term; + ErlNifBinary key, message, nonce; + unsigned char *cipher, *mac; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + if (enif_inspect_binary(env, argv[0], &key) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (key.size != crypto_secretbox_KEYBYTES) { + reason = atom_err_invalid_key_size; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[1], &nonce) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (nonce.size != crypto_secretbox_NONCEBYTES) { + reason = atom_err_invalid_nonce_size; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[2], &message) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + cipher = enif_make_new_binary(env, message.size, &cipher_term); + if (cipher == NULL) { + reason = atom_err_nif_alloc; + goto error_FAULT; + } + + mac = enif_make_new_binary(env, crypto_secretbox_MACBYTES, &mac_term); + if (mac == NULL) { + reason = atom_err_nif_alloc; + goto error_FAULT; + } + + crypto_secretbox_detached(cipher, mac, message.data, message.size, nonce.data, key.data); + + return enif_make_tuple2(env, cipher_term, mac_term); + +error_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} + +ERL_NIF_TERM saltywitch_secretbox_open_detached(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary key, nonce, cipher, mac; + unsigned char *message; + ERL_NIF_TERM term; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + if (enif_inspect_binary(env, argv[0], &key) == false) { + reason = atom_err_invalid_type; + goto FAULT; + } + + if (key.size != crypto_secretbox_KEYBYTES) { + goto FAULT; + } + + if (enif_inspect_binary(env, argv[1], &nonce) == false) { + reason = atom_err_invalid_type; + goto FAULT; + } + + if (nonce.size != crypto_secretbox_NONCEBYTES) { + goto FAULT; + } + + if (enif_inspect_binary(env, argv[2], &cipher) == false) { + reason = atom_err_invalid_type; + goto FAULT; + } + + if (enif_inspect_binary(env, argv[3], &mac) == false) { + reason = atom_err_invalid_type; + goto FAULT; + } + + if (mac.size != crypto_secretbox_MACBYTES) { + goto FAULT; + } + + message = enif_make_new_binary(env, cipher.size, &term); + + if (crypto_secretbox_open_detached(message, cipher.data, mac.data, cipher.size, nonce.data, key.data) != 0) { + err = atom_error; + goto FAULT; + } + + return term; + +FAULT: + term = saltywitch_exception(env, err, reason); + return term; +} + +ERL_NIF_TERM saltywitch_secretbox_keybytes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + (void) argc; + (void) argv; + + return enif_make_uint(env, crypto_secretbox_KEYBYTES); +} + +ERL_NIF_TERM saltywitch_secretbox_macbytes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + (void) argc; + (void) argv; + + return enif_make_uint(env, crypto_secretbox_MACBYTES); +} + +ERL_NIF_TERM saltywitch_secretbox_noncebytes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + (void) argc; + (void) argv; + + return enif_make_uint(env, crypto_secretbox_NONCEBYTES); +} diff --git a/c_src/shorthash.c b/c_src/shorthash.c new file mode 100644 index 0000000..4e1d244 --- /dev/null +++ b/c_src/shorthash.c @@ -0,0 +1,60 @@ +#include "saltywitch.h" + +ERL_NIF_TERM saltywitch_shorthash_keygen(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM term; + + (void) argc; + (void) argv; + + unsigned char *key = enif_make_new_binary(env, crypto_shorthash_KEYBYTES, &term); + if (key == NULL) { + return saltywitch_exception(env, atom_error, atom_err_opaque); + } + + crypto_shorthash_keygen(key); + + return term; +} + +ERL_NIF_TERM saltywitch_shorthash(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary key, message; + unsigned char *hash; + ERL_NIF_TERM term; + + ERL_NIF_TERM err = atom_badarg; + ERL_NIF_TERM reason = atom_err_opaque; + + (void) argc; + + if (enif_inspect_binary(env, argv[0], &key) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + if (key.size != crypto_shorthash_KEYBYTES) { + reason = atom_err_invalid_key_size; + goto badarg_FAULT; + } + + if (enif_inspect_binary(env, argv[1], &message) == false) { + reason = atom_err_invalid_type; + goto badarg_FAULT; + } + + hash = enif_make_new_binary(env, crypto_shorthash_BYTES, &term); + if (hash == NULL) { + reason = atom_err_nif_alloc; + goto error_FAULT; + } + + crypto_shorthash(hash, message.data, message.size, key.data); + + return term; + +error_FAULT: + err = atom_error; +badarg_FAULT: + return saltywitch_exception(env, err, reason); +} diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..6dc53c9 --- /dev/null +++ b/clean.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +for dir in ./build-*; do + [ -d $dir ] && rm -r $dir +done diff --git a/lib/salty_witch.ex b/lib/salty_witch.ex new file mode 100644 index 0000000..9d61522 --- /dev/null +++ b/lib/salty_witch.ex @@ -0,0 +1,18 @@ +defmodule SaltyWitch do + + @moduledoc """ + Documentation for `SaltyWitch`. + """ + + @doc """ + Returns version information + + iex> SaltyWitch.info + {'1.0.18', 10, 3} + + """ + + def info do + SaltyWitch.NIF.info() + end +end diff --git a/lib/salty_witch/nif.ex b/lib/salty_witch/nif.ex new file mode 100644 index 0000000..543775e --- /dev/null +++ b/lib/salty_witch/nif.ex @@ -0,0 +1,74 @@ +defmodule SaltyWitch.NIF do + @compile {:autoload, false} + @on_load :__on_load__ + + def __on_load__() do + path = :filename.join(:code.priv_dir(:saltywitch), ~c"saltywitch") + :erlang.load_nif(path, 0) + end + + def info, do: :erlang.nif_error(:not_loaded) + + def randombytes_random, do: :erlang.nif_error(:not_loaded) + + # random + def randombytes_random, do: :erlang.nif_error(:not_loaded) + + def randombytes_uniform(upper) + def randombytes_uniform(_), do: :erlang.nif_error(:not_loaded) + + def randombytes_buf(len) + def randombytes_buf(_), do: :erlang.nif_error(:not_loaded) + + def randombytes_buf_deterministic(len, seed) + def randombytes_buf_deterministic(_, _), do: :erlang.nif_error(:not_loaded) + + def randombytes_seedbytes, do: :erlang.nif_error(:not_loaded) + + # secretbox + def secretbox_keygen, do: :erlang.nif_error(:not_loaded) + + def secretbox_easy(message, nonce, key) + def secretbox_easy(_, _, _), do: :erlang.nif_error(:not_loaded) + + def secretbox_open_easy(ciphertext, nonce, key) + def secretbox_open_easy(_, _, _), do: :erlang.nif_error(:not_loaded) + + def secretbox_detached(message, nonce, key) + def secretbox_detached(_, _, _), do: :erlang.nif_error(:not_loaded) + + def secretbox_open_detached(ciphertext, mac, nonce, key) + def secretbox_open_detached(_, _, _, _), do: :erlang.nif_error(:not_loaded) + + def secretbox_keybytes, do: :erlang.nif_error(:not_loaded) + def secretbox_macbytes, do: :erlang.nif_error(:not_loaded) + def secretbox_noncebytes, do: :erlang.nif_error(:not_loaded) + + # generichash + def generichash(_), do: :erlang.nif_error(:not_loaded) + def generichash(_, _), do: :erlang.nif_error(:not_loaded) + + def generichash_keygen, do: :erlang.nif_error(:not_loaded) + + def generichash_init, do: :erlang.nif_error(:not_loaded) + def generichash_init(_), do: :erlang.nif_error(:not_loaded) + def generichash_init(_, _), do: :erlang.nif_error(:not_loaded) + def generichash_update(_, _), do: :erlang.nif_error(:not_loaded) + def generichash_final(_), do: :erlang.nif_error(:not_loaded) + + # shorthash + def shorthash_keygen, do: :erlang.nif_error(:not_loaded) + + def shorthash(key, message) + def shorthash(_, _), do: :erlang.nif_error(:not_loaded) + + # pwhash + def pwhash_str(pass, ops, mem) + def pwhash_str(_, _, _), do: :erlang.nif_error(:not_loaded) + + def pwhash_str_verify(pwhash, pass) + def pwhash_str_verify(_, _), do: :erlang.nif_error(:not_loaded) + + def pwhash_str_needs_rehash(pwhash, ops, mem) + def pwhash_str_needs_rehash(_, _, _), do: :erlang.nif_error(:not_loaded) +end diff --git a/lib/salty_witch/passwordhash.ex b/lib/salty_witch/passwordhash.ex new file mode 100644 index 0000000..66355e9 --- /dev/null +++ b/lib/salty_witch/passwordhash.ex @@ -0,0 +1,11 @@ +defmodule SaltyWitch.PasswordHash do + alias SaltyWitch.NIF, as: N + + def string(pass, ops, mem) do + N.pwhash_str(pass, ops, mem) + end + + def verify(pwhash, pass) do + N.pwhash_str_verify(pwhash, pass) + end +end diff --git a/lib/salty_witch/random.ex b/lib/salty_witch/random.ex new file mode 100644 index 0000000..51cab3b --- /dev/null +++ b/lib/salty_witch/random.ex @@ -0,0 +1,19 @@ +defmodule SaltyWitch.Random do + alias SaltyWitch.NIF, as: N + + def seedbytes do + N.randombytes_seedbytes() + end + + def uint32 do + N.randombytes_random() + end + + def bytes(size) when size > 0 do + N.randombytes_buf(size) + end + + def deterministic(size, seed) when size > 0 do + N.randombytes_buf_deterministic(size, seed) + end +end diff --git a/lib/salty_witch/secretbox.ex b/lib/salty_witch/secretbox.ex new file mode 100644 index 0000000..e8fb274 --- /dev/null +++ b/lib/salty_witch/secretbox.ex @@ -0,0 +1,2 @@ +defmodule SaltyWitch.SecretBox do +end diff --git a/lib/salty_witch/shorthash.ex b/lib/salty_witch/shorthash.ex new file mode 100644 index 0000000..e9911d9 --- /dev/null +++ b/lib/salty_witch/shorthash.ex @@ -0,0 +1,9 @@ +defmodule SaltyWitch.ShortHash do + def keygen do + SaltyWitch.NIF.shorthash_keygen() + end + + def shorthash(key, message) do + SaltyWitch.NIF.shorthash(key, message) + end +end diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..7017eb3 --- /dev/null +++ b/meson.build @@ -0,0 +1,169 @@ +project('saltywitch', 'c', + version: 'trunk', + license: 'BSD-3-Clause', + meson_version: '>=1.1.0', + default_options: [ + 'c_std=c99', + 'b_asneeded=true', + 'b_lto=true', + 'b_lundef=false', + 'b_ndebug=if-release', + 'b_pie=true', + 'b_staticpic=true', + 'warning_level=2', + 'werror=false', + ], +) + +cc = meson.get_compiler('c') + +# Default Flags +add_project_arguments( + cc.get_supported_arguments([ + '-Walloca', + '-Warith-conversion', + '-Warray-bounds-pointer-arithmetic', + '-Warray-bounds=2', + '-Wbad-function-cast', + '-Wbitwise-instead-of-logical', + '-Wcast-align=strict', + '-Wcomma', + '-Wconditional-uninitialized', + '-Wconversion', + '-Wdate-time', + '-Wduplicated-branches', + '-Wduplicated-cond', + '-Wendif-labels', + '-Wenum-int-mismatch', + '-Wfloat-equal', + '-Wformat-overflow=2', + '-Wformat-security', + '-Wformat-truncation=2', + '-Wformat-type-confusion', + '-Wformat=2', + '-Wimplicit-fallthrough=5', + '-Wimplicit-int', + '-Winit-self', + '-Wjump-misses-init', + '-Wliteral-range', + '-Wlogical-op', + '-Wloop-analysis', + '-Wmaybe-unitialized', + '-Wmisleading-indentation', + '-Wmissing-include-dirs', + '-Wmissing-noreturn', + '-Wmissing-prototypes', + '-Wnested-externs', + '-Wno-format-nonliteral', + '-Wno-missing-braces', + '-Wno-missing-field-initializers', + '-Wno-typedef-redefinition', + '-Wnull-dereference', + '-Woverflow', + '-Wredundant-decls', + '-Wshadow', + '-Wshift-sign-overflow', + '-Wshorten-64-to-32', + '-Wsign-conversion', + '-Wsizeof-pointer-memaccess', + '-Wstack-usage=1000000', + '-Wstrict-overflow=3', + '-Wstrict-prototypes', + '-Wswitch-enum', + '-Wtautological-constant-in-range-compare', + '-Wthread-safety', + '-Wtraditional-conversion', + '-Wtrampolines', + '-Wundef', + '-Wuninitialized', + '-Wunused-but-set-variable', + '-Wuse-after-free=3', + '-Wvla', + '-Wwrite-strings', + + '-Wno-error=#warnings', + '-Wno-error=attribute-warning', + '-Wno-error=unknown-attributes', + '-Wno-error=unknown-pragmas', + '-Wno-error=unused-parameter', + + '-fcf-protection=full', + '-fno-common', + '-fno-delete-null-pointer-checks', + '-fno-strict-aliasing', + '-fstack-clash-protection', + '-fstack-protector', + '-fstack-protector-strong', + '-fwrapv', + '-fzero-call-used-regs=all', + ]), language: 'c' +) + +if get_option('buildtype') != 'debug' + add_project_arguments( + cc.get_supported_arguments([ + '-ffunction-sections', + '-fdata-sections', + ]), language: 'c' + ) + + add_project_link_arguments( + cc.get_supported_link_arguments([ + '-Wl,--gc-sections', + '-Wl,-z,noexecstack', + '-Wl,-z,noexecheap', + '-Wl,-z,now', + '-Wl,-z,relro', + '-Wl,-z,separate-code', + ]), language: 'c' + ) + + add_project_arguments([ + '-D_FORTIFY_SOURCE=3', + ], language: 'c' + ) +endif + +add_project_arguments([ + '-D_DEFAULT_SOURCE', + ], language: 'c' +) + +erts_inc = get_option('erts_include_dir') +erl_interface_inc = get_option('erl_interface_include_dir') +erl_interface_lib_dir = get_option('erl_interface_lib_dir') + +erl_interface_dep = cc.find_library('ei', dirs: [erl_interface_lib_dir], static: true, required: true) + +sodium_dep = dependency('libsodium', version: '1.0.18', required: true) + +saltywitch_src = files([ + 'c_src/saltywitch.c', + + 'c_src/atoms.c', + 'c_src/auth.c', + 'c_src/generichash.c', + 'c_src/kx.c', + 'c_src/pwhash.c', + 'c_src/random.c', + 'c_src/secretbox.c', + 'c_src/shorthash.c', +]) + +saltywitch = shared_library( + 'saltywitch', + saltywitch_src, + gnu_symbol_visibility: 'hidden', + include_directories: [ erl_interface_inc, erts_inc ], + dependencies: [ erl_interface_dep, sodium_dep ], + name_prefix: '', + install: true, + install_dir: 'priv', +) + +summary( + { + 'C compiler' : cc.get_id(), + 'C linker' : cc.get_linker_id(), + }, bool_yn: true +) diff --git a/meson.options b/meson.options new file mode 100644 index 0000000..ca3dcb0 --- /dev/null +++ b/meson.options @@ -0,0 +1,4 @@ +option('mix_target', type: 'string', description: '') +option('erts_include_dir', type: 'string', description: '') +option('erl_interface_lib_dir', type: 'string', description: '') +option('erl_interface_include_dir', type: 'string', description: '') diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..387ac4c --- /dev/null +++ b/mix.exs @@ -0,0 +1,45 @@ +defmodule SaltyWitch.MixProject do + use Mix.Project + + @version "0.0.1" + + def project do + [ + app: :saltywitch, + version: @version, + elixir: "~> 1.14", + compilers: [:elixir_make] ++ Mix.compilers(), + make_clean: ["clean"], + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + defp deps do + [ + {:elixir_make, "~> 0.7", runtime: false} + ] + end + + defp package do + [ + maintainers: ["Aydin Mercan"], + licenses: ["BSD-3-Clause"], + files: [ + # Sources + "c_src", + "lib", + "mix.exs", + # Build + "CMakeLists.txt", + "Makefile", + "build.sh", + "clean.sh", + "meson.build", + # Meta + "LICENSE", + "README.md" + ] + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..76a5ca7 --- /dev/null +++ b/mix.lock @@ -0,0 +1,3 @@ +%{ + "elixir_make": {:hex, :elixir_make, "0.7.6", "67716309dc5d43e16b5abbd00c01b8df6a0c2ab54a8f595468035a50189f9169", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5a0569756b0f7873a77687800c164cca6dfc03a09418e6fcf853d78991f49940"}, +} diff --git a/test/salty_witch_test.exs b/test/salty_witch_test.exs new file mode 100644 index 0000000..220f088 --- /dev/null +++ b/test/salty_witch_test.exs @@ -0,0 +1,8 @@ +defmodule SaltyWitchTest do + use ExUnit.Case + doctest SaltyWitch + + test "greets the world" do + assert SaltyWitch.hello() == :world + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()