aboutsummaryrefslogblamecommitdiffstats
path: root/lib/crypto/c_src/api_ng.c
blob: d970f45f94b15d34e7a19bbb104cbbd6128dc02e (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.
 *
 */
ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM ng_crypto_one_shot(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);



/* All nif functions return a valid value or throws an exception */
#define EXCP(Env, Class, Str)  enif_raise_exception((Env), \
                                    enif_make_tuple2((Env), (Class), \
                                                     enif_make_string((Env),(Str),(ERL_NIF_LATIN1)) ))

#define EXCP_NOTSUP(Env, Str) EXCP((Env), atom_notsup, (Str))
#define EXCP_BADARG(Env, Str) EXCP((Env), atom_badarg, (Str))
#define EXCP_ERROR(Env, Str)  EXCP((Env), atom_error, (Str))


#ifdef HAVE_ECB_IVEC_BUG
    /* <= 0.9.8l returns faulty ivec length */
# define GET_IV_LEN(Ciph) ((Ciph)->flags & ECB_BUG_0_9_8L) ? 0 : EVP_CIPHER_iv_length((Ciph)->cipher.p)
#else
# define GET_IV_LEN(Ciph) EVP_CIPHER_iv_length((Ciph)->cipher.p)
#endif

/*************************************************************************/
/* Get the arguments for the initialization of the EVP_CIPHER_CTX. Check */
/* them and initialize that context.                                     */
/*************************************************************************/
static int get_init_args(ErlNifEnv* env,
                         struct evp_cipher_ctx *ctx_res,
                         const ERL_NIF_TERM cipher_arg,
                         const ERL_NIF_TERM key_arg, 
                         const ERL_NIF_TERM ivec_arg,
                         const ERL_NIF_TERM encflg_arg,
                         const struct cipher_type_t **cipherp,
                         ERL_NIF_TERM *return_term)
{
    int ivec_len;
    ErlNifBinary key_bin;
    ErlNifBinary ivec_bin;
    int encflg;                    

    ctx_res->ctx = NULL; /* For testing if *ctx should be freed after errors */

    /* 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
        {
            *return_term = EXCP_BADARG(env, "Bad enc flag");
            goto err;
        }

    /* Fetch the key */
    if (!enif_inspect_iolist_as_binary(env, key_arg, &key_bin))
        {
            *return_term = EXCP_BADARG(env, "Bad key");
            goto err;
        }

    /* Fetch cipher type */
    if (!enif_is_atom(env, cipher_arg))
        {
            *return_term = EXCP_BADARG(env, "Cipher id is not an atom");
            goto err;
        }

    if (!(*cipherp = get_cipher_type(cipher_arg, key_bin.size)))
        {
            *return_term = EXCP_BADARG(env, "Unknown cipher or bad key size for the cipher");
            goto err;
        }

    if (FORBIDDEN_IN_FIPS(*cipherp))
        {
            *return_term = EXCP_NOTSUP(env, "Forbidden in FIPS");
            goto err;
        }
    
    /* Fetch IV */
#if !defined(HAVE_EVP_AES_CTR)
    /* This is code for OpenSSL 0.9.8. Therefore we could accept some ineficient code */
    ctx_res->env = NULL;
    ctx_res->state = atom_undefined;

    if (!((*cipherp)->cipher.p) && (*cipherp)->flags & AES_CTR_COMPAT)
        ivec_len = 16;
    else
#endif
        ivec_len = GET_IV_LEN(*cipherp);

    if (ivec_len && (ivec_arg != atom_undefined)) {
        if (!enif_inspect_iolist_as_binary(env, ivec_arg, &ivec_bin))
            {
                *return_term = EXCP_BADARG(env, "Bad iv type");
                goto err;
            }

        if (ivec_len != ivec_bin.size)
            {
                *return_term = EXCP_BADARG(env, "Bad iv size");
                goto err;
            }
    }

    ctx_res -> iv_len = ivec_len;
    
    if (!((*cipherp)->cipher.p))
        {
#if !defined(HAVE_EVP_AES_CTR)
            if ((*cipherp)->flags & AES_CTR_COMPAT)
                {
                    ERL_NIF_TERM ecount_bin;
                    unsigned char *outp;
                    if ((outp = enif_make_new_binary(env, AES_BLOCK_SIZE, &ecount_bin)) == NULL) {
                        *return_term = EXCP_ERROR(env, "Can't allocate ecount_bin");
                        goto err;
                    }
                    memset(outp, 0, AES_BLOCK_SIZE);
                    
                    ctx_res->env = enif_alloc_env();
                    if (!ctx_res->env) {
                        *return_term = EXCP_ERROR(env, "Can't allocate env");
                        goto err;
                    }
                    ctx_res->state =
                        enif_make_copy(ctx_res->env,
                                       enif_make_tuple4(env, key_arg, ivec_arg, ecount_bin, enif_make_int(env, 0)));
                    goto success;
                } 
#endif
            *return_term = EXCP_NOTSUP(env, "Cipher");
            goto err;
        }

    /* Initialize the EVP_CIPHER_CTX */

    ctx_res->ctx = EVP_CIPHER_CTX_new();
    if (! ctx_res->ctx)
        {
            *return_term = EXCP_ERROR(env, "Can't allocate context");
            goto err;
        }

    if (!EVP_CipherInit_ex(ctx_res->ctx, (*cipherp)->cipher.p, NULL, NULL, NULL, encflg))
        {
            *return_term = EXCP_ERROR(env, "Can't initialize context, step 1");
            goto err;
        }

    if (!EVP_CIPHER_CTX_set_key_length(ctx_res->ctx, (int)key_bin.size))
        {
            *return_term = EXCP_ERROR(env, "Can't initialize context, key_length");
            goto err;
        }


    if (EVP_CIPHER_type((*cipherp)->cipher.p) == NID_rc2_cbc) {
        if (key_bin.size > INT_MAX / 8) {
            *return_term = EXCP_BADARG(env, "To large rc2_cbc key");
            goto err;
        }
        if (!EVP_CIPHER_CTX_ctrl(ctx_res->ctx, EVP_CTRL_SET_RC2_KEY_BITS, (int)key_bin.size * 8, NULL)) {
            *return_term = EXCP_ERROR(env, "ctrl rc2_cbc key");
            goto err;
        }
    }

    if (!EVP_CipherInit_ex(ctx_res->ctx, NULL, NULL, key_bin.data, NULL, -1)) {
        *return_term = EXCP_ERROR(env, "Can't initialize key");
        goto err;
    }

    if (ivec_arg != atom_undefined) {
        if (!EVP_CipherInit_ex(ctx_res->ctx, NULL, NULL, NULL, ivec_bin.data, -1)) {
            *return_term = EXCP_ERROR(env, "Can't initialize iv");
            goto err;
        }
    }

    EVP_CIPHER_CTX_set_padding(ctx_res->ctx, 0);

    *return_term = atom_ok;

#if !defined(HAVE_EVP_AES_CTR)
 success:
#endif
    return 1;

 err:
    if (ctx_res->ctx) EVP_CIPHER_CTX_free(ctx_res->ctx);
    return 0;
}

