ruby-changes:65582
From: Kazuki <ko1@a...>
Date: Tue, 16 Mar 2021 20:38:46 +0900 (JST)
Subject: [ruby-changes:65582] fbadb01d6e (master): [ruby/openssl] pkey: add PKey::PKey#derive
https://git.ruby-lang.org/ruby.git/commit/?id=fbadb01d6e From fbadb01d6e0881ef6c6e5e105b8ac20fe663c817 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi <k@r...> Date: Sat, 18 Mar 2017 21:58:46 +0900 Subject: [ruby/openssl] pkey: add PKey::PKey#derive Add OpenSSL::PKey::PKey#derive as the wrapper for EVP_PKEY_CTX_derive(). This is useful for pkey types that we don't have dedicated classes, such as X25519. https://github.com/ruby/openssl/commit/28f0059bea --- ext/openssl/ossl_pkey.c | 52 ++++++++++++++++++++++++++++++++++++++++++++ test/openssl/test_pkey.rb | 26 ++++++++++++++++++++++ test/openssl/test_pkey_dh.rb | 13 +++++++++++ test/openssl/test_pkey_ec.rb | 16 ++++++++++++++ 4 files changed, 107 insertions(+) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 19544ec..df8b425 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -887,6 +887,57 @@ ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_pkey.c#L887 } /* + * call-seq: + * pkey.derive(peer_pkey) -> string + * + * Derives a shared secret from _pkey_ and _peer_pkey_. _pkey_ must contain + * the private components, _peer_pkey_ must contain the public components. + */ +static VALUE +ossl_pkey_derive(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey, *peer_pkey; + EVP_PKEY_CTX *ctx; + VALUE peer_pkey_obj, str; + size_t keylen; + int state; + + GetPKey(self, pkey); + rb_scan_args(argc, argv, "1", &peer_pkey_obj); + GetPKey(peer_pkey_obj, peer_pkey); + + ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); + if (!ctx) + ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); + if (EVP_PKEY_derive_init(ctx) <= 0) { + EVP_PKEY_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_PKEY_derive_init"); + } + if (EVP_PKEY_derive_set_peer(ctx, peer_pkey) <= 0) { + EVP_PKEY_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_PKEY_derive_set_peer"); + } + if (EVP_PKEY_derive(ctx, NULL, &keylen) <= 0) { + EVP_PKEY_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_PKEY_derive"); + } + if (keylen > LONG_MAX) + rb_raise(ePKeyError, "derived key would be too large"); + str = ossl_str_new(NULL, (long)keylen, &state); + if (state) { + EVP_PKEY_CTX_free(ctx); + rb_jump_tag(state); + } + if (EVP_PKEY_derive(ctx, (unsigned char *)RSTRING_PTR(str), &keylen) <= 0) { + EVP_PKEY_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_PKEY_derive"); + } + EVP_PKEY_CTX_free(ctx); + rb_str_set_len(str, keylen); + return str; +} + +/* * INIT */ void @@ -983,6 +1034,7 @@ Init_ossl_pkey(void) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_pkey.c#L1034 rb_define_method(cPKey, "sign", ossl_pkey_sign, 2); rb_define_method(cPKey, "verify", ossl_pkey_verify, 3); + rb_define_method(cPKey, "derive", ossl_pkey_derive, -1); id_private_q = rb_intern("private?"); diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index d811b9c..5307fe5 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -125,4 +125,30 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase https://github.com/ruby/ruby/blob/trunk/test/openssl/test_pkey.rb#L125 # Ed25519 pkey type does not support key derivation assert_raise(OpenSSL::PKey::PKeyError) { priv.derive(pub) } end + + def test_x25519 + # Test vector from RFC 7748 Section 6.1 + alice_pem = <<~EOF + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VuBCIEIHcHbQpzGKV9PBbBclGyZkXfTC+H68CZKrF3+6UduSwq + -----END PRIVATE KEY----- + EOF + bob_pem = <<~EOF + -----BEGIN PUBLIC KEY----- + MCowBQYDK2VuAyEA3p7bfXt9wbTTW2HC7OQ1Nz+DQ8hbeGdNrfx+FG+IK08= + -----END PUBLIC KEY----- + EOF + shared_secret = "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742" + begin + alice = OpenSSL::PKey.read(alice_pem) + bob = OpenSSL::PKey.read(bob_pem) + rescue OpenSSL::PKey::PKeyError + # OpenSSL < 1.1.0 + pend "X25519 is not implemented" + end + assert_instance_of OpenSSL::PKey::PKey, alice + assert_equal alice_pem, alice.private_to_pem + assert_equal bob_pem, bob.public_to_pem + assert_equal [shared_secret].pack("H*"), alice.derive(bob) + end end diff --git a/test/openssl/test_pkey_dh.rb b/test/openssl/test_pkey_dh.rb index 4a05626..9efc3ba 100644 --- a/test/openssl/test_pkey_dh.rb +++ b/test/openssl/test_pkey_dh.rb @@ -18,6 +18,19 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase https://github.com/ruby/ruby/blob/trunk/test/openssl/test_pkey_dh.rb#L18 end end + def test_derive_key + dh1 = Fixtures.pkey("dh1024").generate_key! + dh2 = Fixtures.pkey("dh1024").generate_key! + dh1_pub = OpenSSL::PKey.read(dh1.public_to_der) + dh2_pub = OpenSSL::PKey.read(dh2.public_to_der) + z = dh1.g.mod_exp(dh1.priv_key, dh1.p).mod_exp(dh2.priv_key, dh1.p).to_s(2) + assert_equal z, dh1.derive(dh2_pub) + assert_equal z, dh2.derive(dh1_pub) + + assert_equal z, dh1.compute_key(dh2.pub_key) + assert_equal z, dh2.compute_key(dh1.pub_key) + end + def test_DHparams dh1024 = Fixtures.pkey("dh1024") asn1 = OpenSSL::ASN1::Sequence([ diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb index a0e6a23..95d4338 100644 --- a/test/openssl/test_pkey_ec.rb +++ b/test/openssl/test_pkey_ec.rb @@ -93,6 +93,22 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase https://github.com/ruby/ruby/blob/trunk/test/openssl/test_pkey_ec.rb#L93 assert_equal false, p256.verify("SHA256", signature1, data) end + def test_derive_key + # NIST CAVP, KAS_ECC_CDH_PrimitiveTest.txt, P-256 COUNT = 0 + qCAVSx = "700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287" + qCAVSy = "db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac" + dIUT = "7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534" + zIUT = "46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b" + a = OpenSSL::PKey::EC.new("prime256v1") + a.private_key = OpenSSL::BN.new(dIUT, 16) + b = OpenSSL::PKey::EC.new("prime256v1") + uncompressed = OpenSSL::BN.new("04" + qCAVSx + qCAVSy, 16) + b.public_key = OpenSSL::PKey::EC::Point.new(b.group, uncompressed) + assert_equal [zIUT].pack("H*"), a.derive(b) + + assert_equal a.derive(b), a.dh_compute_key(b.public_key) + end + def test_dsa_sign_verify data1 = "foo" data2 = "bar" -- cgit v1.1 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/