Skip to content

Commit

Permalink
OpenSSL::PKey.from_parameters for generating RSA and EC keys from par…
Browse files Browse the repository at this point in the history
…ameter values
  • Loading branch information
anakinj committed Oct 25, 2022
1 parent 1ddbf28 commit 61052b4
Show file tree
Hide file tree
Showing 2 changed files with 255 additions and 0 deletions.
147 changes: 147 additions & 0 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,141 @@ pkey_generate(int argc, VALUE *argv, VALUE self, int genparam)
return ossl_pkey_new(gen_arg.pkey);
}

#if OSSL_OPENSSL_PREREQ(3, 0, 0)
#include <openssl/param_build.h>
#include <openssl/core_names.h>

static int
add_ec_parameters_to_builder(VALUE key, VALUE value, VALUE arg) {
OSSL_PARAM_BLD *params_builder = (OSSL_PARAM_BLD *) arg;

if(NIL_P(value))
return ST_CONTINUE;

if (SYMBOL_P(key))
key = rb_sym2str(key);

const char * key_ptr = StringValueCStr(key);

if(strcmp(OSSL_PKEY_PARAM_GROUP_NAME, key_ptr) == 0)
if(!OSSL_PARAM_BLD_push_utf8_string(params_builder, key_ptr, StringValueCStr(value), 0))
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_push_utf8_string");

return ST_CONTINUE;
}

static int
add_rsa_parameters_to_builder(VALUE key, VALUE value, VALUE arg) {
OSSL_PARAM_BLD *params_builder = (OSSL_PARAM_BLD *) arg;

if(NIL_P(value))
return ST_CONTINUE;

if (SYMBOL_P(key))
key = rb_sym2str(key);

static const struct {
char alias[5];
char param_name[20];
} key_aliases[] = {
{ "p", OSSL_PKEY_PARAM_RSA_FACTOR1 },
{ "q", OSSL_PKEY_PARAM_RSA_FACTOR2 },
{ "dmp1", OSSL_PKEY_PARAM_RSA_EXPONENT1 },
{ "dmq1", OSSL_PKEY_PARAM_RSA_EXPONENT2 },
{ "iqmp", OSSL_PKEY_PARAM_RSA_COEFFICIENT1 }
};

const char * key_ptr = StringValueCStr(key);

for(int i = 0; i < (int)(sizeof(key_aliases)/sizeof((key_aliases)[0])); i++) {
if(strcmp(key_aliases[i].alias, key_ptr) == 0) {
key_ptr = key_aliases[i].param_name;
break;
}
}

if((strcmp(OSSL_PKEY_PARAM_RSA_N, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_E, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_D, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR1, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR2, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR3, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR4, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR5, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR6, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR7, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR8, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR9, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR10, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT1, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT2, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT3, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT4, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT5, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT6, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT7, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT8, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT9, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT10, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT1, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT2, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT3, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT4, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT5, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT6, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT7, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT8, key_ptr) != 0 &&
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT9, key_ptr)) != 0)
ossl_raise(ePKeyError, "Unsupported RSA parameter \"%s\"", key_ptr);

if(!OSSL_PARAM_BLD_push_BN(params_builder, key_ptr, GetBNPtr(value)))
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_push_BN");

return ST_CONTINUE;
}

static VALUE
pkey_from_parameters(int argc, VALUE *argv, VALUE self)
{
VALUE alg, options;
rb_scan_args(argc, argv, "11", &alg, &options);

OSSL_PARAM_BLD *params_builder = OSSL_PARAM_BLD_new();

if (params_builder == NULL)
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_new");

int (*param_adder)(VALUE, VALUE, VALUE) = NULL;

if(strcmp("RSA", StringValueCStr(alg)) == 0)
param_adder = &add_rsa_parameters_to_builder;
else if (strcmp("EC", StringValueCStr(alg)) == 0)
param_adder = &add_ec_parameters_to_builder;
else
ossl_raise(ePKeyError, "\"%s\" is not a supported algorithm", StringValueCStr(alg));

rb_hash_foreach(options, param_adder, (VALUE) params_builder);

OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(params_builder);
OSSL_PARAM_BLD_free(params_builder);

if (params == NULL)
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_to_param");

EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, StringValueCStr(alg), NULL);
EVP_PKEY *pkey = NULL;

if (ctx == NULL ||
EVP_PKEY_fromdata_init(ctx) <= 0 ||
EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
ossl_clear_error();
ossl_raise(ePKeyError, "EVP_PKEY_fromdata");
}

return ossl_pkey_new(pkey);
}
#endif

