From 61052b4c66134369c095c75d88c1893d9212a22e Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Wed, 19 Oct 2022 18:26:03 +0300 Subject: [PATCH] OpenSSL::PKey.from_parameters for generating RSA and EC keys from parameter values --- ext/openssl/ossl_pkey.c | 147 ++++++++++++++++++++++++++++++++++++++ test/openssl/test_pkey.rb | 108 ++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index ec39e8bd7..f4a3cf755 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -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 +#include + +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 @@ -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 @@ -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); diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 544340e37..25309c46e 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -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