aboutsummaryrefslogblamecommitdiffstats
path: root/lib/crypto/c_src/pkey.c
blob: d53d91c25b5d0d174c70b676c0bd405ef323c256 (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 "pkey.h"
#include "bn.h"
#include "digest.h"
#include "dss.h"
#include "ec.h"
#include "eddsa.h"
#include "engine.h"
#include "rsa.h"

#define PKEY_BADARG -1
#define PKEY_NOTSUP 0
#define PKEY_OK 1

typedef struct PKeyCryptOptions {
    const EVP_MD *rsa_mgf1_md;
    ErlNifBinary rsa_oaep_label;
    const EVP_MD *rsa_oaep_md;
    int rsa_padding;
    const EVP_MD *signature_md;
} PKeyCryptOptions;

typedef struct PKeySignOptions {
    const EVP_MD *rsa_mgf1_md;
    int rsa_padding;
    int rsa_pss_saltlen;
} PKeySignOptions;


static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type,
				const EVP_MD **md);
static int get_pkey_sign_digest(ErlNifEnv *env, ERL_NIF_TERM algorithm,
				ERL_NIF_TERM type, ERL_NIF_TERM data,
				unsigned char *md_value, const EVP_MD **mdp,
				unsigned char **tbsp, size_t *tbslenp);
static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
                                 const EVP_MD *md, PKeySignOptions *opt);
static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, EVP_PKEY **pkey);
static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key,
			       EVP_PKEY **pkey);
static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
				  PKeyCryptOptions *opt);
#ifdef HAVE_RSA_SSLV23_PADDING
static size_t size_of_RSA(EVP_PKEY *pkey);
#endif

static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type,
				const EVP_MD **md)
{
    struct digest_type_t *digp = NULL;
    *md = NULL;

    if (type == atom_none && algorithm == atom_rsa)
        return PKEY_OK;
    if (algorithm == atom_eddsa) {
#ifdef HAVE_EDDSA
        if (!FIPS_mode()) return PKEY_OK;
#else
        return PKEY_NOTSUP;
#endif
    }
    if ((digp = get_digest_type(type)) == NULL)
        return PKEY_BADARG;
    if (digp->md.p == NULL)
        return PKEY_NOTSUP;

    *md = digp->md.p;
    return PKEY_OK;
}

static int get_pkey_sign_digest(ErlNifEnv *env, ERL_NIF_TERM algorithm,
				ERL_NIF_TERM type, ERL_NIF_TERM data,
				unsigned char *md_value, const EVP_MD **mdp,
				unsigned char **tbsp, size_t *tbslenp)
{
    int i, ret;
    const ERL_NIF_TERM *tpl_terms;
    int tpl_arity;
    ErlNifBinary tbs_bin;
    EVP_MD_CTX *mdctx = NULL;
    const EVP_MD *md;
    unsigned char *tbs;
    size_t tbslen;
    unsigned int tbsleni;

    md = *mdp;
    tbs = *tbsp;
    tbslen = *tbslenp;

    if ((i = get_pkey_digest_type(env, algorithm, type, &md)) != PKEY_OK)
        return i;

    if (enif_get_tuple(env, data, &tpl_arity, &tpl_terms)) {
        if (tpl_arity != 2)
            goto bad_arg;
        if (tpl_terms[0] != atom_digest)
            goto bad_arg;
        if (!enif_inspect_iolist_as_binary(env, tpl_terms[1], &tbs_bin))
            goto bad_arg;
        if (tbs_bin.size > INT_MAX)
            goto bad_arg;
        if (md != NULL) {
            if ((int)tbs_bin.size != EVP_MD_size(md))
                goto bad_arg;
        }

        /* We have a digest (= hashed text) in tbs_bin */
	tbs = tbs_bin.data;
	tbslen = tbs_bin.size;
    } else if (md == NULL) {
        if (!enif_inspect_iolist_as_binary(env, data, &tbs_bin))
            goto bad_arg;

        /* md == NULL, that is no hashing because DigestType argument was atom_none */
	tbs = tbs_bin.data;
	tbslen = tbs_bin.size;
    } else {
        if (!enif_inspect_iolist_as_binary(env, data, &tbs_bin))
            goto bad_arg;

        /* We have the cleartext in tbs_bin and the hash algo info in md */
	tbs = md_value;

        if ((mdctx = EVP_MD_CTX_create()) == NULL)
            goto err;

        /* Looks well, now hash the plain text into a digest according to md */
        if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
            goto err;
        if (EVP_DigestUpdate(mdctx, tbs_bin.data, tbs_bin.size) != 1)
            goto err;
        if (EVP_DigestFinal_ex(mdctx, tbs, &tbsleni) != 1)
            goto err;

        tbslen = (size_t)tbsleni;
    }

    *mdp = md;
    *tbsp = tbs;
    *tbslenp = tbslen;

    ret = PKEY_OK;
    goto done;

 bad_arg:
 err:
    ret = PKEY_BADARG;

 done:
    if (mdctx)
        EVP_MD_CTX_destroy(mdctx);
    return ret;
}

