aboutsummaryrefslogblamecommitdiffstats
path: root/lib/crypto/c_src/api_ng.c
blob: c4114d1626041cf8c08b822b576450ae19020f76 (plain) (tree)






























































































































































































































                                                                                                               
/*
 * %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 "api_ng.h"
#include "aes.h"
#include "cipher.h"

/*
 * A unified set of functions for encryption/decryption.
 *
 * EXPERIMENTAL!!
 *
 */
ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);



/* Try better error messages in new functions */
#define ERROR_Term(Env, ReasonTerm) enif_make_tuple2((Env), atom_error, (ReasonTerm))
#define ERROR_Str(Env, ReasonString) ERROR_Term((Env), enif_make_string((Env),(ReasonString),(ERL_NIF_LATIN1)))

/* Initializes state for (de)encryption
 */
ERL_NIF_TERM ng_crypto_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Cipher, Key, IVec, Encrypt)  % if no IV for the Cipher, set IVec = <<>>
 */
    ErlNifBinary     key_bin, ivec_bin;
    unsigned char *iv = NULL;
    struct evp_cipher_ctx *ctx;
    const struct cipher_type_t *cipherp;
    const EVP_CIPHER *cipher;
    ERL_NIF_TERM enc_flg_arg, ret;
    int enc;
    unsigned iv_len;

    enc_flg_arg = argv[argc-1];
    if (enc_flg_arg == atom_true)
        enc = 1;
    else if (enc_flg_arg == atom_false)
        enc = 0;
    else if (enc_flg_arg == atom_undefined)
        /* For compat funcs in crypto.erl */
        enc = -1;
    else
        return ERROR_Str(env, "Bad enc flag");

    if (!enif_inspect_binary(env, argv[1], &key_bin))
        return ERROR_Str(env, "Bad key");

    if (!(cipherp = get_cipher_type(argv[0], key_bin.size)))
        return ERROR_Str(env, "Unknown cipher or bad key size");

    if (FORBIDDEN_IN_FIPS(cipherp))
        return enif_raise_exception(env, atom_notsup);

    if (enc == -1)
        return atom_undefined;

    if (!(cipher = cipherp->cipher.p)) {
#if !defined(HAVE_EVP_AES_CTR)
        if (cipherp->flags & AES_CTR_COMPAT)
            return aes_ctr_stream_init_compat(env, argv[1], argv[2]);
        else
#endif
            return enif_raise_exception(env, atom_notsup);
    }

#ifdef HAVE_ECB_IVEC_BUG
    if (cipherp->flags & ECB_BUG_0_9_8L)
        iv_len = 0; /* <= 0.9.8l returns faulty ivec length */
    else
#endif
        iv_len = EVP_CIPHER_iv_length(cipher);

    if (iv_len) {
        if (!enif_inspect_binary(env, argv[2], &ivec_bin))
            return ERROR_Str(env, "Bad iv type");

        if (iv_len != ivec_bin.size)
            return ERROR_Str(env, "Bad iv size");

        iv = ivec_bin.data;
    }

    if ((ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
        return ERROR_Str(env, "Can't allocate resource");

    ctx->ctx = EVP_CIPHER_CTX_new();
    if (! ctx->ctx)
        return ERROR_Str(env, "Can't allocate context");

    if (!EVP_CipherInit_ex(ctx->ctx, cipher, NULL, NULL, NULL, enc)) {
        enif_release_resource(ctx);
        return ERROR_Str(env, "Can't initialize context, step 1");
    }

    if (!EVP_CIPHER_CTX_set_key_length(ctx->ctx, (int)key_bin.size)) {
        enif_release_resource(ctx);
        return ERROR_Str(env, "Can't initialize context, key_length");
    }

    if (EVP_CIPHER_type(cipher) == NID_rc2_cbc) {
        if (key_bin.size > INT_MAX / 8) {
            enif_release_resource(ctx);
            return ERROR_Str(env, "To large rc2_cbc key");
        }
        if (!EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_SET_RC2_KEY_BITS, (int)key_bin.size * 8, NULL)) {
            enif_release_resource(ctx);
            return ERROR_Str(env, "ctrl rc2_cbc key");
        }
    }

    if (!EVP_CipherInit_ex(ctx->ctx, NULL, NULL, key_bin.data, iv, enc)) {
        enif_release_resource(ctx);
        return ERROR_Str(env, "Can't initialize key and/or iv");
    }

    EVP_CIPHER_CTX_set_padding(ctx->ctx, 0);

    ret = enif_make_resource(env, ctx);
    enif_release_resource(ctx);
    return ret;
}

ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Context, Data)
    (Context, Data, IV) */
    struct evp_cipher_ctx *ctx;
    ErlNifBinary   in_data_bin, ivec_bin, out_data_bin;
    int            out_len, block_size;

