From 279c2c32c83cbe97ad92ec3c183d1ffbeb05de15 Mon Sep 17 00:00:00 2001 From: Garry Hill Date: Wed, 20 Nov 2019 12:11:21 +0000 Subject: [PATCH] Add support for multi-part signatures --- c_src/enacl_nif.c | 146 +++++++++++++++++++++++++++++++++++++++++++++- src/enacl.erl | 46 +++++++++++++++ src/enacl_nif.erl | 10 ++++ 3 files changed, 201 insertions(+), 1 deletion(-) diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 1497215..da8535d 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -10,6 +10,7 @@ #define ATOM_FALSE "false" #define CRYPTO_GENERICHASH_STATE_RESOURCE "crypto_generichash_state" +#define CRYPTO_SIGN_STATE_RESOURCE "crypto_sign_state" #ifdef ERL_NIF_DIRTY_JOB_CPU_BOUND #define erl_nif_dirty_job_cpu_bound_macro(a,b,c) {a,b,c,ERL_NIF_DIRTY_JOB_CPU_BOUND} @@ -20,8 +21,9 @@ //{"crypto_box_keypair", 0, enif_crypto_box_keypair, ERL_NIF_DIRTY_JOB_CPU_BOUND} /* Errors */ -/* This is a global variable for resource type */ +/* These are global variables for resource types */ static ErlNifResourceType *generichash_state_type = NULL; +static ErlNifResourceType *sign_state_type = NULL; static ERL_NIF_TERM nacl_error_tuple(ErlNifEnv *env, char *error_atom) { @@ -35,6 +37,10 @@ int enif_crypto_load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) { if( !(generichash_state_type = enif_open_resource_type(env, NULL, CRYPTO_GENERICHASH_STATE_RESOURCE, NULL, ERL_NIF_RT_CREATE, NULL)) ) { return -1; } + // Create a new resource type for crypto_sign_state + if( !(sign_state_type = enif_open_resource_type(env, NULL, CRYPTO_SIGN_STATE_RESOURCE, NULL, ERL_NIF_RT_CREATE, NULL)) ) { + return -1; + } return sodium_init(); } @@ -620,6 +626,139 @@ ERL_NIF_TERM enif_crypto_sign_verify_detached(ErlNifEnv* env, int argc, ERL_NIF_ } } +/* + int crypto_sign_init(crypto_sign_state *state) + */ + +static +ERL_NIF_TERM enif_crypto_sign_init(ErlNifEnv* env, int argc, ERL_NIF_TERM const argv[]) { + if( (argc != 0) ) { + return enif_make_badarg(env); + } + + void* state = enif_alloc_resource(sign_state_type, crypto_sign_statebytes()); + + if( !state ) { + return nacl_error_tuple(env, "alloc_failed"); + } + + if( 0 != crypto_sign_init(state) ) { + return nacl_error_tuple(env, "sign_init_error"); + } + + // Create return values + ERL_NIF_TERM e1 = enif_make_atom(env, "signstate"); + ERL_NIF_TERM e2 = enif_make_resource(env, state); + + // release dynamically allocated memory to erlang to mange + enif_release_resource(state); + + // return a tuple + return enif_make_tuple2(env, e1, e2); +} + +/* + int crypto_sign_update(crypto_sign_state *state, + const unsigned char *m, + unsigned long long mlen); + */ + +static +ERL_NIF_TERM enif_crypto_sign_update(ErlNifEnv* env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary data; + + void *state; + + // Validate the arguments + if( (argc != 2) || + (!enif_get_resource(env, argv[0], sign_state_type, (void **)&state)) || + (!enif_inspect_binary(env, argv[1], &data)) ) { + return enif_make_badarg(env); + } + + if( 0 != crypto_sign_update(state, data.data, data.size) ) { + return nacl_error_tuple(env, "sign_update_error"); + } + + // Generate return value + ERL_NIF_TERM e1 = enif_make_atom(env, "signstate"); + ERL_NIF_TERM e2 = enif_make_resource(env, state); + + // return a tuple + return enif_make_tuple2(env, e1, e2); +} + +/* + int crypto_sign_final_create(crypto_sign_state *state, + unsigned char *sig, + unsigned long long *siglen_p, + const unsigned char *sk); + */ + +static +ERL_NIF_TERM enif_crypto_sign_final_create(ErlNifEnv* env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary sk, sig; + + void *state; + + unsigned long long siglen; + + if( (argc != 2) || + (!enif_get_resource(env, argv[0], sign_state_type, (void **)&state)) || + (!enif_inspect_binary(env, argv[1], &sk)) ) { + return enif_make_badarg(env); + } + + if( sk.size != crypto_sign_SECRETKEYBYTES ) { + return enif_make_badarg(env); + } + + if( !enif_alloc_binary(crypto_sign_BYTES, &sig) ) { + return nacl_error_tuple(env, "alloc_failed"); + } + + if( 0 != crypto_sign_final_create(state, sig.data, &siglen, sk.data) ) { + enif_release_binary(&sig); + return nacl_error_tuple(env, "sign_error"); + } + + ERL_NIF_TERM ok = enif_make_atom(env, ATOM_OK); + ERL_NIF_TERM ret = enif_make_binary(env, &sig); + + return enif_make_tuple2(env, ok, ret); +} + +/* + int crypto_sign_final_verify(crypto_sign_state *state, + unsigned char *sig, + const unsigned char *pk); + */ + +static +ERL_NIF_TERM enif_crypto_sign_final_verify(ErlNifEnv* env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary pk, sig; + + void *state; + + if( (argc != 3) || + (!enif_get_resource(env, argv[0], sign_state_type, (void **)&state)) || + (!enif_inspect_binary(env, argv[1], &sig)) || + (!enif_inspect_binary(env, argv[2], &pk)) ) { + return enif_make_badarg(env); + } + + if( pk.size != crypto_sign_PUBLICKEYBYTES ) { + return enif_make_badarg(env); + } + + if( 0 == crypto_sign_final_verify(state, sig.data, pk.data) ) { + return enif_make_atom(env, ATOM_OK); + } + + return nacl_error_tuple(env, "failed_verification"); +} + + /* Sealed box functions */ static @@ -1675,6 +1814,11 @@ static ErlNifFunc nif_funcs[] = { erl_nif_dirty_job_cpu_bound_macro("crypto_sign_detached", 2, enif_crypto_sign_detached), erl_nif_dirty_job_cpu_bound_macro("crypto_sign_verify_detached", 3, enif_crypto_sign_verify_detached), + {"crypto_sign_init", 0, enif_crypto_sign_init}, + {"crypto_sign_update", 2, enif_crypto_sign_update}, + erl_nif_dirty_job_cpu_bound_macro("crypto_sign_final_create", 2, enif_crypto_sign_final_create), + erl_nif_dirty_job_cpu_bound_macro("crypto_sign_final_verify", 3, enif_crypto_sign_final_verify), + {"crypto_box_SEALBYTES", 0, enif_crypto_box_SEALBYTES}, erl_nif_dirty_job_cpu_bound_macro("crypto_box_seal", 2, enif_crypto_box_seal), diff --git a/src/enacl.erl b/src/enacl.erl index 15ea2c2..7af1790 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -39,6 +39,11 @@ sign_detached/2, sign_verify_detached/3, + sign_init/0, + sign_update/2, + sign_final_create/2, + sign_final_verify/3, + %% EQC box_seal/2, box_seal_open/3 @@ -579,6 +584,47 @@ sign_verify_detached(SIG, M, PK) -> false -> {error, failed_verification} end. +-type sign_state() :: {signstate, reference()}. + +%% @doc sign_init/0 initialize a multi-part signature state. +%% +%% This state must be passed to all future calls to `sign_update/2`, +%% `sign_final_create/2` and `sign_final_verify/3`. +%% @end +-spec sign_init() -> sign_state(). +sign_init() -> + enacl_nif:crypto_sign_init(). + +%% @doc sign_update/2 update the signature state `S` with a new chunk of data `M`. +%% @end +-spec sign_update(S, M) -> sign_state() | {error, sign_update_error} + when S :: sign_state(), + M :: iodata(). +sign_update({signstate, SignState}, M) -> + enacl_nif:crypto_sign_update(SignState, M). + + +%% @doc sign_final_create/2 computes the signature for the previously supplied +%% message(s) using the secret key `SK`. +%% @end +-spec sign_final_create(S, SK) -> {ok, binary()} | {error, atom()} + when S :: sign_state(), + SK :: iodata(). +sign_final_create({signstate, SignState}, SK) -> + enacl_nif:crypto_sign_final_create(SignState, SK). + +%% @doc sign_final_verify/3 verify a chunked signature +%% +%% Verifies that `SIG` is a valid signature for the message whose content has +%% been previously supplied using `sign_update/2` using the public key `PK.` +%% @end +-spec sign_final_verify(S, SIG, PK) -> ok | {error, failed_verification} + when S :: sign_state(), + SIG :: binary(), + PK :: iodata(). +sign_final_verify({signstate, SignState}, SIG, PK) -> + enacl_nif:crypto_sign_final_verify(SignState, SIG, PK). + %% @private -spec box_secret_key_bytes() -> pos_integer(). box_secret_key_bytes() -> diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index 798ef53..c6a9771 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -34,6 +34,11 @@ crypto_sign_detached/2, crypto_sign_verify_detached/3, + crypto_sign_init/0, + crypto_sign_update/2, + crypto_sign_final_create/2, + crypto_sign_final_verify/3, + crypto_box_seal/2, crypto_box_seal_open/3, crypto_box_SEALBYTES/0 @@ -220,6 +225,11 @@ crypto_sign_detached(_M, _SK) -> erlang:nif_error(nif_not_loaded). crypto_sign_verify_detached(_Sig, _M, _PK) -> erlang:nif_error(nif_not_loaded). +crypto_sign_init() -> erlang:nif_error(nif_not_loaded). +crypto_sign_update(_S, _M) -> erlang:nif_error(nif_not_loaded). +crypto_sign_final_create(_S, _SK) -> erlang:nif_error(nif_not_loaded). +crypto_sign_final_verify(_S, _S, _PK) -> erlang:nif_error(nif_not_loaded). + crypto_box_seal(_Msg, _PK) -> erlang:nif_error(nif_not_loaded). crypto_box_seal_open(_CipherText, _PK, _SK) -> erlang:nif_error(nif_not_loaded). crypto_box_SEALBYTES() -> erlang:nif_error(nif_not_loaded).