static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
                                 const EVP_MD *md, PKeySignOptions *opt)
{
    ERL_NIF_TERM head, tail;
    const ERL_NIF_TERM *tpl_terms;
    int tpl_arity;
    const EVP_MD *opt_md;

    if (!enif_is_list(env, options))
        goto bad_arg;

    /* defaults */
    if (algorithm == atom_rsa) {
	opt->rsa_mgf1_md = NULL;
	opt->rsa_padding = RSA_PKCS1_PADDING;
	opt->rsa_pss_saltlen = -2;
    }

    if (enif_is_empty_list(env, options))
	return PKEY_OK;

    if (algorithm != atom_rsa)
        goto bad_arg;

    tail = options;
    while (enif_get_list_cell(env, tail, &head, &tail)) {
        if (!enif_get_tuple(env, head, &tpl_arity, &tpl_terms))
            goto bad_arg;
        if (tpl_arity != 2)
            goto bad_arg;

        if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) {
            int result;

            result = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
            if (result != PKEY_OK)
                return result;

            opt->rsa_mgf1_md = opt_md;

        } else if (tpl_terms[0] == atom_rsa_padding) {
            if (tpl_terms[1] == atom_rsa_pkcs1_padding) {
                opt->rsa_padding = RSA_PKCS1_PADDING;

            } else if (tpl_terms[1] == atom_rsa_pkcs1_pss_padding) {
#ifdef HAVE_RSA_PKCS1_PSS_PADDING
                opt->rsa_padding = RSA_PKCS1_PSS_PADDING;
                if (opt->rsa_mgf1_md == NULL)
                    opt->rsa_mgf1_md = md;
#else
                return PKEY_NOTSUP;
#endif

            } else if (tpl_terms[1] == atom_rsa_x931_padding) {
                opt->rsa_padding = RSA_X931_PADDING;

            } else if (tpl_terms[1] == atom_rsa_no_padding) {
                opt->rsa_padding = RSA_NO_PADDING;

            } else {
                goto bad_arg;
            }

        } else if (tpl_terms[0] == atom_rsa_pss_saltlen) {
            if (!enif_get_int(env, tpl_terms[1], &(opt->rsa_pss_saltlen)))
                goto bad_arg;
            if (opt->rsa_pss_saltlen < -2)
                goto bad_arg;

        } else {
            goto bad_arg;
        }
    }

    return PKEY_OK;

 bad_arg:
    return PKEY_BADARG;
}

static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, EVP_PKEY **pkey)
{
    EVP_PKEY *result = NULL;
    RSA *rsa = NULL;
#ifdef HAVE_DSA
    DSA *dsa = NULL;
#endif
#if defined(HAVE_EC)
    EC_KEY *ec = NULL;
#endif
    char *id = NULL;
    char *password = NULL;

    if (enif_is_map(env, key)) {
#ifdef HAS_ENGINE_SUPPORT
        /* Use key stored in engine */
        ENGINE *e;

        if (!get_engine_and_key_id(env, key, &id, &e))
            goto err;

        password = get_key_password(env, key);
        result = ENGINE_load_private_key(e, id, NULL, password);

#else
        return PKEY_BADARG;
#endif
    } else if (algorithm == atom_rsa) {
        if ((rsa = RSA_new()) == NULL)
            goto err;

        if (!get_rsa_private_key(env, key, rsa))
            goto err;
        if ((result = EVP_PKEY_new()) == NULL)
            goto err;
        if (EVP_PKEY_assign_RSA(result, rsa) != 1)
            goto err;
        /* On success, result owns rsa */
        rsa = NULL;

    } else if (algorithm == atom_ecdsa) {
#if defined(HAVE_EC)
	const ERL_NIF_TERM *tpl_terms;
	int tpl_arity;

        if (!enif_get_tuple(env, key, &tpl_arity, &tpl_terms))
            goto err;
        if (tpl_arity != 2)
            goto err;
        if (!enif_is_tuple(env, tpl_terms[0]))
            goto err;
        if (!enif_is_binary(env, tpl_terms[1]))
            goto err;
        if (!get_ec_key(env, tpl_terms[0], tpl_terms[1], atom_undefined, &ec))
            goto err;

        if ((result = EVP_PKEY_new()) == NULL)
            goto err;
        if (EVP_PKEY_assign_EC_KEY(result, ec) != 1)
            goto err;
        /* On success, result owns ec */
        ec = NULL;

#else
	return PKEY_NOTSUP;
#endif
    } else if (algorithm == atom_eddsa) {
#ifdef HAVE_EDDSA
        if (!FIPS_mode())
            {
                if (!get_eddsa_key(env, 0, key, &result))
                    goto err;
                else
                    goto done; // Not nice....
            }
#else
            return PKEY_NOTSUP;
#endif
    } else if (algorithm == atom_dss) {
#ifdef HAVE_DSA
        if ((dsa = DSA_new()) == NULL)
            goto err;
        if (!get_dss_private_key(env, key, dsa))
            goto err;

        if ((result = EVP_PKEY_new()) == NULL)
            goto err;
        if (EVP_PKEY_assign_DSA(result, dsa) != 1)
            goto err;
        /* On success, result owns dsa */
        dsa = NULL;

    } else {
#endif
	return PKEY_BADARG;
    }
    goto done;

 err:
    if (result)
        EVP_PKEY_free(result);
    result = NULL;

 done:
    if (password)
        enif_free(password);
    if (id)
        enif_free(id);
    if (rsa)
        RSA_free(rsa);
#ifdef HAVE_DSA
    if (dsa)
        DSA_free(dsa);
#endif
#ifdef HAVE_EC
    if (ec)
        EC_KEY_free(ec);
#endif

    if (result == NULL) {
        return PKEY_BADARG;
    } else {
        *pkey = result;
        return PKEY_OK;
    }
}