#if !defined(HAVE_EVP_AES_CTR)
    const ERL_NIF_TERM *state_term;
    int state_arity;

    if (enif_get_tuple(env, argv[0], &state_arity, &state_term) && (state_arity == 4)) {
        return aes_ctr_stream_encrypt_compat(env, argv[0], argv[1]);
    }
#endif

    if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx))
        return ERROR_Str(env, "Bad 1:st arg");
    
    if (!enif_inspect_binary(env, argv[1], &in_data_bin) )
        return ERROR_Str(env, "Bad 2:nd arg");

    /* arg[1] was checked by the caller */
    ASSERT(in_data_bin.size =< INT_MAX);

    block_size = EVP_CIPHER_CTX_block_size(ctx->ctx);
    if (in_data_bin.size % (size_t)block_size != 0)
        return ERROR_Str(env, "Data not a multiple of block size");

    if (argc==3) {
        if (!enif_inspect_iolist_as_binary(env, argv[2], &ivec_bin))
            return ERROR_Str(env, "Not binary IV");
       
        if (ivec_bin.size > INT_MAX)
            return ERROR_Str(env, "Too big IV");
       
        if (!EVP_CipherInit_ex(ctx->ctx, NULL, NULL, NULL, ivec_bin.data, -1))
            return ERROR_Str(env, "Can't set IV");
    }

    if (!enif_alloc_binary((size_t)in_data_bin.size+block_size, &out_data_bin))
        return ERROR_Str(env, "Can't allocate outdata");

    if (!EVP_CipherUpdate(ctx->ctx, out_data_bin.data, &out_len, in_data_bin.data, in_data_bin.size))
        return ERROR_Str(env, "Can't update");

    if (!enif_realloc_binary(&out_data_bin, (size_t)out_len))
        return ERROR_Str(env, "Can't reallocate");

    CONSUME_REDS(env, in_data_bin);
    return enif_make_binary(env, &out_data_bin);
}


ERL_NIF_TERM ng_crypto_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Context, Data)
    (Context, Data, IV) */
    int i;
    ErlNifBinary   data_bin;
    ERL_NIF_TERM new_argv[3];

    ASSERT(argc =< 3);

    if (!enif_inspect_iolist_as_binary(env, argv[1], &data_bin))
        return ERROR_Str(env, "iodata expected as data");

    if (data_bin.size > INT_MAX)
        return ERROR_Str(env, "to long data");

    for (i=0; i<argc; i++) new_argv[i] = argv[i];
    new_argv[1] = enif_make_binary(env, &data_bin);

    /* Run long jobs on a dirty scheduler to not block the current emulator thread */
    if (data_bin.size > MAX_BYTES_TO_NIF) {
        return enif_schedule_nif(env, "ng_crypto_update",
                                 ERL_NIF_DIRTY_JOB_CPU_BOUND,
                                 ng_crypto_update, argc, new_argv);
    }

    return ng_crypto_update(env, argc, new_argv);
}