Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: Break out aead_one_time to 2 functions #9289

Merged
merged 1 commit into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
312 changes: 240 additions & 72 deletions lib/crypto/c_src/aead.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,92 +21,246 @@
#include "aead.h"
#include "aes.h"
#include "cipher.h"
#include "info.h"


ErlNifResourceType* aead_cipher_ctx_rtype;

ERL_NIF_TERM aead_cipher_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
struct aead_cipher_ctx {
const struct cipher_type_t *cipherp;
EVP_CIPHER_CTX *ctx;

ERL_NIF_TERM key;

int encflg;
unsigned int tag_len;
ErlNifEnv *env;
};

static void aead_cipher_ctx_dtor(ErlNifEnv* env, struct aead_cipher_ctx* ctx) {
enif_free_env(ctx->env);
if (ctx->ctx)
EVP_CIPHER_CTX_free(ctx->ctx);

return;
}

int init_aead_cipher_ctx(ErlNifEnv *env, ErlNifBinary* rt_buf) {
aead_cipher_ctx_rtype = enif_open_resource_type(env, NULL,
resource_name("AEAD_CIPHER_CTX", rt_buf),
(ErlNifResourceDtor*) aead_cipher_ctx_dtor,
ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
NULL);
if (aead_cipher_ctx_rtype == NULL)
goto err;

return 1;

err:
PRINTF_ERR0("CRYPTO: Could not open resource type 'AEAD_CIPHER_CTX'");
return 0;
}

ERL_NIF_TERM aead_cipher_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/*
(Type,Key,Iv,AAD,In,TagLen,true)
(Type,Key,Iv,AAD,In,Tag,false)
(Type,Key,TagLen,DoEncode)
*/
#if defined(HAVE_AEAD)
const struct cipher_type_t *cipherp;
EVP_CIPHER_CTX *ctx = NULL;
const EVP_CIPHER *cipher = NULL;
ErlNifBinary key, iv, aad, in, tag;
unsigned int tag_len;
unsigned char *outp, *tagp, *tag_data;
ERL_NIF_TERM type, out, out_tag, ret, encflg_arg;
int len, encflg;
struct aead_cipher_ctx *ctx_res = NULL;
ERL_NIF_TERM ret, encflg_arg, type;
ErlNifBinary key;

if ((ctx_res = enif_alloc_resource(aead_cipher_ctx_rtype, sizeof(struct aead_cipher_ctx))) == NULL)
return EXCP_ERROR(env, "Can't allocate resource");

encflg_arg = argv[6];
ctx_res->env = enif_alloc_env();
encflg_arg = argv[3];

/* Fetch the flag telling if we are going to encrypt (=true) or decrypt (=false) */
if (encflg_arg == atom_true)
encflg = 1;
ctx_res->encflg = 1;
else if (encflg_arg == atom_false)
encflg = 0;
else if (encflg_arg == atom_undefined)
/* For compat funcs in crypto.erl */
encflg = -1;
ctx_res->encflg = 0;
else
{
ret = EXCP_BADARG_N(env, 6, "Bad enc flag");
ret = EXCP_BADARG_N(env, 3, "Bad enc flag");
goto done;
}

type = argv[0];

if (!enif_is_atom(env, type))
{ret = EXCP_BADARG_N(env, 0, "non-atom cipher type"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[1], &key))

if (!enif_inspect_binary(env, argv[1], &key))
{ret = EXCP_BADARG_N(env, 1, "non-binary key"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[2], &iv))
{ret = EXCP_BADARG_N(env, 2, "non-binary iv"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[3], &in))
{ret = EXCP_BADARG_N(env, 3, "non-binary text"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[4], &aad))
{ret = EXCP_BADARG_N(env, 4, "non-binary AAD"); goto done;}

if (encflg) {
if (!enif_get_uint(env, argv[5], &tag_len))
{ret = EXCP_BADARG_N(env, 5, "Bad Tag length"); goto done;}
tag_data = NULL;
} else {
if (!enif_inspect_iolist_as_binary(env, argv[5], &tag))
{ret = EXCP_BADARG_N(env, 5, "non-binary Tag"); goto done;}
tag_len = tag.size;
tag_data = tag.data;
}
ctx_res->key = enif_make_copy(ctx_res->env, argv[1]);

if (tag_len > INT_MAX
|| key.size > INT_MAX
|| iv.size > INT_MAX
|| in.size > INT_MAX
|| aad.size > INT_MAX)
{ret = EXCP_BADARG_N(env, 5, "binary too long"); goto done;}
if (!enif_get_uint(env, argv[2], &ctx_res->tag_len))
{ret = EXCP_BADARG_N(env, 2, "Bad Tag length"); goto done;}