/*************************************************************************/
/* Get the arguments for the EVP_CipherUpdate function, and call it.     */
/*************************************************************************/

static int get_update_args(ErlNifEnv* env,
                           struct evp_cipher_ctx *ctx_res,
                           const ERL_NIF_TERM indata_arg,
                           ERL_NIF_TERM *return_term)
{
    ErlNifBinary in_data_bin, out_data_bin;
    int out_len, block_size;

    if (!enif_inspect_binary(env, indata_arg, &in_data_bin) )
        {
            *return_term = EXCP_BADARG(env, "Bad 2:nd arg");
            goto err;
        }

    ASSERT(in_data_bin.size <= INT_MAX);

#if !defined(HAVE_EVP_AES_CTR)
    // enif_fprintf(stdout, "%s:%u state = %T\r\n", __FILE__, __LINE__, ctx_res->state);
    if (ctx_res->state != atom_undefined) {
        ERL_NIF_TERM state0, newstate_and_outdata;
        const ERL_NIF_TERM *tuple_argv;
        int tuple_argc;
        
        state0 = enif_make_copy(env, ctx_res->state);
        
        if (enif_get_tuple(env, state0, &tuple_argc, &tuple_argv) && (tuple_argc == 4)) {
            /* A compatibility state term */
            /* encrypt and decrypt is performed by calling the same function */
            newstate_and_outdata = aes_ctr_stream_encrypt_compat(env, state0, indata_arg);

            if (enif_get_tuple(env, newstate_and_outdata, &tuple_argc, &tuple_argv) && (tuple_argc == 2)) {
                /* newstate_and_outdata = {NewState, OutData} */
                ctx_res->state = enif_make_copy(ctx_res->env, tuple_argv[0]);
                /* Return the OutData (from the newstate_and_outdata tuple) only: */
                *return_term = tuple_argv[1];
            }
        }
    } else
#endif
    {
        block_size = EVP_CIPHER_CTX_block_size(ctx_res->ctx);

        if (!enif_alloc_binary((size_t)in_data_bin.size+block_size, &out_data_bin))
            {
                *return_term = EXCP_ERROR(env, "Can't allocate outdata");
                goto err;
            }

        if (!EVP_CipherUpdate(ctx_res->ctx, out_data_bin.data, &out_len, in_data_bin.data, in_data_bin.size))
            {
                *return_term = EXCP_ERROR(env, "Can't update");
                goto err;
            }

        if (!enif_realloc_binary(&out_data_bin, (size_t)out_len))
            {
                *return_term = EXCP_ERROR(env, "Can't reallocate");
                goto err;
            }

        CONSUME_REDS(env, in_data_bin);
        /* return the result text as a binary: */
        *return_term = enif_make_binary(env, &out_data_bin);
    }

    /* success: */
    return 1;

 err:
    return 0;
}