static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key,
			       EVP_PKEY **pkey)
{
    EVP_PKEY *result = NULL;
    RSA *rsa = NULL;
#ifdef HAVE_DSA
    DSA *dsa = NULL;
#endif
#if defined(HAVE_EC)
    EC_KEY *ec = NULL;
#endif
    char *id = NULL;
    char *password = NULL;

    if (enif_is_map(env, key)) {
#ifdef HAS_ENGINE_SUPPORT
        /* Use key stored in engine */
        ENGINE *e;

        if (!get_engine_and_key_id(env, key, &id, &e))
            goto err;

        password = get_key_password(env, key);
        result = ENGINE_load_public_key(e, id, NULL, password);

#else
        return PKEY_BADARG;
#endif
    } else  if (algorithm == atom_rsa) {
        if ((rsa = RSA_new()) == NULL)
            goto err;

        if (!get_rsa_public_key(env, key, rsa))
            goto err;

        if ((result = EVP_PKEY_new()) == NULL)
            goto err;
        if (EVP_PKEY_assign_RSA(result, rsa) != 1)
            goto err;
        /* On success, result owns rsa */
        rsa = NULL;

    } else if (algorithm == atom_ecdsa) {
#if defined(HAVE_EC)
	const ERL_NIF_TERM *tpl_terms;
	int tpl_arity;

        if (!enif_get_tuple(env, key, &tpl_arity, &tpl_terms))
            goto err;
        if (tpl_arity != 2)
            goto err;
        if (!enif_is_tuple(env, tpl_terms[0]))
            goto err;
        if (!enif_is_binary(env, tpl_terms[1]))
            goto err;
        if (!get_ec_key(env, tpl_terms[0], atom_undefined, tpl_terms[1], &ec))
            goto err;

        if ((result = EVP_PKEY_new()) == NULL)
            goto err;

        if (EVP_PKEY_assign_EC_KEY(result, ec) != 1)
            goto err;
        /* On success, result owns ec */
        ec = NULL;

#else
	return PKEY_NOTSUP;
#endif
    } else if (algorithm == atom_eddsa) {
#ifdef HAVE_EDDSA
        if (!FIPS_mode()) {
            if (!get_eddsa_key(env, 1, key, &result))
                goto err;
        }
#else
	return PKEY_NOTSUP;
#endif
    } else if (algorithm == atom_dss) {
#ifdef HAVE_DSA
        if ((dsa = DSA_new()) == NULL)
            goto err;

        if (!get_dss_public_key(env, key, dsa))
            goto err;

        if ((result = EVP_PKEY_new()) == NULL)
            goto err;
        if (EVP_PKEY_assign_DSA(result, dsa) != 1)
            goto err;
        /* On success, result owns dsa */
        dsa = NULL;
#else
        return PKEY_NOTSUP;
#endif
    } else {
	return PKEY_BADARG;
    }

    goto done;

 err:
    if (result)
        EVP_PKEY_free(result);
    result = NULL;

 done:
    if (password)
        enif_free(password);
    if (id)
        enif_free(id);
    if (rsa)
        RSA_free(rsa);
#ifdef HAVE_DSA
    if (dsa)
        DSA_free(dsa);
#endif
#ifdef HAVE_EC
    if (ec)
        EC_KEY_free(ec);
#endif

    if (result == NULL) {
        return PKEY_BADARG;
    } else {
        *pkey = result;
        return PKEY_OK;
    }
}

ERL_NIF_TERM pkey_sign_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{/* (Algorithm, Type, Data|{digest,Digest}, Key|#{}, Options) */
    int i;
    int sig_bin_alloc = 0;
    ERL_NIF_TERM ret;
    const EVP_MD *md = NULL;
    unsigned char md_value[EVP_MAX_MD_SIZE];
    EVP_PKEY *pkey = NULL;
#ifdef HAVE_EDDSA
    EVP_MD_CTX *mdctx = NULL;
#endif
#ifdef HAS_EVP_PKEY_CTX
    EVP_PKEY_CTX *ctx = NULL;
    size_t siglen;
#else
    int len;
    unsigned int siglen;
#endif
    PKeySignOptions sig_opt;
    ErlNifBinary sig_bin; /* signature */
    unsigned char *tbs; /* data to be signed */
    size_t tbslen;
    RSA *rsa = NULL;
#ifdef HAVE_DSA
    DSA *dsa = NULL;
#endif
#if defined(HAVE_EC)
    EC_KEY *ec = NULL;
#endif
/*char buf[1024];
enif_get_atom(env,argv[0],buf,1024,ERL_NIF_LATIN1); printf("algo=%s ",buf);
enif_get_atom(env,argv[1],buf,1024,ERL_NIF_LATIN1); printf("hash=%s ",buf);
*/

#ifndef HAS_ENGINE_SUPPORT
    if (enif_is_map(env, argv[3]))
        return atom_notsup;
#endif

    i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen);
    switch (i) {
    case PKEY_OK:
        break;
    case PKEY_NOTSUP:
        goto notsup;
    default:
        goto bad_arg;
    }

    i = get_pkey_sign_options(env, argv[0], argv[4], md, &sig_opt);
    switch (i) {
    case PKEY_OK:
        break;
    case PKEY_NOTSUP:
        goto notsup;
    default:
        goto bad_arg;
    }

    if (get_pkey_private_key(env, argv[0], argv[3], &pkey) != PKEY_OK)
        goto bad_arg;

