From 9d6412127d79c90f5e28e49d706465fe5d11a233 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 17 May 2020 22:20:47 +0900 Subject: [PATCH 1/4] [TESTING] .github/workflows: add OpenSSL 3.0.0 and master --- .github/workflows/test.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f09a65282..24e6dc8df 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,3 +94,40 @@ jobs: - name: test run: rake test TESTOPTS="-v --no-show-detail-immediately" OSSL_MDEBUG=1 + + test-openssl-master: + name: OpenSSL master + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + ruby: [ 2.7 ] + steps: + - name: repo checkout + uses: actions/checkout@v2 + + - name: prepare openssl + run: | + mkdir -p tmp/build-openssl && cd tmp/build-openssl + git clone https://github.com/openssl/openssl.git --depth=1 + cd openssl + ./Configure --prefix=$HOME/.openssl/master --libdir=lib \ + linux-x86_64 + make depend + make -j4 + make install_sw + + - name: load ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + - name: depends + run: bundle install + + - name: compile + run: rake compile -- --enable-debug --with-openssl-dir=$HOME/.openssl/master + + - name: test + run: rake test TESTOPTS="-v --no-show-detail-immediately" From 0480c07e389b66547e1b512877bd4d3894b88128 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 15 Apr 2021 18:37:34 +0900 Subject: [PATCH 2/4] pkey: add PKey#to_data wrapping EVP_PKEY_todata() EVP_PKEY_todata() returns all key components contained in an EVP_PKEY object as an array of OSSL_PARAM. This is useful as the replacement for low-level API which is used to implement #params. EVP_PKEY_todata() currently only exists in OpenSSL 3.0.0. --- ext/openssl/extconf.rb | 1 + ext/openssl/ossl_bn.c | 17 ++++++++ ext/openssl/ossl_bn.h | 3 ++ ext/openssl/ossl_pkey.c | 74 +++++++++++++++++++++++++++++++++++ ext/openssl/ossl_pkey_dh.c | 27 ++++++------- ext/openssl/ossl_pkey_dsa.c | 27 ++++++------- ext/openssl/ossl_pkey_ec.c | 28 +++++++++++++ ext/openssl/ossl_pkey_rsa.c | 36 +++++++---------- lib/openssl/pkey.rb | 36 +++++++++++++++++ test/openssl/test_pkey_dh.rb | 39 ++++++++++++++++++ test/openssl/test_pkey_dsa.rb | 29 ++++++++++++++ test/openssl/test_pkey_ec.rb | 8 ++++ test/openssl/test_pkey_rsa.rb | 48 +++++++++++++++++++++++ 13 files changed, 322 insertions(+), 51 deletions(-) diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index a6273b4d8..aed711c13 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -181,6 +181,7 @@ def find_openssl_library have_func("EVP_MD_CTX_get_pkey_ctx") have_func("EVP_PKEY_eq") have_func("EVP_PKEY_dup") +have_func("EVP_PKEY_todata") Logging::message "=== Checking done. ===\n" diff --git a/ext/openssl/ossl_bn.c b/ext/openssl/ossl_bn.c index 56fa0ec30..a28feff7e 100644 --- a/ext/openssl/ossl_bn.c +++ b/ext/openssl/ossl_bn.c @@ -74,6 +74,23 @@ ossl_bn_new(const BIGNUM *bn) return obj; } +#ifdef HAVE_EVP_PKEY_TODATA +VALUE +ossl_bn_new_from_native(const void *data, size_t data_size) +{ + BIGNUM *bn; + VALUE obj; + + obj = NewBN(cBN); + bn = BN_native2bn(data, data_size, NULL); + if (!bn) + ossl_raise(eBNError, "BN_native2bn"); + SetBN(obj, bn); + + return obj; +} +#endif + static BIGNUM * integer_to_bnptr(VALUE obj, BIGNUM *orig) { diff --git a/ext/openssl/ossl_bn.h b/ext/openssl/ossl_bn.h index 1cc041fc2..c44fa5b02 100644 --- a/ext/openssl/ossl_bn.h +++ b/ext/openssl/ossl_bn.h @@ -19,6 +19,9 @@ BN_CTX *ossl_bn_ctx_get(void); #define GetBNPtr(obj) ossl_bn_value_ptr(&(obj)) VALUE ossl_bn_new(const BIGNUM *); +#ifdef HAVE_EVP_PKEY_TODATA +VALUE ossl_bn_new_from_native(const void *data, size_t data_size); +#endif BIGNUM *ossl_bn_value_ptr(volatile VALUE *); void Init_ossl_bn(void); diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 24d0da468..e6bade3cd 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -645,6 +645,79 @@ ossl_pkey_to_text(VALUE self) return ossl_membio2str(bio); } +#ifdef HAVE_EVP_PKEY_TODATA +static VALUE +ossl_params_to_hash(VALUE paramsv) +{ + const OSSL_PARAM *params = (void *)paramsv; + VALUE hash = rb_hash_new(); + size_t i; + + for (i = 0; params[i].key; i++) { + const OSSL_PARAM *p = ¶ms[i]; + VALUE key = ID2SYM(rb_intern(p->key)), value = Qundef; + + switch (p->data_type) { + case OSSL_PARAM_INTEGER: + // FIXME + value = ossl_bn_new_from_native(p->data, p->data_size); + break; + case OSSL_PARAM_UNSIGNED_INTEGER: + value = ossl_bn_new_from_native(p->data, p->data_size); + break; + case OSSL_PARAM_UTF8_STRING: + value = rb_enc_str_new(p->data, p->data_size, rb_utf8_encoding()); + break; + case OSSL_PARAM_OCTET_STRING: + value = rb_str_new(p->data, p->data_size); + break; + default: + OSSL_Debug("ossl_params_to_hash: " \ + "unsupported data type %d for key %s", + (int)p->data_type, p->key); + break; + } + if (value != Qundef) + rb_hash_aset(hash, key, value); + } + return hash; +} +#endif + +/* + * call-seq: + * pkey.to_data -> hash + * + * Returns all information about the key in a Hash. This includes the key + * parameters, public and private key components, and all other miscellaneous + * information returned by the algorithm implementation. + * + * The key names vary depending on the algorithm and also the implementation. + * + * See also the man page EVP_PKEY_todata(3). + */ +static VALUE +ossl_pkey_to_data(VALUE self) +{ +#ifdef HAVE_EVP_PKEY_TODATA + EVP_PKEY *pkey; + OSSL_PARAM *params; + VALUE hash; + int state; + + GetPKey(self, pkey); + if (EVP_PKEY_todata(pkey, OSSL_KEYMGMT_SELECT_ALL, ¶ms) != 1) + ossl_raise(ePKeyError, "EVP_PKEY_todata"); + hash = rb_protect(ossl_params_to_hash, (VALUE)params, &state); + OSSL_PARAM_free(params); + if (state) + rb_jump_tag(state); + return hash; +#else + return rb_hash_new(); +#endif +} + VALUE ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der) { @@ -1557,6 +1630,7 @@ Init_ossl_pkey(void) rb_define_method(cPKey, "oid", ossl_pkey_oid, 0); rb_define_method(cPKey, "inspect", ossl_pkey_inspect, 0); rb_define_method(cPKey, "to_text", ossl_pkey_to_text, 0); + rb_define_method(cPKey, "to_data", ossl_pkey_to_data, 0); rb_define_method(cPKey, "private_to_der", ossl_pkey_private_to_der, -1); rb_define_method(cPKey, "private_to_pem", ossl_pkey_private_to_pem, -1); rb_define_method(cPKey, "public_to_der", ossl_pkey_public_to_der, 0); diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 696455dcf..97b1730cd 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -269,16 +269,10 @@ ossl_dh_to_der(VALUE self) return str; } -/* - * call-seq: - * dh.params -> hash - * - * Stores all parameters of key to the hash - * INSECURE: PRIVATE INFORMATIONS CAN LEAK OUT!!! - * Don't use :-)) (I's up to you) - */ +#ifndef HAVE_EVP_PKEY_TODATA +/* :nodoc: */ static VALUE -ossl_dh_get_params(VALUE self) +ossl_dh_to_data(VALUE self) { DH *dh; VALUE hash; @@ -289,14 +283,15 @@ ossl_dh_get_params(VALUE self) DH_get0_key(dh, &pub_key, &priv_key); hash = rb_hash_new(); - rb_hash_aset(hash, rb_str_new2("p"), ossl_bn_new(p)); - rb_hash_aset(hash, rb_str_new2("q"), ossl_bn_new(q)); - rb_hash_aset(hash, rb_str_new2("g"), ossl_bn_new(g)); - rb_hash_aset(hash, rb_str_new2("pub_key"), ossl_bn_new(pub_key)); - rb_hash_aset(hash, rb_str_new2("priv_key"), ossl_bn_new(priv_key)); + rb_hash_aset(hash, ID2SYM(rb_intern("p")), p ? ossl_bn_new(p) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("q")), q ? ossl_bn_new(q) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("g")), g ? ossl_bn_new(g) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("pub")), pub_key ? ossl_bn_new(pub_key) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("priv")), priv_key ? ossl_bn_new(priv_key) : Qnil); return hash; } +#endif /* * call-seq: @@ -429,7 +424,9 @@ Init_ossl_dh(void) rb_define_method(cDH, "set_pqg", ossl_dh_set_pqg, 3); rb_define_method(cDH, "set_key", ossl_dh_set_key, 2); - rb_define_method(cDH, "params", ossl_dh_get_params, 0); +#ifndef HAVE_EVP_PKEY_TODATA + rb_define_method(cDH, "to_data", ossl_dh_to_data, 0); +#endif } #else /* defined NO_DH */ diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c index 25404aa7f..aded059a9 100644 --- a/ext/openssl/ossl_pkey_dsa.c +++ b/ext/openssl/ossl_pkey_dsa.c @@ -254,16 +254,10 @@ ossl_dsa_to_der(VALUE self) } -/* - * call-seq: - * dsa.params -> hash - * - * Stores all parameters of key to the hash - * INSECURE: PRIVATE INFORMATIONS CAN LEAK OUT!!! - * Don't use :-)) (I's up to you) - */ +#ifndef HAVE_EVP_PKEY_TODATA +/* :nodoc: */ static VALUE -ossl_dsa_get_params(VALUE self) +ossl_dsa_to_data(VALUE self) { DSA *dsa; VALUE hash; @@ -274,14 +268,15 @@ ossl_dsa_get_params(VALUE self) DSA_get0_key(dsa, &pub_key, &priv_key); hash = rb_hash_new(); - rb_hash_aset(hash, rb_str_new2("p"), ossl_bn_new(p)); - rb_hash_aset(hash, rb_str_new2("q"), ossl_bn_new(q)); - rb_hash_aset(hash, rb_str_new2("g"), ossl_bn_new(g)); - rb_hash_aset(hash, rb_str_new2("pub_key"), ossl_bn_new(pub_key)); - rb_hash_aset(hash, rb_str_new2("priv_key"), ossl_bn_new(priv_key)); + rb_hash_aset(hash, ID2SYM(rb_intern("p")), p ? ossl_bn_new(p) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("q")), q ? ossl_bn_new(q) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("g")), g ? ossl_bn_new(g) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("pub")), pub_key ? ossl_bn_new(pub_key) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("priv")), priv_key ? ossl_bn_new(priv_key) : Qnil); return hash; } +#endif /* * Document-method: OpenSSL::PKey::DSA#set_pqg @@ -348,7 +343,9 @@ Init_ossl_dsa(void) rb_define_method(cDSA, "set_pqg", ossl_dsa_set_pqg, 3); rb_define_method(cDSA, "set_key", ossl_dsa_set_key, 2); - rb_define_method(cDSA, "params", ossl_dsa_get_params, 0); +#ifndef HAVE_EVP_PKEY_TODATA + rb_define_method(cDSA, "to_data", ossl_dsa_to_data, 0); +#endif } #else /* defined NO_DSA */ diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index dee215447..9831c315c 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -500,6 +500,31 @@ static VALUE ossl_ec_key_check_key(VALUE self) return Qtrue; } +#ifndef HAVE_EVP_PKEY_TODATA +/* :nodoc: */ +static VALUE +ossl_ec_key_to_data(VALUE self) +{ + EC_KEY *ec; + const EC_GROUP *group; + const BIGNUM *priv; + int nid; + VALUE hash; + + /* FIXME: INCOMPLETE: What about non-named curves? Other components? */ + GetEC(self, ec); + group = EC_KEY_get0_group(ec); + nid = EC_GROUP_get_curve_name(group); + priv = EC_KEY_get0_private_key(ec); + + hash = rb_hash_new(); + rb_hash_aset(hash, ID2SYM(rb_intern("group")), rb_str_new2(OBJ_nid2sn(nid))); + rb_hash_aset(hash, ID2SYM(rb_intern("priv")), priv ? ossl_bn_new(priv) : Qnil); + + return hash; +} +#endif + /* * OpenSSL::PKey::EC::Group */ @@ -1570,6 +1595,9 @@ void Init_ossl_ec(void) rb_define_alias(cEC, "to_pem", "export"); rb_define_method(cEC, "to_der", ossl_ec_key_to_der, 0); +#ifndef HAVE_EVP_PKEY_TODATA + rb_define_method(cEC, "to_data", ossl_ec_key_to_data, 0); +#endif rb_define_alloc_func(cEC_GROUP, ossl_ec_group_alloc); rb_define_method(cEC_GROUP, "initialize", ossl_ec_group_initialize, -1); diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 4d66010f4..c731ce00d 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -439,19 +439,10 @@ ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) ossl_raise(eRSAError, NULL); } -/* - * call-seq: - * rsa.params => hash - * - * THIS METHOD IS INSECURE, PRIVATE INFORMATION CAN LEAK OUT!!! - * - * Stores all parameters of key to the hash. The hash has keys 'n', 'e', 'd', - * 'p', 'q', 'dmp1', 'dmq1', 'iqmp'. - * - * Don't use :-)) (It's up to you) - */ +#ifndef HAVE_EVP_PKEY_TODATA +/* :nodoc: */ static VALUE -ossl_rsa_get_params(VALUE self) +ossl_rsa_to_data(VALUE self) { RSA *rsa; VALUE hash; @@ -463,17 +454,18 @@ ossl_rsa_get_params(VALUE self) RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); hash = rb_hash_new(); - rb_hash_aset(hash, rb_str_new2("n"), ossl_bn_new(n)); - rb_hash_aset(hash, rb_str_new2("e"), ossl_bn_new(e)); - rb_hash_aset(hash, rb_str_new2("d"), ossl_bn_new(d)); - rb_hash_aset(hash, rb_str_new2("p"), ossl_bn_new(p)); - rb_hash_aset(hash, rb_str_new2("q"), ossl_bn_new(q)); - rb_hash_aset(hash, rb_str_new2("dmp1"), ossl_bn_new(dmp1)); - rb_hash_aset(hash, rb_str_new2("dmq1"), ossl_bn_new(dmq1)); - rb_hash_aset(hash, rb_str_new2("iqmp"), ossl_bn_new(iqmp)); + rb_hash_aset(hash, ID2SYM(rb_intern("n")), n ? ossl_bn_new(n) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("e")), e ? ossl_bn_new(e) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("d")), d ? ossl_bn_new(d) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("rsa-factor1")), p ? ossl_bn_new(p) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("rsa-factor2")), q ? ossl_bn_new(q) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("rsa-exponent1")), dmp1 ? ossl_bn_new(dmp1) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("rsa-exponent2")), dmq1 ? ossl_bn_new(dmq1) : Qnil); + rb_hash_aset(hash, ID2SYM(rb_intern("rsa-coefficient1")), iqmp ? ossl_bn_new(iqmp) : Qnil); return hash; } +#endif /* * Document-method: OpenSSL::PKey::RSA#set_key @@ -562,7 +554,9 @@ Init_ossl_rsa(void) rb_define_method(cRSA, "set_factors", ossl_rsa_set_factors, 2); rb_define_method(cRSA, "set_crt_params", ossl_rsa_set_crt_params, 3); - rb_define_method(cRSA, "params", ossl_rsa_get_params, 0); +#ifndef HAVE_EVP_PKEY_TODATA + rb_define_method(cRSA, "to_data", ossl_rsa_to_data, 0); +#endif /* * TODO: Test it diff --git a/lib/openssl/pkey.rb b/lib/openssl/pkey.rb index c3e062909..447f77884 100644 --- a/lib/openssl/pkey.rb +++ b/lib/openssl/pkey.rb @@ -10,6 +10,17 @@ module OpenSSL::PKey class DH include OpenSSL::Marshal + def params + data = to_data + { + "p" => data[:p] || 0.to_bn, + "q" => data[:q] || 0.to_bn, + "g" => data[:g] || 0.to_bn, + "pub_key" => data[:pub] || 0.to_bn, + "priv_key" => data[:priv] || 0.to_bn, + } + end + # :call-seq: # dh.public_key -> dhnew # @@ -138,6 +149,17 @@ def new(*args, &blk) # :nodoc: class DSA include OpenSSL::Marshal + def params + data = to_data + { + "p" => data[:p] || 0.to_bn, + "q" => data[:q] || 0.to_bn, + "g" => data[:g] || 0.to_bn, + "pub_key" => data[:pub] || 0.to_bn, + "priv_key" => data[:priv] || 0.to_bn, + } + end + # :call-seq: # dsa.public_key -> dsanew # @@ -305,6 +327,20 @@ def to_bn(conversion_form = group.point_conversion_form) class RSA include OpenSSL::Marshal + def params + data = to_data + { + "n" => data[:n] || 0.to_bn, + "e" => data[:e] || 0.to_bn, + "d" => data[:d] || 0.to_bn, + "p" => data[:"rsa-factor1"] || 0.to_bn, + "q" => data[:"rsa-factor2"] || 0.to_bn, + "dmp1" => data[:"rsa-exponent1"] || 0.to_bn, + "dmq1" => data[:"rsa-exponent2"] || 0.to_bn, + "iqmp" => data[:"rsa-coefficient1"] || 0.to_bn, + } + end + # :call-seq: # rsa.public_key -> rsanew # diff --git a/test/openssl/test_pkey_dh.rb b/test/openssl/test_pkey_dh.rb index 161af1897..17d573aa9 100644 --- a/test/openssl/test_pkey_dh.rb +++ b/test/openssl/test_pkey_dh.rb @@ -135,6 +135,15 @@ def test_dup assert_equal [dh3.pub_key, dh3.priv_key], [dh4.pub_key, dh4.priv_key] end + def test_set_components + dh1 = Fixtures.pkey("dh-1") + dh2 = Fixtures.pkey("dh1024") + assert_not_equal dh1.to_der, dh2.to_der + + dh2.set_pqg(dh1.p, dh1.q, dh1.g) + assert_equal dh1.to_der, dh2.to_der + end if !openssl?(3, 0, 0) + def test_marshal dh = Fixtures.pkey("dh1024") deserialized = Marshal.load(Marshal.dump(dh)) @@ -142,6 +151,36 @@ def test_marshal assert_equal dh.to_der, deserialized.to_der end + def test_to_data + # PKCS #3 DH - q does not exist + dh = OpenSSL::PKey.generate_key(Fixtures.pkey("dh1024")) + + # #params + params_keys = %w{p g pub_key priv_key} + params = dh.params + assert_equal [], params_keys - params.keys + + # #to_data; may contain additional entries + data_keys = %w{p g pub priv} + data = dh.to_data + assert_equal [], data_keys.map(&:intern) - data.keys + + # Check value + assert_equal dh.p, params["p"] + assert_equal dh.p, data[:p] + assert_equal 1024, dh.p.num_bits + assert_equal true, dh.p.prime? + + params_keys.each_with_index do |pk, i| + dk = data_keys[i] + getter_value = dh.public_send(pk) + + assert_kind_of OpenSSL::BN, getter_value + assert_equal getter_value, params[pk] + assert_equal getter_value, data[dk.intern] + end + end + private def assert_no_key(dh) diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb index 726b7dbf7..982dce7c5 100644 --- a/test/openssl/test_pkey_dsa.rb +++ b/test/openssl/test_pkey_dsa.rb @@ -223,6 +223,35 @@ def test_marshal assert_equal key.to_der, deserialized.to_der end + def test_to_data + dsa = Fixtures.pkey("dsa1024") + + # #params and gettters + params_keys = %w{p q g pub_key priv_key} + params = dsa.params + assert_equal [], params_keys - params.keys + + # #to_data; may contain additional entries + data_keys = %w{p q g pub priv} + data = dsa.to_data + assert_equal [], data_keys.map(&:intern) - data.keys + + # Check value + assert_equal dsa.p, params["p"] + assert_equal dsa.p, data[:p] + assert_equal 1024, dsa.p.num_bits + assert_equal true, dsa.p.prime? + + params_keys.each_with_index do |pk, i| + dk = data_keys[i] + getter_value = dsa.public_send(pk) + + assert_kind_of OpenSSL::BN, getter_value + assert_equal getter_value, params[pk] + assert_equal getter_value, data[dk.intern] + end + end + private def assert_same_dsa(expected, key) check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key]) diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb index ffe5a94e5..4670c10a4 100644 --- a/test/openssl/test_pkey_ec.rb +++ b/test/openssl/test_pkey_ec.rb @@ -72,6 +72,14 @@ def test_marshal assert_equal key.to_der, deserialized.to_der end + def test_to_data + p256 = Fixtures.pkey("p256") + + data = p256.to_data + assert_equal "prime256v1", data[:group] + assert_equal p256.private_key, data[:priv] + end + def test_check_key key0 = Fixtures.pkey("p256") assert_equal(true, key0.check_key) diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb index 4bb39ed4a..5030af165 100644 --- a/test/openssl/test_pkey_rsa.rb +++ b/test/openssl/test_pkey_rsa.rb @@ -535,6 +535,54 @@ def test_marshal assert_equal key.to_der, deserialized.to_der end + def test_to_data + rsa = Fixtures.pkey("rsa2048") + + # #params + params_keys = %w{n e d p q dmp1 dmq1 iqmp} + params = rsa.params + assert_equal [], params_keys - params.keys + + # #to_data; may contain additional keys + data_keys = %w{n e d rsa-factor1 rsa-factor2 rsa-exponent1 rsa-exponent2 rsa-coefficient1} + data = rsa.to_data + assert_equal [], data_keys.map(&:intern) - data.keys + + # Check value + assert_equal rsa.n, params["n"] + assert_equal rsa.n, data[:n] + assert_equal 2048, rsa.n.num_bits + + params_keys.each_with_index do |pk, i| + dk = data_keys[i] + getter_value = rsa.public_send(pk) + + assert_kind_of OpenSSL::BN, getter_value + assert_equal getter_value, params[pk] + assert_equal getter_value, data[dk.intern] + end + end + + def test_to_data_public + rsa = OpenSSL::PKey.read(Fixtures.pkey("rsa2048").public_to_der) + + # params: missing components will be BN 0 + params = rsa.params + assert_equal %w{n e d p q dmp1 dmq1 iqmp}, params.keys + assert_equal 65537.to_bn, params["e"] + assert_equal 0.to_bn, params["d"] + + # to_data + data = rsa.to_data + assert_equal [], %i{n e} - data.keys + assert_equal nil, data[:d] + assert_equal 65537.to_bn, data[:e] + + # #d returns nil + assert_equal 65537.to_bn, rsa.e + assert_equal nil, rsa.d + end + private def assert_same_rsa(expected, key) check_component(expected, key, [:n, :e, :d, :p, :q, :dmp1, :dmq1, :iqmp]) From fae71c49504d55bf8756a1089d85613a4671aff2 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 22 Apr 2021 19:48:47 +0900 Subject: [PATCH 3/4] pkey: use EVP_PKEY_get_params() to implement key component getters If EVP_PKEY_get_params() family is available, use it to get the key component values. Otherwise, check the return value of #to_data, which should be overriden by the subclasses. --- ext/openssl/ossl_pkey.c | 72 +++++++++++++++++++++++++++++++++++++ ext/openssl/ossl_pkey.h | 36 ------------------- ext/openssl/ossl_pkey_dh.c | 5 --- ext/openssl/ossl_pkey_dsa.c | 5 --- ext/openssl/ossl_pkey_rsa.c | 8 ----- lib/openssl/pkey.rb | 28 +++++++++++++++ 6 files changed, 100 insertions(+), 54 deletions(-) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index e6bade3cd..96e695b14 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -718,6 +718,77 @@ ossl_pkey_to_data(VALUE self) #endif } +/* + * :nodoc: + * + * call-seq: + * pkey.get_bn_param(key, default) -> bn + * + * Retrieves a BIGNUM value associated with a name of +key+. + * + * See also #to_data, which returns all available parameters. + * + * See also the man page EVP_PKEY_get_bn_param(3). + */ +static VALUE +ossl_pkey_get_bn_param(VALUE self, VALUE key) +{ +#ifdef HAVE_EVP_PKEY_TODATA + EVP_PKEY *pkey; + OSSL_PARAM params[2]; + const OSSL_PARAM *gettable, *found; + unsigned char buf[2048]; + BIGNUM *bn; + VALUE ret, tmp = Qnil; + + GetPKey(self, pkey); + ret = ossl_bn_new(NULL); + bn = GetBNPtr(ret); + if (SYMBOL_P(key)) + key = rb_sym2str(key); + + gettable = EVP_PKEY_gettable_params(pkey); + if (!gettable) + ossl_raise(ePKeyError, "EVP_PKEY_gettable_params"); + found = OSSL_PARAM_locate_const(gettable, StringValueCStr(key)); + if (!found) + ossl_raise(ePKeyError, "OSSL_PARAM_locate_const"); + if (found->data_type != OSSL_PARAM_INTEGER && + found->data_type != OSSL_PARAM_UNSIGNED_INTEGER) + ossl_raise(ePKeyError, "OSSL_PARAM_locate_const: unsupported param type"); + + memset(buf, 0, sizeof(buf)); + params[0] = OSSL_PARAM_construct_BN(StringValueCStr(key), buf, sizeof(buf)); + params[1] = OSSL_PARAM_construct_end(); + if (!EVP_PKEY_get_params(pkey, params)) { + size_t size = params[0].return_size; + if (!OSSL_PARAM_modified(params) || size == 0) + return Qnil; + + /* It might have failed because the buffer was too small */ + params[0].data = ALLOCV_N(unsigned char, tmp, size); + params[0].data_size = size; + + if (!EVP_PKEY_get_params(pkey, params) || !OSSL_PARAM_modified(params)) + ossl_raise(ePKeyError, "EVP_PKEY_get_params"); + } + if (!OSSL_PARAM_get_BN(params, &bn)) + ossl_raise(ePKeyError, "OSSL_PARAM_get_BN"); + + return ret; +#else + VALUE ret, hash; + + /* Let's assume the subclass implements #to_data */ + hash = rb_funcall(self, rb_intern("to_data"), 0); + Check_Type(hash, T_HASH); + ret = rb_hash_lookup2(hash, key, Qundef); + if (NIL_P(ret) || rb_obj_is_kind_of(ret, cBN)) + return ret; + rb_raise(ePKeyError, "key not found"); +#endif +} + VALUE ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der) { @@ -1631,6 +1702,7 @@ Init_ossl_pkey(void) rb_define_method(cPKey, "inspect", ossl_pkey_inspect, 0); rb_define_method(cPKey, "to_text", ossl_pkey_to_text, 0); rb_define_method(cPKey, "to_data", ossl_pkey_to_data, 0); + rb_define_method(cPKey, "get_bn_param", ossl_pkey_get_bn_param, 1); rb_define_method(cPKey, "private_to_der", ossl_pkey_private_to_der, -1); rb_define_method(cPKey, "private_to_pem", ossl_pkey_private_to_pem, -1); rb_define_method(cPKey, "public_to_der", ossl_pkey_public_to_der, 0); diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h index 38fb9fad1..6e9d04424 100644 --- a/ext/openssl/ossl_pkey.h +++ b/ext/openssl/ossl_pkey.h @@ -85,37 +85,6 @@ extern VALUE eEC_POINT; VALUE ossl_ec_new(EVP_PKEY *); void Init_ossl_ec(void); -#define OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, _name, _get) \ -/* \ - * call-seq: \ - * _keytype##.##_name -> aBN \ - */ \ -static VALUE ossl_##_keytype##_get_##_name(VALUE self) \ -{ \ - _type *obj; \ - const BIGNUM *bn; \ - \ - Get##_type(self, obj); \ - _get; \ - if (bn == NULL) \ - return Qnil; \ - return ossl_bn_new(bn); \ -} - -#define OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1, \ - _type##_get0_##_group(obj, &bn, NULL, NULL)) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, \ - _type##_get0_##_group(obj, NULL, &bn, NULL)) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a3, \ - _type##_get0_##_group(obj, NULL, NULL, &bn)) - -#define OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1, \ - _type##_get0_##_group(obj, &bn, NULL)) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, \ - _type##_get0_##_group(obj, NULL, &bn)) - #if !OSSL_OPENSSL_PREREQ(3, 0, 0) #define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) \ /* \ @@ -191,14 +160,9 @@ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2) \ #endif #define OSSL_PKEY_BN_DEF3(_keytype, _type, _group, a1, a2, a3) \ - OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) \ OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) #define OSSL_PKEY_BN_DEF2(_keytype, _type, _group, a1, a2) \ - OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2) \ OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) -#define DEF_OSSL_PKEY_BN(class, keytype, name) \ - rb_define_method((class), #name, ossl_##keytype##_get_##name, 0) - #endif /* OSSL_PKEY_H */ diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 97b1730cd..105bc9307 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -416,11 +416,6 @@ Init_ossl_dh(void) rb_define_method(cDH, "to_der", ossl_dh_to_der, 0); rb_define_method(cDH, "params_ok?", ossl_dh_check_params, 0); - DEF_OSSL_PKEY_BN(cDH, dh, p); - DEF_OSSL_PKEY_BN(cDH, dh, q); - DEF_OSSL_PKEY_BN(cDH, dh, g); - DEF_OSSL_PKEY_BN(cDH, dh, pub_key); - DEF_OSSL_PKEY_BN(cDH, dh, priv_key); rb_define_method(cDH, "set_pqg", ossl_dh_set_pqg, 3); rb_define_method(cDH, "set_key", ossl_dh_set_key, 2); diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c index aded059a9..a43f2dfd0 100644 --- a/ext/openssl/ossl_pkey_dsa.c +++ b/ext/openssl/ossl_pkey_dsa.c @@ -335,11 +335,6 @@ Init_ossl_dsa(void) rb_define_alias(cDSA, "to_s", "export"); rb_define_method(cDSA, "to_der", ossl_dsa_to_der, 0); - DEF_OSSL_PKEY_BN(cDSA, dsa, p); - DEF_OSSL_PKEY_BN(cDSA, dsa, q); - DEF_OSSL_PKEY_BN(cDSA, dsa, g); - DEF_OSSL_PKEY_BN(cDSA, dsa, pub_key); - DEF_OSSL_PKEY_BN(cDSA, dsa, priv_key); rb_define_method(cDSA, "set_pqg", ossl_dsa_set_pqg, 3); rb_define_method(cDSA, "set_key", ossl_dsa_set_key, 2); diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index c731ce00d..9edc7c529 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -542,14 +542,6 @@ Init_ossl_rsa(void) rb_define_method(cRSA, "sign_pss", ossl_rsa_sign_pss, -1); rb_define_method(cRSA, "verify_pss", ossl_rsa_verify_pss, -1); - DEF_OSSL_PKEY_BN(cRSA, rsa, n); - DEF_OSSL_PKEY_BN(cRSA, rsa, e); - DEF_OSSL_PKEY_BN(cRSA, rsa, d); - DEF_OSSL_PKEY_BN(cRSA, rsa, p); - DEF_OSSL_PKEY_BN(cRSA, rsa, q); - DEF_OSSL_PKEY_BN(cRSA, rsa, dmp1); - DEF_OSSL_PKEY_BN(cRSA, rsa, dmq1); - DEF_OSSL_PKEY_BN(cRSA, rsa, iqmp); rb_define_method(cRSA, "set_key", ossl_rsa_set_key, 3); rb_define_method(cRSA, "set_factors", ossl_rsa_set_factors, 2); rb_define_method(cRSA, "set_crt_params", ossl_rsa_set_crt_params, 3); diff --git a/lib/openssl/pkey.rb b/lib/openssl/pkey.rb index 447f77884..8390caa2d 100644 --- a/lib/openssl/pkey.rb +++ b/lib/openssl/pkey.rb @@ -21,6 +21,14 @@ def params } end + def p; get_bn_param(:p) end + def q; get_bn_param(:q) end + def g; get_bn_param(:g) end + def pub_key; get_bn_param(:pub) end + def priv_key; get_bn_param(:priv) end + def get_pqg; [p, q, g] end + def get_key; [pub_key, priv_key] end + # :call-seq: # dh.public_key -> dhnew # @@ -160,6 +168,14 @@ def params } end + def p; get_bn_param(:p) end + def q; get_bn_param(:q) end + def g; get_bn_param(:g) end + def pub_key; get_bn_param(:pub) end + def priv_key; get_bn_param(:priv) end + def get_pqg; [p, q, g] end + def get_key; [pub_key, priv_key] end + # :call-seq: # dsa.public_key -> dsanew # @@ -341,6 +357,18 @@ def params } end + def n; get_bn_param(:n) end + def e; get_bn_param(:e) end + def d; get_bn_param(:d) end + def p; get_bn_param(:"rsa-factor1") end + def q; get_bn_param(:"rsa-factor2") end + def dmp1; get_bn_param(:"rsa-exponent1") end + def dmq1; get_bn_param(:"rsa-exponent2") end + def iqmp; get_bn_param(:"rsa-coefficient1") end + def get_key; [n, e, d] end + def get_factors; [p, q] end + def get_crt_params; [dmp1, dmq1, iqmp] end + # :call-seq: # rsa.public_key -> rsanew # From 179c22c8c2455effb9eb6e50de227bc274951feb Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 9 Nov 2021 19:36:05 +0900 Subject: [PATCH 4/4] pkey: from_data wip --- ext/openssl/ossl_pkey.c | 153 ++++++++++++++++++++++++++++++++++ test/openssl/test_pkey_rsa.rb | 6 ++ 2 files changed, 159 insertions(+) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 96e695b14..d2f9b5925 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -13,6 +13,10 @@ # include #endif +#if OSSL_OPENSSL_PREREQ(3, 0, 0) +# include +#endif + /* * Classes */ @@ -180,6 +184,150 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self) return ossl_pkey_new(pkey); } +struct ossl_params_build_args { + const OSSL_PARAM *settable; + VALUE hash, *memo; + OSSL_PARAM_BLD *param_bld; +}; + +static int +ossl_params_set_i(VALUE key, VALUE value, VALUE _args) +{ + struct ossl_params_build_args *args = (void *)_args; + const OSSL_PARAM *p; + int ret; + + if (SYMBOL_P(key)) + key = rb_sym2str(key); + p = OSSL_PARAM_locate_const(args->settable, StringValueCStr(key)); + if (p == NULL) + rb_raise(eOSSLError, "key not settable: %"PRIsVALUE, key); + + switch (p->data_type) { + case OSSL_PARAM_INTEGER: + case OSSL_PARAM_UNSIGNED_INTEGER: + ret = OSSL_PARAM_BLD_push_BN(args->param_bld, p->key, + GetBNPtr(value)); + break; + case OSSL_PARAM_UTF8_STRING: + ret = OSSL_PARAM_BLD_push_utf8_string(args->param_bld, p->key, + StringValueCStr(value), + RSTRING_LEN(value)); + break; + case OSSL_PARAM_OCTET_STRING: + ret = OSSL_PARAM_BLD_push_octet_string(args->param_bld, p->key, + StringValuePtr(value), + RSTRING_LEN(value)); + break; + case OSSL_PARAM_UTF8_PTR: + ret = OSSL_PARAM_BLD_push_utf8_ptr(args->param_bld, p->key, + StringValueCStr(value), + RSTRING_LEN(value)); + if (*args->memo == Qundef) + *args->memo = rb_ary_new(); + rb_ary_push(*args->memo, value); + break; + case OSSL_PARAM_OCTET_PTR: + ret = OSSL_PARAM_BLD_push_utf8_ptr(args->param_bld, p->key, + StringValuePtr(value), + RSTRING_LEN(value)); + if (*args->memo == Qundef) + *args->memo = rb_ary_new(); + rb_ary_push(*args->memo, value); + break; + default: + rb_raise(eOSSLError, "unsupported data type %d for key %s", + (int)p->data_type, p->key); + } + + if (ret <= 0) + ossl_raise(eOSSLError, "OSSL_PARAM_BLD_push*"); + + return ST_CONTINUE; +} + +static VALUE +ossl_params_build(VALUE _args) +{ + struct ossl_params_build_args *args = (void *)_args; + OSSL_PARAM *params; + + args->param_bld = OSSL_PARAM_BLD_new(); + if (args->param_bld == NULL) + ossl_raise(eOSSLError, "OSSL_PARAM_BLD_new"); + + rb_hash_foreach(args->hash, ossl_params_set_i, _args); + + params = OSSL_PARAM_BLD_to_param(args->param_bld); + if (!params) + ossl_raise(eOSSLError, "OSSL_PARAM_BLD_to_params"); + + return (VALUE)params; +} + +static OSSL_PARAM * +ossl_protect_params_build(const OSSL_PARAM *settable, VALUE hash, + VALUE *memo, int *state) +{ + struct ossl_params_build_args args; + OSSL_PARAM *params; + + args.settable = settable; + args.hash = hash; + args.memo = memo; + + params = (void *)rb_protect(ossl_params_build, (VALUE)&args, state); + OSSL_PARAM_BLD_free(args.param_bld); + return params; +} + +/* + * call-seq: + * OpenSSL::PKey.from_data(algo, selection, hash) -> pkey + */ +static VALUE +ossl_pkey_s_from_data(int argc, VALUE *argv, VALUE self) +{ + VALUE type, vselection, hash, memo; + const OSSL_PARAM *settable; + OSSL_PARAM *params; + EVP_PKEY_CTX *pctx; + EVP_PKEY *pkey = NULL; + int selection, state, ret; + + rb_scan_args(argc, argv, "3", &type, &vselection, &hash); + selection = NUM2INT(vselection); + StringValueCStr(type); + Check_Type(hash, T_HASH); + + pctx = EVP_PKEY_CTX_new_from_name(NULL, RSTRING_PTR(type), NULL); + if (!pctx) + ossl_raise(ePKeyError, "EVP_PKEY_CTX_new_from_name"); + settable = EVP_PKEY_fromdata_settable(pctx, selection); + if (!settable) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(ePKeyError, "EVP_PKEY_fromdata_settable"); + } + + params = ossl_protect_params_build(settable, hash, &memo, &state); + if (state) { + EVP_PKEY_CTX_free(pctx); + rb_jump_tag(state); + } + + if (EVP_PKEY_fromdata_init(pctx) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(ePKeyError, "EVP_PKEY_fromdata_init"); + } + ret = EVP_PKEY_fromdata(pctx, &pkey, selection, params); + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(pctx); + + if (ret <= 0) + ossl_raise(ePKeyError, "EVP_PKEY_fromdata"); + return ossl_pkey_new(pkey); +} + static VALUE pkey_ctx_apply_options_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, ctx_v)) { @@ -1687,6 +1835,7 @@ Init_ossl_pkey(void) */ cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); + rb_define_module_function(mPKey, "from_data", ossl_pkey_s_from_data, -1); 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); @@ -1718,6 +1867,10 @@ Init_ossl_pkey(void) rb_define_method(cPKey, "encrypt", ossl_pkey_encrypt, -1); rb_define_method(cPKey, "decrypt", ossl_pkey_decrypt, -1); + rb_define_const(mPKey, "KEY_PARAMETERS", INT2NUM(EVP_PKEY_KEY_PARAMETERS)); + rb_define_const(mPKey, "PUBLIC_KEY", INT2NUM(EVP_PKEY_PUBLIC_KEY)); + rb_define_const(mPKey, "KEYPAIR", INT2NUM(EVP_PKEY_KEYPAIR)); + id_private_q = rb_intern("private?"); /* diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb index 5030af165..abf9286f4 100644 --- a/test/openssl/test_pkey_rsa.rb +++ b/test/openssl/test_pkey_rsa.rb @@ -583,6 +583,12 @@ def test_to_data_public assert_equal nil, rsa.d end + def test_from_data + pkey = Fixtures.pkey("rsa2048") + + rsa1 = OpenSSL::PKey.from_data("RSA", OpenSSL::PKey::KEYPAIR, data) + end + private def assert_same_rsa(expected, key) check_component(expected, key, [:n, :e, :d, :p, :q, :dmp1, :dmq1, :iqmp])