if ((cipherp = get_cipher_type(type, key.size)) == NULL)
if (ctx_res->tag_len > INT_MAX
|| key.size > INT_MAX)
{ret = EXCP_BADARG_N(env, 1, "key or tag too long"); goto done;}

if ((ctx_res->cipherp = get_cipher_type(type, key.size)) == NULL)
{ret = EXCP_BADARG_N(env, 0, "Unknown cipher or invalid key size"); goto done;}
if (cipherp->flags & NON_EVP_CIPHER)
if (ctx_res->cipherp->flags & NON_EVP_CIPHER)
{ret = EXCP_BADARG_N(env, 0, "Bad cipher"); goto done;}
if (! (cipherp->flags & AEAD_CIPHER) )
if (! (ctx_res->cipherp->flags & AEAD_CIPHER) )
{ret = EXCP_BADARG_N(env, 0, "Not aead cipher"); goto done;}
if (CIPHER_FORBIDDEN_IN_FIPS(cipherp))
if (CIPHER_FORBIDDEN_IN_FIPS(ctx_res->cipherp))
{ret = EXCP_NOTSUP_N(env, 0, "Forbidden in FIPS"); goto done;}
if ((cipher = cipherp->cipher.p) == NULL)
{ret = EXCP_NOTSUP_N(env, 0, "The cipher is not supported in this libcrypto version"); goto done;}

#if defined(HAVE_GCM_EVP_DECRYPT_BUG)
if ( !encflg && (cipherp->flags & GCM_MODE))
return aes_gcm_decrypt_NO_EVP(env, argc, argv);
if ( !ctx_res->encflg && (ctx_res->cipherp->flags & GCM_MODE)) {
{ret = EXCP_NOTSUP_N(env, 0, "HAVE_GCM_EVP_DECRYPT_BUG with init aead not supported, update ssl version"); goto done;}
}
#endif

if ((ctx = EVP_CIPHER_CTX_new()) == NULL)
{ret = EXCP_ERROR(env, "Can't allocate ctx"); goto done;}
if (ctx_res->cipherp->cipher.p == NULL)
{ret = EXCP_NOTSUP_N(env, 0, "The cipher is not supported in this libcrypto version"); goto done;}

if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encflg) != 1)
if ((ctx_res->ctx = EVP_CIPHER_CTX_new()) == NULL)
{ret = EXCP_ERROR(env, "Can't allocate ctx"); goto done;}
if (EVP_CipherInit_ex(ctx_res->ctx, ctx_res->cipherp->cipher.p, NULL, NULL, NULL, ctx_res->encflg) != 1)
{ret = EXCP_ERROR(env, "CipherInit failed"); goto done;}

ret = enif_make_resource(env, ctx_res);

done:
if(ctx_res) enif_release_resource(ctx_res);
return ret;
#else
return EXCP_NOTSUP_N(env, 0, "Unsupported Cipher");
#endif
}

ERL_NIF_TERM aead_cipher_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
#if defined(HAVE_AEAD)
const struct cipher_type_t *cipherp;
EVP_CIPHER_CTX *ctx = NULL;
const EVP_CIPHER *cipher = NULL;
ErlNifBinary key, iv, aad, in, tag;
unsigned int tag_len;
unsigned char *outp, *tagp, *tag_data, *in_data;
ERL_NIF_TERM type, out, out_tag, ret, encflg_arg;
int len, encflg, in_len;

