Skip to content

Commit 28f0059

Browse files
committed
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.
1 parent ae19454 commit 28f0059

File tree

4 files changed

+107
-0
lines changed

4 files changed

+107
-0
lines changed

ext/openssl/ossl_pkey.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,57 @@ ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data)
834834
}
835835
}
836836

837+
/*
838+
* call-seq:
839+
* pkey.derive(peer_pkey) -> string
840+
*
841+
* Derives a shared secret from _pkey_ and _peer_pkey_. _pkey_ must contain
842+
* the private components, _peer_pkey_ must contain the public components.
843+
*/
844+
static VALUE
845+
ossl_pkey_derive(int argc, VALUE *argv, VALUE self)
846+
{
847+
EVP_PKEY *pkey, *peer_pkey;
848+
EVP_PKEY_CTX *ctx;
849+
VALUE peer_pkey_obj, str;
850+
size_t keylen;
851+
int state;
852+
853+
GetPKey(self, pkey);
854+
rb_scan_args(argc, argv, "1", &peer_pkey_obj);
855+
GetPKey(peer_pkey_obj, peer_pkey);
856+
857+
ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
858+
if (!ctx)
859+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
860+
if (EVP_PKEY_derive_init(ctx) <= 0) {
861+
EVP_PKEY_CTX_free(ctx);
862+
ossl_raise(ePKeyError, "EVP_PKEY_derive_init");
863+
}
864+
if (EVP_PKEY_derive_set_peer(ctx, peer_pkey) <= 0) {
865+
EVP_PKEY_CTX_free(ctx);
866+
ossl_raise(ePKeyError, "EVP_PKEY_derive_set_peer");
867+
}
868+
if (EVP_PKEY_derive(ctx, NULL, &keylen) <= 0) {
869+
EVP_PKEY_CTX_free(ctx);
870+
ossl_raise(ePKeyError, "EVP_PKEY_derive");
871+
}
872+
if (keylen > LONG_MAX)
873+
rb_raise(ePKeyError, "derived key would be too large");
874+
str = ossl_str_new(NULL, (long)keylen, &state);
875+
if (state) {
876+
EVP_PKEY_CTX_free(ctx);
877+
rb_jump_tag(state);
878+
}
879+
if (EVP_PKEY_derive(ctx, (unsigned char *)RSTRING_PTR(str), &keylen) <= 0) {
880+
EVP_PKEY_CTX_free(ctx);
881+
ossl_raise(ePKeyError, "EVP_PKEY_derive");
882+
}
883+
EVP_PKEY_CTX_free(ctx);
884+
rb_str_set_len(str, keylen);
885+
return str;
886+
}
887+
837888
/*
838889
* INIT
839890
*/
@@ -931,6 +982,7 @@ Init_ossl_pkey(void)
931982

932983
rb_define_method(cPKey, "sign", ossl_pkey_sign, 2);
933984
rb_define_method(cPKey, "verify", ossl_pkey_verify, 3);
985+
rb_define_method(cPKey, "derive", ossl_pkey_derive, -1);
934986

935987
id_private_q = rb_intern("private?");
936988

test/openssl/test_pkey.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,30 @@ def test_ed25519
125125
# Ed25519 pkey type does not support key derivation
126126
assert_raise(OpenSSL::PKey::PKeyError) { priv.derive(pub) }
127127
end
128+
129+
def test_x25519
130+
# Test vector from RFC 7748 Section 6.1
131+
alice_pem = <<~EOF
132+
-----BEGIN PRIVATE KEY-----
133+
MC4CAQAwBQYDK2VuBCIEIHcHbQpzGKV9PBbBclGyZkXfTC+H68CZKrF3+6UduSwq
134+
-----END PRIVATE KEY-----
135+
EOF
136+
bob_pem = <<~EOF
137+
-----BEGIN PUBLIC KEY-----
138+
MCowBQYDK2VuAyEA3p7bfXt9wbTTW2HC7OQ1Nz+DQ8hbeGdNrfx+FG+IK08=
139+
-----END PUBLIC KEY-----
140+
EOF
141+
shared_secret = "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"
142+
begin
143+
alice = OpenSSL::PKey.read(alice_pem)
144+
bob = OpenSSL::PKey.read(bob_pem)
145+
rescue OpenSSL::PKey::PKeyError
146+
# OpenSSL < 1.1.0
147+
pend "X25519 is not implemented"
148+
end
149+
assert_instance_of OpenSSL::PKey::PKey, alice
150+
assert_equal alice_pem, alice.private_to_pem
151+
assert_equal bob_pem, bob.public_to_pem
152+
assert_equal [shared_secret].pack("H*"), alice.derive(bob)
153+
end
128154
end

test/openssl/test_pkey_dh.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ def test_new_break
1818
end
1919
end
2020

21+
def test_derive_key
22+
dh1 = Fixtures.pkey("dh1024").generate_key!
23+
dh2 = Fixtures.pkey("dh1024").generate_key!
24+
dh1_pub = OpenSSL::PKey.read(dh1.public_to_der)
25+
dh2_pub = OpenSSL::PKey.read(dh2.public_to_der)
26+
z = dh1.g.mod_exp(dh1.priv_key, dh1.p).mod_exp(dh2.priv_key, dh1.p).to_s(2)
27+
assert_equal z, dh1.derive(dh2_pub)
28+
assert_equal z, dh2.derive(dh1_pub)
29+
30+
assert_equal z, dh1.compute_key(dh2.pub_key)
31+
assert_equal z, dh2.compute_key(dh1.pub_key)
32+
end
33+
2134
def test_DHparams
2235
dh1024 = Fixtures.pkey("dh1024")
2336
asn1 = OpenSSL::ASN1::Sequence([

test/openssl/test_pkey_ec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,22 @@ def test_sign_verify
9393
assert_equal false, p256.verify("SHA256", signature1, data)
9494
end
9595

96+
def test_derive_key
97+
# NIST CAVP, KAS_ECC_CDH_PrimitiveTest.txt, P-256 COUNT = 0
98+
qCAVSx = "700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287"
99+
qCAVSy = "db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac"
100+
dIUT = "7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534"
101+
zIUT = "46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b"
102+
a = OpenSSL::PKey::EC.new("prime256v1")
103+
a.private_key = OpenSSL::BN.new(dIUT, 16)
104+
b = OpenSSL::PKey::EC.new("prime256v1")
105+
uncompressed = OpenSSL::BN.new("04" + qCAVSx + qCAVSy, 16)
106+
b.public_key = OpenSSL::PKey::EC::Point.new(b.group, uncompressed)
107+
assert_equal [zIUT].pack("H*"), a.derive(b)
108+
109+
assert_equal a.derive(b), a.dh_compute_key(b.public_key)
110+
end
111+
96112
def test_dsa_sign_verify
97113
data1 = "foo"
98114
data2 = "bar"

0 commit comments

Comments
 (0)