diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index ec39e8bd7..bf61fc347 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -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 +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 @@ -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 @@ -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); diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 544340e37..a5d0940cd 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -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