#ifdef HAS_EVP_PKEY_CTX
    if ((ctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL)
        goto err;

    if (argv[0] != atom_eddsa) {
        if (EVP_PKEY_sign_init(ctx) != 1)
            goto err;
        if (md != NULL) {
            if (EVP_PKEY_CTX_set_signature_md(ctx, md) != 1)
                goto err;
        }
    }

    if (argv[0] == atom_rsa) {
        if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) != 1)
            goto err;
# ifdef HAVE_RSA_PKCS1_PSS_PADDING
	if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) {
            if (sig_opt.rsa_mgf1_md != NULL) {
# ifdef HAVE_RSA_MGF1_MD
                if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) != 1)
                    goto err;
# else
                goto notsup;
# endif
            }
            if (sig_opt.rsa_pss_saltlen > -2) {
                if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) != 1)
                    goto err;
            }
        }
#endif
    }

    if (argv[0] == atom_eddsa) {
#ifdef HAVE_EDDSA
        if (!FIPS_mode()) {
            if ((mdctx = EVP_MD_CTX_new()) == NULL)
                goto err;

            if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) != 1)
                goto err;
            if (EVP_DigestSign(mdctx, NULL, &siglen, tbs, tbslen) != 1)
                goto err;
            if (!enif_alloc_binary(siglen, &sig_bin))
                goto err;
            sig_bin_alloc = 1;

            if (EVP_DigestSign(mdctx, sig_bin.data, &siglen, tbs, tbslen) != 1)
                goto bad_key;
        }
        else
#endif
            goto notsup;
    } else {
        if (EVP_PKEY_sign(ctx, NULL, &siglen, tbs, tbslen) != 1)
            goto err;
        if (!enif_alloc_binary(siglen, &sig_bin))
            goto err;
        sig_bin_alloc = 1;

        if (md != NULL) {
            ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md));
        }
        if (EVP_PKEY_sign(ctx, sig_bin.data, &siglen, tbs, tbslen) != 1)
            goto bad_key;
    }
#else
/*printf("Old interface\r\n");
 */
    if (argv[0] == atom_rsa) {
        if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL)
            goto err;
        if ((len = RSA_size(rsa)) < 0)
            goto err;
        if (!enif_alloc_binary((size_t)len, &sig_bin))
            goto err;
        sig_bin_alloc = 1;

        if ((len = EVP_MD_size(md)) < 0)
            goto err;
        ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);

        if (RSA_sign(md->type, tbs, (unsigned int)len, sig_bin.data, &siglen, rsa) != 1)
            goto bad_key;
    } else if (argv[0] == atom_dss) {
        if ((dsa = EVP_PKEY_get1_DSA(pkey)) == NULL)
            goto err;
        if ((len = DSA_size(dsa)) < 0)
            goto err;
        if (!enif_alloc_binary((size_t)len, &sig_bin))
            goto err;
        sig_bin_alloc = 1;

        if ((len = EVP_MD_size(md)) < 0)
            goto err;
        ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);

        if (DSA_sign(md->type, tbs, len, sig_bin.data, &siglen, dsa) != 1)
            goto bad_key;
    } else if (argv[0] == atom_ecdsa) {
#if defined(HAVE_EC)
        if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL)
            goto err;
        if ((len = ECDSA_size(ec)) < 0)
            goto err;
        if (!enif_alloc_binary((size_t)len, &sig_bin))
            goto err;
        sig_bin_alloc = 1;

        len = EVP_MD_size(md);
        ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);

        if (ECDSA_sign(md->type, tbs, len, sig_bin.data, &siglen, ec) != 1)
            goto bad_key;
#else
        goto notsup;
#endif
    } else {
        goto bad_arg;
    }
#endif

    ERL_VALGRIND_MAKE_MEM_DEFINED(sig_bin.data, siglen);
    if (siglen != sig_bin.size) {
        if (!enif_realloc_binary(&sig_bin, siglen))
            goto err;
        ERL_VALGRIND_ASSERT_MEM_DEFINED(sig_bin.data, siglen);
    }
    ret = enif_make_binary(env, &sig_bin);
    sig_bin_alloc = 0;
    goto done;

 bad_key:
    ret = atom_error;
    goto done;

 notsup:
    ret = atom_notsup;
    goto done;

 bad_arg:
 err:
    ret = enif_make_badarg(env);
    goto done;

 done:
    if (sig_bin_alloc)
        enif_release_binary(&sig_bin);
    if (rsa)
        RSA_free(rsa);
#ifdef HAVE_DSA
    if (dsa)
        DSA_free(dsa);
#endif
#ifdef HAVE_EC
    if (ec)
        EC_KEY_free(ec);
#endif
#ifdef HAS_EVP_PKEY_CTX
    if (ctx)
        EVP_PKEY_CTX_free(ctx);
#endif
    if (pkey)
        EVP_PKEY_free(pkey);

#ifdef HAVE_EDDSA
    if (mdctx)
        EVP_MD_CTX_free(mdctx);
