From 2982515f2ed87ab5bf92de2809e50e136f456932 Mon Sep 17 00:00:00 2001 From: Aydin Mercan Date: Wed, 31 May 2023 14:31:03 +0300 Subject: [PATCH] initial commit --- .formatter.exs | 4 + .gitignore | 28 ++++ CMakeLists.txt | 47 ++++++ Makefile | 7 + README.md | 41 +++++ STYLE.md | 17 +++ build.sh | 59 +++++++ c_src/atoms.c | 66 ++++++++ c_src/auth.c | 102 +++++++++++++ c_src/generichash.c | 262 ++++++++++++++++++++++++++++++++ c_src/kx.c | 1 + c_src/pwhash.c | 199 ++++++++++++++++++++++++ c_src/random.c | 97 ++++++++++++ c_src/saltywitch.c | 93 ++++++++++++ c_src/saltywitch.h | 113 ++++++++++++++ c_src/secretbox.c | 260 +++++++++++++++++++++++++++++++ c_src/shorthash.c | 60 ++++++++ clean.sh | 5 + lib/salty_witch.ex | 18 +++ lib/salty_witch/nif.ex | 74 +++++++++ lib/salty_witch/passwordhash.ex | 11 ++ lib/salty_witch/random.ex | 19 +++ lib/salty_witch/secretbox.ex | 2 + lib/salty_witch/shorthash.ex | 9 ++ meson.build | 169 ++++++++++++++++++++ meson.options | 4 + mix.exs | 45 ++++++ mix.lock | 3 + test/salty_witch_test.exs | 8 + test/test_helper.exs | 1 + 30 files changed, 1824 insertions(+) create mode 100644 .formatter.exs create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 STYLE.md create mode 100755 build.sh create mode 100644 c_src/atoms.c create mode 100644 c_src/auth.c create mode 100644 c_src/generichash.c create mode 100644 c_src/kx.c create mode 100644 c_src/pwhash.c create mode 100644 c_src/random.c create mode 100644 c_src/saltywitch.c create mode 100644 c_src/saltywitch.h create mode 100644 c_src/secretbox.c create mode 100644 c_src/shorthash.c create mode 100755 clean.sh create mode 100644 lib/salty_witch.ex create mode 100644 lib/salty_witch/nif.ex create mode 100644 lib/salty_witch/passwordhash.ex create mode 100644 lib/salty_witch/random.ex create mode 100644 lib/salty_witch/secretbox.ex create mode 100644 lib/salty_witch/shorthash.ex create mode 100644 meson.build create mode 100644 meson.options create mode 100644 mix.exs create mode 100644 mix.lock create mode 100644 test/salty_witch_test.exs create mode 100644 test/test_helper.exs 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()