/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2010-2018. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* %CopyrightEnd%
*/
#include "aead.h"
#include "aes.h"
#include "cipher.h"
ERL_NIF_TERM aead_cipher(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/*
(Type,Key,Iv,AAD,In,TagLen,true)
(Type,Key,Iv,AAD,In,Tag,false)
*/
#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;
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(env, "Bad enc flag");
goto done;
}
type = argv[0];
if (!enif_is_atom(env, type))
{ret = EXCP_BADARG(env, "non-atom cipher type"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[1], &key))
{ret = EXCP_BADARG(env, "non-binary key"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[2], &iv))
{ret = EXCP_BADARG(env, "non-binary iv"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[3], &in))
{ret = EXCP_BADARG(env, "non-binary text"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[4], &aad))
{ret = EXCP_BADARG(env, "non-binary AAD"); goto done;}
if (encflg) {
if (!enif_get_uint(env, argv[5], &tag_len))
{ret = EXCP_BADARG(env, "Bad Tag length"); goto done;}
tag_data = NULL;
} else {
if (!enif_inspect_iolist_as_binary(env, argv[5], &tag))
{ret = EXCP_BADARG(env, "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(env, "binary too long"); goto done;}
if ((cipherp = get_cipher_type(type, key.size)) == NULL)
{ret = EXCP_BADARG(env, "Unknown cipher"); goto done;}
if (cipherp->flags & NON_EVP_CIPHER)
{ret = EXCP_BADARG(env, "Bad cipher"); goto done;}
if (! (cipherp->flags & AEAD_CIPHER) )
{ret = EXCP_BADARG(env, "Not aead cipher"); goto done;}
if ((cipher = cipherp->cipher.p) == NULL)
{ret = EXCP_NOTSUP(env, "Cipher 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);
#endif
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;}
if (EVP_CIPHER_CTX_ctrl(ctx, cipherp->extra.aead.ctx_ctrl_set_ivlen, (int)iv.size, NULL) != 1)
{ret = EXCP_BADARG(env, "Bad IV length"); goto done;}
#if defined(HAVE_CCM)
if (cipherp->flags & CCM_MODE) {
if (EVP_CIPHER_CTX_ctrl(ctx, cipherp->extra.aead.ctx_ctrl_set_tag, (int)tag_len, tag_data) != 1)
{ret = EXCP_BADARG(env, "Can't set tag"); goto done;}
if (EVP_CipherInit_ex(ctx, NULL, NULL, key.data, iv.data, -1) != 1)
{ret = EXCP_BADARG(env, "Can't set key or iv"); goto done;}
if (EVP_CipherUpdate(ctx, NULL, &len, NULL, (int)in.size) != 1)
{ret = EXCP_BADARG(env, "Can't set text size"); goto done;}
} else
#endif
{
if (EVP_CipherInit_ex(ctx, NULL, NULL, key.data, iv.data, -1) != 1)
{ret = EXCP_BADARG(env, "Can't set key or iv"); goto done;}
}
if (EVP_CipherUpdate(ctx, NULL, &len, aad.data, (int)aad.size) != 1)
{ret = EXCP_BADARG(env, "Can't set AAD"); goto done;}
if ((outp = enif_make_new_binary(env, in.size, &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 (encflg)
ret = EXCP_BADARG(env, "Can't set in-text");
else
/* Decrypt error */
ret = atom_error;
goto done;
}
if (encflg)
{
if (EVP_CipherFinal_ex(ctx, outp/*+len*/, &len) != 1)
{ret = EXCP_ERROR(env, "Encrypt error"); goto done;}
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;}
ret = enif_make_tuple2(env, out, out_tag);
}
else
{
#if defined(HAVE_GCM)
if (cipherp->flags & GCM_MODE) {
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;}
if (EVP_DecryptFinal_ex(ctx, outp+len, &len) != 1)
/* Decrypt error */
{ret = atom_error; goto done;}
}
#endif
ret = out;
}
CONSUME_REDS(env, in);
done:
if (ctx)
EVP_CIPHER_CTX_free(ctx);
return ret;
#else
return EXCP_NOTSUP(env, "Unsupported Cipher");
#endif
}