#endif

    return ret;
}

ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{/* (Algorithm, Type, Data|{digest,Digest}, Signature, Key, Options) */
    int i;
    int result;
    const EVP_MD *md = NULL;
    unsigned char md_value[EVP_MAX_MD_SIZE];
    EVP_PKEY *pkey = NULL;
#ifdef HAS_EVP_PKEY_CTX
    EVP_PKEY_CTX *ctx = NULL;
#else
#endif
    PKeySignOptions sig_opt;
    ErlNifBinary sig_bin; /* signature */
    unsigned char *tbs; /* data to be signed */
    size_t tbslen;
    ERL_NIF_TERM ret;
    RSA *rsa = NULL;
#ifdef HAVE_DSA
    DSA *dsa = NULL;
#endif
#ifdef HAVE_EC
    EC_KEY *ec = NULL;
#endif
#ifdef HAVE_EDDSA
    EVP_MD_CTX *mdctx = NULL;
#endif

#ifndef HAS_ENGINE_SUPPORT
    if (enif_is_map(env, argv[4]))
        return atom_notsup;
#endif

    if (!enif_inspect_binary(env, argv[3], &sig_bin))
	return enif_make_badarg(env);

    i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen);
    switch (i) {
    case PKEY_OK:
        break;
    case PKEY_NOTSUP:
        goto notsup;
    default:
        goto bad_arg;
    }

    i = get_pkey_sign_options(env, argv[0], argv[5], md, &sig_opt);
    switch (i) {
    case PKEY_OK:
        break;
    case PKEY_NOTSUP:
        goto notsup;
    default:
        goto bad_arg;
    }

    if (get_pkey_public_key(env, argv[0], argv[4], &pkey) != PKEY_OK) {
        goto bad_arg;
    }

#ifdef HAS_EVP_PKEY_CTX
/* printf("EVP interface\r\n");
 */
    if ((ctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL)
        goto err;

    if (argv[0] != atom_eddsa) {
        if (EVP_PKEY_verify_init(ctx) != 1)
            goto err;
        if (md != NULL) {
            if (EVP_PKEY_CTX_set_signature_md(ctx, md) != 1)
                goto err;
        }
    }

    if (argv[0] == atom_rsa) {
        if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) != 1)
            goto err;
        if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) {
            if (sig_opt.rsa_mgf1_md != NULL) {
# ifdef HAVE_RSA_MGF1_MD
                if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) != 1)
                    goto err;
# else
                goto notsup;
# endif
            }
            if (sig_opt.rsa_pss_saltlen > -2) {
                if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) != 1)
                    goto err;
            }
        }
    }

    if (argv[0] == atom_eddsa) {
#ifdef HAVE_EDDSA
        if (!FIPS_mode()) {
            if ((mdctx = EVP_MD_CTX_new()) == NULL)
                goto err;

            if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) != 1)
                goto err;

            result = EVP_DigestVerify(mdctx, sig_bin.data, sig_bin.size, tbs, tbslen);
        }
        else
#endif
        goto notsup;
    } else {
        if (md != NULL) {
            ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md));
        }
        result = EVP_PKEY_verify(ctx, sig_bin.data, sig_bin.size, tbs, tbslen);
    }
#else
/*printf("Old interface\r\n");
*/
    if (tbslen > INT_MAX)
        goto bad_arg;
    if (sig_bin.size > INT_MAX)
        goto bad_arg;
    if (argv[0] == atom_rsa) {
        if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL)
            goto err;
        result = RSA_verify(md->type, tbs, (unsigned int)tbslen, sig_bin.data, (unsigned int)sig_bin.size, rsa);
    } else if (argv[0] == atom_dss) {
        if ((dsa = EVP_PKEY_get1_DSA(pkey)) == NULL)
            goto err;
        result = DSA_verify(0, tbs, (int)tbslen, sig_bin.data, (int)sig_bin.size, dsa);
    } else if (argv[0] == atom_ecdsa) {
#if defined(HAVE_EC)
        if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL)
            goto err;
        result = ECDSA_verify(EVP_MD_type(md), tbs, (int)tbslen, sig_bin.data, (int)sig_bin.size, ec);
#else
        goto notsup;
#endif
    } else {
        goto bad_arg;
    }
#endif

    ret = (result == 1 ? atom_true : atom_false);
    goto done;

 bad_arg:
 err:
    ret = enif_make_badarg(env);
    goto done;

 notsup:
    ret = atom_notsup;

 done:
#ifdef HAS_EVP_PKEY_CTX
    if (ctx)
        EVP_PKEY_CTX_free(ctx);
#endif
#ifdef HAVE_EDDSA
    if (mdctx)
        EVP_MD_CTX_free(mdctx);
#endif
    if (pkey)
        EVP_PKEY_free(pkey);
    if (rsa)
        RSA_free(rsa);
#ifdef HAVE_DSA
    if (dsa)
        DSA_free(dsa);
#endif
#ifdef HAVE_EC
    if (ec)
        EC_KEY_free(ec);
#endif

    return ret;
}