if(argc == 7) {
/*
(Type,Key,Iv,In,AAD,TagLen,true)
(Type,Key,Iv,In,AAD,Tag,false)
*/
encflg_arg = argv[6];

/* Fetch the flag telling if we are going to encrypt (=true) or decrypt (=false) */
if (encflg_arg == atom_true)
encflg = 1;
else if (encflg_arg == atom_false)
encflg = 0;
else if (encflg_arg == atom_undefined)
/* For compat funcs in crypto.erl */
encflg = -1;
else
{
ret = EXCP_BADARG_N(env, 6, "Bad enc flag");
goto done;
}

type = argv[0];

if (!enif_is_atom(env, type))
{ret = EXCP_BADARG_N(env, 0, "non-atom cipher type"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[1], &key))
{ret = EXCP_BADARG_N(env, 1, "non-binary key"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[2], &iv))
{ret = EXCP_BADARG_N(env, 2, "non-binary iv"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[3], &in))
{ret = EXCP_BADARG_N(env, 3, "non-binary text"); goto done;}
in_data = in.data;
in_len = in.size;
if (!enif_inspect_iolist_as_binary(env, argv[4], &aad))
{ret = EXCP_BADARG_N(env, 4, "non-binary AAD"); goto done;}

if (encflg) {
if (!enif_get_uint(env, argv[5], &tag_len))
{ret = EXCP_BADARG_N(env, 5, "Bad Tag length"); goto done;}
tag_data = NULL;
} else {
if (!enif_inspect_iolist_as_binary(env, argv[5], &tag))
{ret = EXCP_BADARG_N(env, 5, "non-binary Tag"); goto done;}
tag_len = tag.size;
tag_data = tag.data;
}

if (tag_len > INT_MAX
|| key.size > INT_MAX
|| iv.size > INT_MAX
|| in.size > INT_MAX
|| aad.size > INT_MAX)
{ret = EXCP_BADARG_N(env, 5, "binary too long"); goto done;}

if ((cipherp = get_cipher_type(type, key.size)) == NULL)
{ret = EXCP_BADARG_N(env, 0, "Unknown cipher or invalid key size"); goto done;}
if (cipherp->flags & NON_EVP_CIPHER)
{ret = EXCP_BADARG_N(env, 0, "Bad cipher"); goto done;}
if (! (cipherp->flags & AEAD_CIPHER) )
{ret = EXCP_BADARG_N(env, 0, "Not aead cipher"); goto done;}
if (CIPHER_FORBIDDEN_IN_FIPS(cipherp))
{ret = EXCP_NOTSUP_N(env, 0, "Forbidden in FIPS"); goto done;}

#if defined(HAVE_GCM_EVP_DECRYPT_BUG)
if ( !encflg && (cipherp->flags & GCM_MODE)) {
return aes_gcm_decrypt_NO_EVP(env, argc, argv);
}
#endif
if ((cipher = cipherp->cipher.p) == NULL)
{ret = EXCP_NOTSUP_N(env, 0, "The cipher is not supported in this libcrypto version"); goto done;}

if ((ctx = EVP_CIPHER_CTX_new()) == NULL)
{ret = EXCP_ERROR(env, "Can't allocate ctx"); goto done;}
if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encflg) != 1)
{ret = EXCP_ERROR(env, "CipherInit failed"); goto done;}

} else {
/* argc = 4 {state, IV, InData, AAD } */
struct aead_cipher_ctx *ctx_res = NULL;
if (!enif_get_resource(env, argv[0], aead_cipher_ctx_rtype, (void**)&ctx_res))
{ret = EXCP_BADARG_N(env, 0, "Bad State"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[1], &iv))
{ret = EXCP_BADARG_N(env, 1, "non-binary iv"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[2], &in))
{ret = EXCP_BADARG_N(env, 2, "non-binary text"); goto done;}
in_data = in.data;
in_len = in.size;

if (!enif_inspect_iolist_as_binary(env, argv[3], &aad))
{ret = EXCP_BADARG_N(env, 3, "non-binary AAD"); goto done;}

if (!enif_inspect_binary(env, ctx_res->key, &key))
{ret = EXCP_BADARG_N(env, 0, "Bad State key"); goto done;}

encflg = ctx_res->encflg;

if(ctx_res->encflg) {
tag_len = ctx_res->tag_len;
tag_data = NULL;
} else {
tag_len = ctx_res->tag_len;
in_len = in_len - tag_len;
if (in_len < 0)
{ret = EXCP_ERROR(env, "Bad in data"); goto done;}
tag_data = in_data + in_len;
}

cipherp = ctx_res->cipherp;
cipher = cipherp->cipher.p;
ctx = ctx_res->ctx;
}
/* Init done */

if (EVP_CIPHER_CTX_ctrl(ctx, cipherp->extra.aead.ctx_ctrl_set_ivlen, (int)iv.size, NULL) != 1)
{ret = EXCP_BADARG_N(env, 2, "Bad IV length"); goto done;}

Expand All @@ -116,7 +270,7 @@ ERL_NIF_TERM aead_cipher_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
{ret = EXCP_BADARG_N(env, 5, "Can't set tag"); goto done;}
if (EVP_CipherInit_ex(ctx, NULL, NULL, key.data, iv.data, -1) != 1)
{ret = EXCP_ERROR(env, "Can't set key or iv"); goto done;}
if (EVP_CipherUpdate(ctx, NULL, &len, NULL, (int)in.size) != 1)
if (EVP_CipherUpdate(ctx, NULL, &len, NULL, (int)in_len) != 1)
{ret = EXCP_ERROR(env, "Can't set text size"); goto done;}
} else
#endif
Expand All @@ -131,9 +285,15 @@ ERL_NIF_TERM aead_cipher_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
{ret = EXCP_BADARG_N(env, 4, "Can't set AAD"); goto done;}

