#include "rsa.h"
#include "bn.h"
static ERL_NIF_TERM rsa_generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM put_rsa_private_key(ErlNifEnv* env, const RSA *rsa);
static int check_erlang_interrupt(int maj, int min, BN_GENCB *ctxt);
int get_rsa_private_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa)
{
/* key=[E,N,D]|[E,N,D,P1,P2,E1,E2,C] */
ERL_NIF_TERM head, tail;
BIGNUM *e, *n, *d;
BIGNUM *p, *q;
BIGNUM *dmp1, *dmq1, *iqmp;
if (!enif_get_list_cell(env, key, &head, &tail)
|| !get_bn_from_bin(env, head, &e)
|| !enif_get_list_cell(env, tail, &head, &tail)
|| !get_bn_from_bin(env, head, &n)
|| !enif_get_list_cell(env, tail, &head, &tail)
|| !get_bn_from_bin(env, head, &d)) {
return 0;
}
(void) RSA_set0_key(rsa, n, e, d);
if (enif_is_empty_list(env, tail)) {
return 1;
}
if (!enif_get_list_cell(env, tail, &head, &tail)
|| !get_bn_from_bin(env, head, &p)
|| !enif_get_list_cell(env, tail, &head, &tail)
|| !get_bn_from_bin(env, head, &q)
|| !enif_get_list_cell(env, tail, &head, &tail)
|| !get_bn_from_bin(env, head, &dmp1)
|| !enif_get_list_cell(env, tail, &head, &tail)
|| !get_bn_from_bin(env, head, &dmq1)
|| !enif_get_list_cell(env, tail, &head, &tail)
|| !get_bn_from_bin(env, head, &iqmp)
|| !enif_is_empty_list(env, tail)) {
return 0;
}
(void) RSA_set0_factors(rsa, p, q);
(void) RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp);
return 1;
}
int get_rsa_public_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa)
{
/* key=[E,N] */
ERL_NIF_TERM head, tail;
BIGNUM *e, *n;
if (!enif_get_list_cell(env, key, &head, &tail)
|| !get_bn_from_bin(env, head, &e)
|| !enif_get_list_cell(env, tail, &head, &tail)
|| !get_bn_from_bin(env, head, &n)
|| !enif_is_empty_list(env, tail)) {
return 0;
}
(void) RSA_set0_key(rsa, n, e, NULL);
return 1;
}
/* Creates a term which can be parsed by get_rsa_private_key(). This is a list of plain integer binaries (not mpints). */
static ERL_NIF_TERM put_rsa_private_key(ErlNifEnv* env, const RSA *rsa)
{
ERL_NIF_TERM result[8];
const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp;
/* Return at least [E,N,D] */
n = NULL; e = NULL; d = NULL;
RSA_get0_key(rsa, &n, &e, &d);
result[0] = bin_from_bn(env, e); // Exponent E
result[1] = bin_from_bn(env, n); // Modulus N = p*q
result[2] = bin_from_bn(env, d); // Exponent D
/* Check whether the optional additional parameters are available */
p = NULL; q = NULL;
RSA_get0_factors(rsa, &p, &q);
dmp1 = NULL; dmq1 = NULL; iqmp = NULL;
RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
if (p && q && dmp1 && dmq1 && iqmp) {
result[3] = bin_from_bn(env, p); // Factor p
result[4] = bin_from_bn(env, q); // Factor q
result[5] = bin_from_bn(env, dmp1); // D mod (p-1)
result[6] = bin_from_bn(env, dmq1); // D mod (q-1)
result[7] = bin_from_bn(env, iqmp); // (1/q) mod p
return enif_make_list_from_array(env, result, 8);
} else {
return enif_make_list_from_array(env, result, 3);
}
}
static int check_erlang_interrupt(int maj, int min, BN_GENCB *ctxt)
{
ErlNifEnv *env = BN_GENCB_get_arg(ctxt);
if (!enif_is_current_process_alive(env)) {
return 0;
} else {
return 1;
}
}
static ERL_NIF_TERM rsa_generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (ModulusSize, PublicExponent) */
int modulus_bits;
BIGNUM *pub_exp, *three;
RSA *rsa;
int success;
ERL_NIF_TERM result;
BN_GENCB *intr_cb;
#ifndef HAVE_OPAQUE_BN_GENCB
BN_GENCB intr_cb_buf;
#endif
if (!enif_get_int(env, argv[0], &modulus_bits) || modulus_bits < 256) {
return enif_make_badarg(env);
}
if (!get_bn_from_bin(env, argv[1], &pub_exp)) {
return enif_make_badarg(env);
}
/* Make sure the public exponent is large enough (at least 3).
* Without this, RSA_generate_key_ex() can run forever. */
three = BN_new();
BN_set_word(three, 3);
success = BN_cmp(pub_exp, three);
BN_free(three);
if (success < 0) {
BN_free(pub_exp);
return enif_make_badarg(env);
}
/* For large keys, prime generation can take many seconds. Set up
* the callback which we use to test whether the process has been
* interrupted. */
#ifdef HAVE_OPAQUE_BN_GENCB
intr_cb = BN_GENCB_new();
#else
intr_cb = &intr_cb_buf;
#endif
BN_GENCB_set(intr_cb, check_erlang_interrupt, env);
rsa = RSA_new();
success = RSA_generate_key_ex(rsa, modulus_bits, pub_exp, intr_cb);
BN_free(pub_exp);
#ifdef HAVE_OPAQUE_BN_GENCB
BN_GENCB_free(intr_cb);
#endif
if (!success) {
RSA_free(rsa);
return atom_error;
}
result = put_rsa_private_key(env, rsa);
RSA_free(rsa);
return result;
}
ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
/* RSA key generation can take a long time (>1 sec for a large
* modulus), so schedule it as a CPU-bound operation. */
return enif_schedule_nif(env, "rsa_generate_key",
ERL_NIF_DIRTY_JOB_CPU_BOUND,
rsa_generate_key, argc, argv);
}