static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
				  PKeyCryptOptions *opt)
{
    ERL_NIF_TERM head, tail;
    const ERL_NIF_TERM *tpl_terms;
    int tpl_arity;
    const EVP_MD *opt_md;

    if (!enif_is_list(env, options))
        goto bad_arg;

    /* defaults */
    if (algorithm == atom_rsa) {
	opt->rsa_mgf1_md = NULL;
	opt->rsa_oaep_label.data = NULL;
	opt->rsa_oaep_label.size = 0;
	opt->rsa_oaep_md = NULL;
	opt->rsa_padding = RSA_PKCS1_PADDING;
	opt->signature_md = NULL;
    }

    if (enif_is_empty_list(env, options))
        return PKEY_OK;

    if (algorithm != atom_rsa)
        goto bad_arg;

    tail = options;
    while (enif_get_list_cell(env, tail, &head, &tail)) {
        if (!enif_get_tuple(env, head, &tpl_arity, &tpl_terms))
            goto bad_arg;
        if (tpl_arity != 2)
            goto bad_arg;

        if (tpl_terms[0] == atom_rsa_padding
            || tpl_terms[0] == atom_rsa_pad /* Compatibility */
            ) {
            if (tpl_terms[1] == atom_rsa_pkcs1_padding) {
                opt->rsa_padding = RSA_PKCS1_PADDING;

#ifdef HAVE_RSA_OAEP_PADDING
            } else if (tpl_terms[1] == atom_rsa_pkcs1_oaep_padding) {
                opt->rsa_padding = RSA_PKCS1_OAEP_PADDING;
#endif

#ifdef HAVE_RSA_SSLV23_PADDING
            } else if (tpl_terms[1] == atom_rsa_sslv23_padding) {
                opt->rsa_padding = RSA_SSLV23_PADDING;
#endif

            } else if (tpl_terms[1] == atom_rsa_x931_padding) {
                opt->rsa_padding = RSA_X931_PADDING;

            } else if (tpl_terms[1] == atom_rsa_no_padding) {
                opt->rsa_padding = RSA_NO_PADDING;

            } else {
                goto bad_arg;
            }

        } else if (tpl_terms[0] == atom_signature_md && enif_is_atom(env, tpl_terms[1])) {
            int i;
            i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
            if (i != PKEY_OK) {
                return i;
            }
            opt->signature_md = opt_md;

        } else if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) {
            int i;
#ifndef HAVE_RSA_MGF1_MD
            if (tpl_terms[1] != atom_sha)
                return PKEY_NOTSUP;
#endif
            i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
            if (i != PKEY_OK) {
                return i;
            }
            opt->rsa_mgf1_md = opt_md;

        } else if (tpl_terms[0] == atom_rsa_oaep_label
                   && enif_inspect_binary(env, tpl_terms[1], &(opt->rsa_oaep_label))) {
#ifdef HAVE_RSA_OAEP_MD
            continue;
#else
            return PKEY_NOTSUP;
#endif

        } else if (tpl_terms[0] == atom_rsa_oaep_md && enif_is_atom(env, tpl_terms[1])) {
            int i;
#ifndef HAVE_RSA_OAEP_MD
            if (tpl_terms[1] != atom_sha)
                return PKEY_NOTSUP;
#endif
            i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
            if (i != PKEY_OK) {
                return i;
            }
            opt->rsa_oaep_md = opt_md;

        } else {
            goto bad_arg;
        }
    }

    return PKEY_OK;

 bad_arg:
    return PKEY_BADARG;
}

#ifdef HAVE_RSA_SSLV23_PADDING
static size_t size_of_RSA(EVP_PKEY *pkey) {
    int ret = 0;
    RSA *rsa = NULL;

    if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL)
        goto err;
    ret = RSA_size(rsa);

 err:
    if (rsa)
        RSA_free(rsa);

    return (ret < 0) ? 0 : (size_t)ret;
}
#endif

ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{/* (Algorithm, Data, PublKey=[E,N]|[E,N,D]|[E,N,D,P1,P2,E1,E2,C], Options, IsPrivate, IsEncrypt) */
    ERL_NIF_TERM ret;
    int i;
    int result = 0;
    int tmp_bin_alloc = 0;
    int out_bin_alloc = 0;
    EVP_PKEY *pkey = NULL;
#ifdef HAS_EVP_PKEY_CTX
    EVP_PKEY_CTX *ctx = NULL;
#else
    int len;
    RSA *rsa = NULL;
#endif
    PKeyCryptOptions crypt_opt;
    ErlNifBinary in_bin, out_bin, tmp_bin;
    size_t outlen;
#ifdef HAVE_RSA_SSLV23_PADDING
    size_t tmplen;
#endif
    int is_private, is_encrypt;
    int algo_init = 0;
    unsigned char *label_copy = NULL;

    ASSERT(argc == 6);

    is_private = (argv[4] == atom_true);
    is_encrypt = (argv[5] == atom_true);

/* char algo[1024]; */

#ifndef HAS_ENGINE_SUPPORT
    if (enif_is_map(env, argv[2]))
        return atom_notsup;
#endif

    if (!enif_inspect_binary(env, argv[1], &in_bin))
        goto bad_arg;

    i = get_pkey_crypt_options(env, argv[0], argv[3], &crypt_opt);
    switch (i) {
    case PKEY_OK:
        break;
    case PKEY_NOTSUP:
        goto notsup;
    default:
        goto bad_arg;
    }

    if (is_private) {
        if (get_pkey_private_key(env, argv[0], argv[2], &pkey) != PKEY_OK)
            goto bad_arg;
    } else {
        if (get_pkey_public_key(env, argv[0], argv[2], &pkey) != PKEY_OK)
            goto bad_arg;
    }

#ifdef HAS_EVP_PKEY_CTX
    if ((ctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL)
        goto err;

/* enif_get_atom(env,argv[0],algo,1024,ERL_NIF_LATIN1);  */

    if (is_private) {
        if (is_encrypt) {
            /* private encrypt */
            if ((algo_init = EVP_PKEY_sign_init(ctx)) != 1)
                goto bad_arg;
        } else {
            /* private decrypt */
            if ((algo_init = EVP_PKEY_decrypt_init(ctx)) != 1)
                goto bad_arg;
        }
    } else {
        if (is_encrypt) {
            /* public encrypt */
            if ((algo_init = EVP_PKEY_encrypt_init(ctx)) != 1)
                goto bad_arg;
        } else {
            /* public decrypt */
            if ((algo_init = EVP_PKEY_verify_recover_init(ctx)) != 1)
                goto bad_arg;
        }
    }

    if (argv[0] == atom_rsa) {
        if (crypt_opt.signature_md != NULL) {
            if (EVP_PKEY_CTX_set_signature_md(ctx, crypt_opt.signature_md) != 1)
                goto bad_arg;
        }

#ifdef HAVE_RSA_SSLV23_PADDING
        if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) {
            if (is_encrypt) {
                tmplen = size_of_RSA(pkey);
                if (tmplen < 1 || tmplen > INT_MAX)
                    goto err;
                if (!enif_alloc_binary(tmplen, &tmp_bin))
                    goto err;
                tmp_bin_alloc = 1;
                if (in_bin.size > INT_MAX)
                    goto err;
                if (!RSA_padding_add_SSLv23(tmp_bin.data, (int)tmplen, in_bin.data, (int)in_bin.size))
                    goto err;
                in_bin = tmp_bin;
            }
            if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) != 1)
                goto err;
        } else