/*************************************************************************/
/* Initialize the state for (de/en)cryption                              */
/*************************************************************************/

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 = <<>>
 */
    struct evp_cipher_ctx *ctx_res = NULL;
    const struct cipher_type_t *cipherp;
    ERL_NIF_TERM ret;
    int encflg;

    if (enif_is_atom(env, argv[0])) {
        if ((ctx_res = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
            return EXCP_ERROR(env, "Can't allocate resource");

        if (!get_init_args(env, ctx_res, argv[0], argv[1], argv[2], argv[argc-1],
                           &cipherp, &ret))
            /* Error msg in &ret */
            goto ret;

        ret = enif_make_resource(env, ctx_res);
        if(ctx_res) enif_release_resource(ctx_res);

    } else if (enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx_res)) {
        /* Fetch the flag telling if we are going to encrypt (=true) or decrypt (=false) */
        if (argv[3] == atom_true)
            encflg = 1;
        else if (argv[3] == atom_false)
            encflg = 0;
        else {
            ret = EXCP_BADARG(env, "Bad enc flag");
            goto ret;
        }
        if (ctx_res->ctx) {
            /* It is *not* a ctx_res for the compatibility handling of non-EVP aes_ctr */
            if (!EVP_CipherInit_ex(ctx_res->ctx, NULL, NULL, NULL, NULL, encflg)) {
                ret = EXCP_ERROR(env, "Can't initialize encflag");
                goto ret;
            }
        }
        ret = argv[0];
    } else {
        ret = EXCP_BADARG(env, "Bad 1:st arg");
        goto ret;
    }

 ret:
    return ret;
}


/*************************************************************************/
/* Encrypt/decrypt                                                       */
/*************************************************************************/


ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Context, Data [, IV]) */
    struct evp_cipher_ctx *ctx_res;
    ERL_NIF_TERM ret;

    if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx_res))
        return EXCP_BADARG(env, "Bad 1:st arg");
    
    if (argc == 3) {
        struct evp_cipher_ctx ctx_res_copy;
        ErlNifBinary ivec_bin;
        memcpy(&ctx_res_copy, ctx_res, sizeof(ctx_res_copy));
        ctx_res = &ctx_res_copy;

        if (!enif_inspect_iolist_as_binary(env, argv[2], &ivec_bin))
            {
                ret = EXCP_BADARG(env, "Bad iv type");
                goto err;
            }

        if (ctx_res_copy.iv_len != ivec_bin.size)
            {
                ret = EXCP_BADARG(env, "Bad iv size");
                printf("Expect %d\r\n", ctx_res_copy.iv_len);
                goto err;
            }
        
#if !defined(HAVE_EVP_AES_CTR)
        // enif_fprintf(stdout, "%s:%u state = %T\r\n", __FILE__, __LINE__, ctx_res->state);
        if ((ctx_res_copy.state != atom_undefined) ) {
            /* replace the iv in state with argv[2] */
            ERL_NIF_TERM state0;
            const ERL_NIF_TERM *tuple_argv;
            int tuple_argc;
            state0 = enif_make_copy(env, ctx_res_copy.state);
            if (enif_get_tuple(env, state0, &tuple_argc, &tuple_argv) && (tuple_argc == 4)) {
                /* A compatibility state term */
                ctx_res_copy.state = enif_make_tuple4(env, tuple_argv[0], argv[2], tuple_argv[2], tuple_argv[3]);
            }
        } else
#endif
            if (!EVP_CipherInit_ex(ctx_res_copy.ctx, NULL, NULL, NULL, ivec_bin.data, -1))
                {
                    ret = EXCP_ERROR(env, "Can't set iv");
                    goto err;
                }
        
        get_update_args(env, &ctx_res_copy, argv[1], &ret);
    } else
        get_update_args(env, ctx_res, argv[1], &ret);

 err:
    return ret; /* Both success and error */
}


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

    ASSERT(argc <= 3);

    if (!enif_inspect_binary(env, argv[1], &data_bin))
        return EXCP_BADARG(env, "expected binary as data");

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

    /* 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, argv);
    }

    return ng_crypto_update(env, argc, argv);
}

/*************************************************************************/
/* One shot                                                              */
/*************************************************************************/

ERL_NIF_TERM ng_crypto_one_shot(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Cipher, Key, IVec, Data, Encrypt) */
    struct evp_cipher_ctx ctx_res;
    const struct cipher_type_t *cipherp;
    ERL_NIF_TERM ret;

    if (!get_init_args(env, &ctx_res, argv[0], argv[1], argv[2], argv[4], &cipherp, &ret))
        goto ret;

    get_update_args(env, &ctx_res, argv[3], &ret);

 ret:
    if (ctx_res.ctx)
        EVP_CIPHER_CTX_free(ctx_res.ctx);
    return ret;
}

ERL_NIF_TERM ng_crypto_one_shot_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Cipher, Key, IVec, Data, Encrypt)  % if no IV for the Cipher, set IVec = <<>>
  */
    ErlNifBinary   data_bin;

    ASSERT(argc == 5);

    if (!enif_inspect_binary(env, argv[3], &data_bin))
        return EXCP_BADARG(env, "expected binary as data");

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

    /* 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_one_shot",
                                 ERL_NIF_DIRTY_JOB_CPU_BOUND,
                                 ng_crypto_one_shot, argc, argv);
    }

    return ng_crypto_one_shot(env, argc, argv);
}