Skip to content

Commit

Permalink
OpenSSL::PKey.from_parameters for generating RSA keys from parameter …
Browse files Browse the repository at this point in the history
…values
  • Loading branch information
anakinj committed Oct 19, 2022
1 parent 1ddbf28 commit d3071b7
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 0 deletions.
65 changes: 65 additions & 0 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,59 @@ 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>
static int
add_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);

//TODO: Translate GEM specific names to the OpenSSL internal names for RSA params
//TODO: handle different type of parameters for EC keys etc.
if(!OSSL_PARAM_BLD_push_BN(params_builder, StringValueCStr(key), 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);

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

OSSL_PARAM_BLD *params_builder = OSSL_PARAM_BLD_new();

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

rb_hash_foreach(options, add_parameters_to_builder, (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");

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 +528,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 +1650,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
76 changes: 76 additions & 0 deletions test/openssl/test_pkey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,80 @@ def test_to_text
rsa = Fixtures.pkey("rsa1024")
assert_include rsa.to_text, "publicExponent"
end

if openssl?(3, 0, 0)
def test_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_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_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_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_from_parameters_with_invalid_alg
e = assert_raise(OpenSSL::PKey::PKeyError) {
OpenSSL::PKey.from_parameters("ASR", {})
}
assert_equal e.message, "EVP_PKEY_fromdata"
end
else
def test_from_parameter_raises_on_pre_3_openssl
e = assert_raise(OpenSSL::PKey::PKeyError) {
OpenSSL::PKey.from_parameters("ASR", {})
}
assert_equal e.message, "Only supported with OpenSSL 3.0"
end
end
end

0 comments on commit d3071b7

Please sign in to comment.