/* Set the plain text and get the crypto text (or vice versa :) ) */
if ((outp = enif_make_new_binary(env, in.size, &out)) == NULL)
if (encflg && argc == 4)
len = in_len+tag_len;
else
len = in_len;

if ((outp = enif_make_new_binary(env, len, &out)) == NULL)
{ret = EXCP_ERROR(env, "Can't make 'Out' binary"); goto done;}
if (EVP_CipherUpdate(ctx, outp, &len, in.data, (int)in.size) != 1)

if (EVP_CipherUpdate(ctx, outp, &len, in_data, in_len) != 1)
{
if (encflg)
ret = EXCP_BADARG_N(env, 3, "Can't set in-text");
Expand All @@ -142,28 +302,37 @@ ERL_NIF_TERM aead_cipher_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
ret = atom_error;
goto done;
}

if (encflg)
{
/* Finalize the encrypted text */
if (EVP_CipherFinal_ex(ctx, outp, &len) != 1)
{ret = EXCP_ERROR(env, "Encrypt error"); goto done;}

/* Get the tag */
if ((tagp = enif_make_new_binary(env, tag_len, &out_tag)) == NULL)
{ret = EXCP_ERROR(env, "Can't make 'Out' binary"); goto done;}
if (EVP_CIPHER_CTX_ctrl(ctx, cipherp->extra.aead.ctx_ctrl_get_tag, (int)tag_len, tagp) != 1)
{ret = EXCP_ERROR(env, "Can't get Tag"); goto done;}

/* Make the return value (the tuple with binary crypto text and the tag) */
ret = enif_make_tuple2(env, out, out_tag);
if (argc == 7) {
/* Finalize the encrypted text */
if (EVP_CipherFinal_ex(ctx, outp, &len) != 1)
{ret = EXCP_ERROR(env, "Encrypt error"); goto done;}

/* Get the tag */
if ((tagp = enif_make_new_binary(env, tag_len, &out_tag)) == NULL)
{ret = EXCP_ERROR(env, "Can't make 'Out' binary"); goto done;}
if (EVP_CIPHER_CTX_ctrl(ctx, cipherp->extra.aead.ctx_ctrl_get_tag, (int)tag_len, tagp) != 1)
{ret = EXCP_ERROR(env, "Can't get Tag"); goto done;}

/* Make the return value (the tuple with binary crypto text and the tag) */
ret = enif_make_tuple2(env, out, out_tag);
} else {
if (EVP_CipherFinal_ex(ctx, outp, &len) != 1)
{ret = EXCP_ERROR(env, "Encrypt error"); goto done;}
/* Add tag to output end */
tagp = outp + in_len;
if (EVP_CIPHER_CTX_ctrl(ctx, cipherp->extra.aead.ctx_ctrl_get_tag, (int)tag_len, tagp) != 1)
{ret = EXCP_ERROR(env, "Can't get Tag"); goto done;}
ret = out;
}
}
else /* Decrypting. The plain text is already pointed to by 'out' */
{
#if defined(HAVE_GCM) || defined(HAVE_CHACHA20_POLY1305)
/* Check the Tag before returning. CCM_MODE does this previously. */
if (!(cipherp->flags & CCM_MODE)) { /* That is, CHACHA20_POLY1305 or GCM_MODE */
if (EVP_CIPHER_CTX_ctrl(ctx, cipherp->extra.aead.ctx_ctrl_set_tag, (int)tag_len, tag.data) != 1)
if (EVP_CIPHER_CTX_ctrl(ctx, cipherp->extra.aead.ctx_ctrl_set_tag, (int)tag_len, tag_data) != 1)
/* Decrypt error */
{ret = atom_error; goto done;}
/* CCM dislikes EVP_DecryptFinal_ex on decrypting for pre 1.1.1, so we do it only here */
Expand All @@ -179,7 +348,7 @@ ERL_NIF_TERM aead_cipher_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
CONSUME_REDS(env, in);

done:
if (ctx)
if (ctx && argc == 7 )
EVP_CIPHER_CTX_free(ctx);
return ret;

Expand All @@ -188,4 +357,3 @@ ERL_NIF_TERM aead_cipher_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
#endif
}


Loading
Loading