/*
* call-seq:
* OpenSSL::PKey.generate_parameters(algo_name [, options]) -> pkey
Expand Down Expand Up @@ -475,6 +610,17 @@ ossl_pkey_s_generate_key(int argc, VALUE *argv, VALUE self)
return pkey_generate(argc, argv, self, 0);
}

static VALUE
ossl_pkey_s_from_parameters(int argc, VALUE *argv, VALUE self)
{
// TODO: A version that works with OpenSSL 1.1
#if OSSL_OPENSSL_PREREQ(3, 0, 0)
return pkey_from_parameters(argc, argv, self);
#else
rb_raise(ePKeyError, "Only supported with OpenSSL 3.0");
#endif
}

/*
* TODO: There is no convenient way to check the presence of public key
* components on OpenSSL 3.0. But since keys are immutable on 3.0, pkeys without
Expand Down Expand Up @@ -1586,6 +1732,7 @@ Init_ossl_pkey(void)
rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1);
rb_define_module_function(mPKey, "generate_parameters", ossl_pkey_s_generate_parameters, -1);
rb_define_module_function(mPKey, "generate_key", ossl_pkey_s_generate_key, -1);
rb_define_module_function(mPKey, "from_parameters", ossl_pkey_s_from_parameters, -1);

rb_define_alloc_func(cPKey, ossl_pkey_alloc);
rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0);
Expand Down
108 changes: 108 additions & 0 deletions test/openssl/test_pkey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,112 @@ def test_to_text
rsa = Fixtures.pkey("rsa1024")
assert_include rsa.to_text, "publicExponent"
end

if openssl?(3, 0, 0)
def test_rsa_from_parameters_with_n_e_and_d_given_as_integers
new_key = OpenSSL::PKey.from_parameters("RSA", n: 3161751493,
e: 65537,
d: 2064855961)

assert_instance_of OpenSSL::PKey::RSA, new_key
assert_equal true, new_key.private?
assert_equal OpenSSL::BN.new(3161751493), new_key.n
assert_equal OpenSSL::BN.new(65537), new_key.e
assert_equal OpenSSL::BN.new(2064855961), new_key.d
end


def test_rsa_from_parameters_with_n_e_and_d_given
new_key = OpenSSL::PKey.from_parameters("RSA", "n" => OpenSSL::BN.new(3161751493),
"e" => OpenSSL::BN.new(65537),
"d" => OpenSSL::BN.new(2064855961))

assert_instance_of OpenSSL::PKey::RSA, new_key
assert_equal true, new_key.private?
assert_equal OpenSSL::BN.new(3161751493), new_key.n
assert_equal OpenSSL::BN.new(65537), new_key.e
assert_equal OpenSSL::BN.new(2064855961), new_key.d
end

def test_rsa_from_parameters_with_n_and_e_given
new_key = OpenSSL::PKey.from_parameters("RSA", n: OpenSSL::BN.new(3161751493),
e: OpenSSL::BN.new(65537))

assert_instance_of OpenSSL::PKey::RSA, new_key
assert_equal false, new_key.private?
assert_equal OpenSSL::BN.new(3161751493), new_key.n
assert_equal OpenSSL::BN.new(65537), new_key.e
assert_equal nil, new_key.d
end

def test_rsa_from_parameters_with_openssl_internal_names
source = OpenSSL::PKey::RSA.generate(2048)
new_key = OpenSSL::PKey.from_parameters("RSA", n: source.n,
e: source.e,
d: source.d,
"rsa-factor1" => source.p,
"rsa-factor2" => source.q,
"rsa-exponent1" => source.dmp1,
"rsa-exponent2" => source.dmq1,
"rsa-coefficient1" => source.iqmp
)

assert_equal source.n, new_key.n
assert_equal source.e, new_key.e
assert_equal source.d, new_key.d
assert_equal source.p, new_key.p
assert_equal source.q, new_key.q
assert_equal source.dmp1, new_key.dmp1
assert_equal source.dmq1, new_key.dmq1
assert_equal source.iqmp, new_key.iqmp

assert_equal source.to_pem, new_key.to_pem
end

def test_rsa_from_parameters_with_simple_names
source = OpenSSL::PKey::RSA.generate(2048)
new_key = OpenSSL::PKey.from_parameters("RSA", n: source.n,
e: source.e,
d: source.d,
p: source.p,
q: source.q,
dmp1: source.dmp1,
dmq1: source.dmq1,
iqmp: source.iqmp
)

assert_equal source.n, new_key.n
assert_equal source.e, new_key.e
assert_equal source.d, new_key.d
assert_equal source.p, new_key.p
assert_equal source.q, new_key.q
assert_equal source.dmp1, new_key.dmp1
assert_equal source.dmq1, new_key.dmq1
assert_equal source.iqmp, new_key.iqmp

assert_equal source.to_pem, new_key.to_pem
end

def test_from_parameters_with_invalid_alg
e = assert_raise(OpenSSL::PKey::PKeyError) {
OpenSSL::PKey.from_parameters("ASR", {})
}
assert_equal e.message, '"ASR" is not a supported algorithm'
end

def test_ec_from_parameters_with_minimal_data
source = OpenSSL::PKey::EC.generate("prime256v1")

new_key = OpenSSL::PKey.from_parameters("EC", { group: source.group.curve_name })
assert_instance_of OpenSSL::PKey::EC, new_key
assert_equal source.group.curve_name, new_key.group.curve_name
end
else
def test_from_parameter_raises_on_pre_3_openssl
e = assert_raise(OpenSSL::PKey::PKeyError) {
OpenSSL::PKey.from_parameters("RSA", {})
}
assert_equal e.message, "Only supported with OpenSSL 3.0"
end
end
end

0 comments on commit 61052b4

Please sign in to comment.