#endif
        {
            if (EVP_PKEY_CTX_set_rsa_padding(ctx, crypt_opt.rsa_padding) != 1)
                goto err;
        }

#ifdef HAVE_RSA_OAEP_MD
        if (crypt_opt.rsa_padding == RSA_PKCS1_OAEP_PADDING) {
            if (crypt_opt.rsa_oaep_md != NULL) {
                if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, crypt_opt.rsa_oaep_md) != 1)
                    goto err;
            }

            if (crypt_opt.rsa_mgf1_md != NULL) {
                if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, crypt_opt.rsa_mgf1_md) != 1)
                    goto err;
            }

            if (crypt_opt.rsa_oaep_label.data != NULL && crypt_opt.rsa_oaep_label.size > 0) {
                if (crypt_opt.rsa_oaep_label.size > INT_MAX)
                    goto err;
                if ((label_copy = OPENSSL_malloc(crypt_opt.rsa_oaep_label.size)) == NULL)
                    goto err;

                memcpy((void *)(label_copy), (const void *)(crypt_opt.rsa_oaep_label.data),
                       crypt_opt.rsa_oaep_label.size);

                if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, label_copy,
                                                     (int)crypt_opt.rsa_oaep_label.size) != 1)
                    goto err;
                /* On success, label_copy is owned by ctx */
                label_copy = NULL;
            }
        }
#endif
    }

    if (is_private) {
        if (is_encrypt) {
            /* private_encrypt */
            result = EVP_PKEY_sign(ctx, NULL, &outlen, in_bin.data, in_bin.size);
        } else {
            /* private_decrypt */
            result = EVP_PKEY_decrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size);
        }
    } else {
        if (is_encrypt) {
            /* public_encrypt */
            result = EVP_PKEY_encrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size);
        } else {
            /* public_decrypt */
            result = EVP_PKEY_verify_recover(ctx, NULL, &outlen, in_bin.data, in_bin.size);
        }
    }
    /* fprintf(stderr,"i = %d %s:%d\r\n", i, __FILE__, __LINE__); */

    if (result != 1)
        goto err;

    if (!enif_alloc_binary(outlen, &out_bin))
        goto err;
    out_bin_alloc = 1;

    if (is_private) {
        if (is_encrypt) {
            /* private_encrypt */
            result = EVP_PKEY_sign(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
        } else {
            /* private_decrypt */
            result = EVP_PKEY_decrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
        }
    } else {
        if (is_encrypt) {
            /* public_encrypt */
            result = EVP_PKEY_encrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
        } else {
            /* public_decrypt */
            result = EVP_PKEY_verify_recover(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
        }
    }

#else
    /* Non-EVP cryptolib. Only support RSA */

    if (argv[0] != atom_rsa) {
        algo_init = -2;         /* exitcode: notsup */
        goto bad_arg;
    }

    if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL)
        goto err;
    if ((len = RSA_size(rsa)) < 0)
        goto err;
    if (!enif_alloc_binary((size_t)len, &out_bin))
        goto err;
    out_bin_alloc = 1;

    if (in_bin.size > INT_MAX)
        goto err;
    if (is_private) {
        if (is_encrypt) {
            /* non-evp rsa private encrypt */
            ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size);
            result = RSA_private_encrypt((int)in_bin.size, in_bin.data,
                                    out_bin.data, rsa, crypt_opt.rsa_padding);
            if (result > 0) {
                ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, result);
            }
        } else {
            /* non-evp rsa private decrypt */
            result = RSA_private_decrypt((int)in_bin.size, in_bin.data,
                                    out_bin.data, rsa, crypt_opt.rsa_padding);
            if (result > 0) {
                ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, result);
                if (!enif_realloc_binary(&out_bin, (size_t)result))
                    goto err;
            }
        }
    } else {
        if (is_encrypt) {
            /* non-evp rsa public encrypt */
            ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size);
            result = RSA_public_encrypt((int)in_bin.size, in_bin.data,
                                   out_bin.data, rsa, crypt_opt.rsa_padding);
            if (result > 0) {
                ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, result);
            }
        } else {
            /* non-evp rsa public decrypt */
            result = RSA_public_decrypt((int)in_bin.size, in_bin.data,
                                   out_bin.data, rsa, crypt_opt.rsa_padding);
            if (result > 0) {
                ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, result);
                if (!enif_realloc_binary(&out_bin, (size_t)result))
                    goto err;
            }
        }
    }

    outlen = (size_t)result;
