Skip to content

Commit

Permalink
pkey: add PKey::PKey#derive
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rhenium committed May 13, 2020
1 parent ae19454 commit 28f0059
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 0 deletions.
52 changes: 52 additions & 0 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,57 @@ ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data)
}
}

/*
* 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
*/
Expand Down Expand Up @@ -931,6 +982,7 @@ Init_ossl_pkey(void)

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?");

Expand Down
26 changes: 26 additions & 0 deletions test/openssl/test_pkey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,30 @@ def test_ed25519
# 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
13 changes: 13 additions & 0 deletions test/openssl/test_pkey_dh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ def test_new_break
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([
Expand Down
16 changes: 16 additions & 0 deletions test/openssl/test_pkey_ec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,22 @@ def test_sign_verify
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"
Expand Down

0 comments on commit 28f0059

Please sign in to comment.