#endif

    if ((result > 0) && argv[0] == atom_rsa && !is_encrypt) {
#ifdef HAVE_RSA_SSLV23_PADDING
        if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) {
            unsigned char *p;

            tmplen = size_of_RSA(pkey);
            if (tmplen < 1 || tmplen > INT_MAX)
                goto err;
            if (!enif_alloc_binary(tmplen, &tmp_bin))
                goto err;
            tmp_bin_alloc = 1;
            if (out_bin.size > INT_MAX)
                goto err;

            p = out_bin.data;
            p++;

            result = RSA_padding_check_SSLv23(tmp_bin.data, (int)tmplen, p, (int)out_bin.size - 1, (int)tmplen);
            if (result >= 0) {
                outlen = (size_t)result;
                in_bin = out_bin;
                out_bin = tmp_bin;
                tmp_bin = in_bin;
                result = 1;
            }
        }
#endif
    }

    if (result > 0) {
        ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, outlen);
        if (outlen != out_bin.size) {
            if (!enif_realloc_binary(&out_bin, outlen))
                goto err;
            ERL_VALGRIND_ASSERT_MEM_DEFINED(out_bin.data, outlen);
        }
        ret = enif_make_binary(env, &out_bin);
        out_bin_alloc = 0;
    } else {
        ret = atom_error;
    }
    goto done;

 notsup:
    ret = atom_notsup;
    goto done;

 bad_arg:
 err:
    if (algo_init == -2)
        ret = atom_notsup;
    else
        ret = enif_make_badarg(env);

 done:
    if (out_bin_alloc)
        enif_release_binary(&out_bin);
    if (tmp_bin_alloc)
        enif_release_binary(&tmp_bin);

#ifdef HAS_EVP_PKEY_CTX
    if (ctx)
        EVP_PKEY_CTX_free(ctx);
#else
    if (rsa)
        RSA_free(rsa);
#endif
    if (pkey)
        EVP_PKEY_free(pkey);

    if (label_copy)
        OPENSSL_free(label_copy);

    return ret;
}

ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{ /* (Algorithm, PrivKey | KeyMap) */
    ERL_NIF_TERM ret;
    EVP_PKEY *pkey = NULL;
    RSA *rsa = NULL;
#ifdef HAVE_DSA
    DSA *dsa = NULL;
#endif
    ERL_NIF_TERM result[8];

    ASSERT(argc == 2);

    if (get_pkey_private_key(env, argv[0], argv[1], &pkey) != PKEY_OK)
        goto bad_arg;

    if (argv[0] == atom_rsa) {
        const BIGNUM *n = NULL, *e = NULL, *d = NULL;

        if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL)
            goto err;

        RSA_get0_key(rsa, &n, &e, &d);

        // Exponent E
        if ((result[0] = bin_from_bn(env, e)) == atom_error)
            goto err;
        // Modulus N = p*q
        if ((result[1] = bin_from_bn(env, n)) == atom_error)
            goto err;

        ret = enif_make_list_from_array(env, result, 2);

#ifdef HAVE_DSA
    } else if (argv[0] == atom_dss) {
        const BIGNUM *p = NULL, *q = NULL, *g = NULL, *pub_key = NULL;

        if ((dsa = EVP_PKEY_get1_DSA(pkey)) == NULL)
            goto err;

        DSA_get0_pqg(dsa, &p, &q, &g);
        DSA_get0_key(dsa, &pub_key, NULL);

        if ((result[0] = bin_from_bn(env, p)) == atom_error)
            goto err;
        if ((result[1] = bin_from_bn(env, q)) == atom_error)
            goto err;
        if ((result[2] = bin_from_bn(env, g)) == atom_error)
            goto err;
        if ((result[3] = bin_from_bn(env, pub_key)) == atom_error)
            goto err;

        ret = enif_make_list_from_array(env, result, 4);
#endif
    } else if (argv[0] == atom_ecdsa) {
#if defined(HAVE_EC)
        /* not yet implemented
          EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
          if (ec) {
          / * Example of result:
               {
                 Curve =  {Field, Prime, Point, Order, CoFactor} =
                    {
                      Field =  {prime_field,<<255,...,255>>},
                      Prime = {<<255,...,252>>,
                               <<90,...,75>>,
                               <<196,...,144>>
                              },
                      Point =    <<4,...,245>>,
                      Order =    <<255,...,81>>,
                      CoFactor = <<1>>
                    },
                Key = <<151,...,62>>
                }
              or
              {
                Curve =
                    {characteristic_two_field,
                     M,
                     Basis = {tpbasis, _}
                           | {ppbasis, k1, k2, k3}
                    },
                Key
               }
        * /
            EVP_PKEY_free(pkey);
            return enif_make_list_from_array(env, ..., ...);
        */
#endif
        goto bad_arg;
    } else {
        goto bad_arg;
    }

    goto done;

 bad_arg:
 err:
    ret = enif_make_badarg(env);

 done:
    if (rsa)
        RSA_free(rsa);
#ifdef HAVE_DSA
    if (dsa)
        DSA_free(dsa);
#endif
    if (pkey)
        EVP_PKEY_free(pkey);

    return ret;
}