Skip to content

Commit ed7e7bb

Browse files
authored
Merge pull request #1062 from bdewater-thatch/pkey-kem-api
Add EVP_PKEY KEM operations
2 parents bd89c50 + f2ec03d commit ed7e7bb

3 files changed

Lines changed: 143 additions & 0 deletions

File tree

‎ext/openssl/extconf.rb‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def find_openssl_library
163163
have_func("EVP_MD_CTX_get_pkey_ctx(NULL)", evp_h)
164164
have_func("EVP_PKEY_eq(NULL, NULL)", evp_h)
165165
have_func("EVP_PKEY_dup(NULL)", evp_h)
166+
have_func("EVP_PKEY_encapsulate_init(NULL, NULL)", evp_h)
166167

167168
# added in 3.2.0
168169
have_func("SSL_get0_group_name(NULL)", ssl_h)

‎ext/openssl/ossl_pkey.c‎

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,122 @@ ossl_pkey_derive(int argc, VALUE *argv, VALUE self)
15141514
return str;
15151515
}
15161516

1517+
#ifdef HAVE_EVP_PKEY_ENCAPSULATE_INIT
1518+
/*
1519+
* call-seq:
1520+
* pkey.encapsulate -> [ciphertext, shared_secret]
1521+
*
1522+
* Performs a key encapsulation operation using the public components of
1523+
* _pkey_.
1524+
*
1525+
* See also the man page EVP_PKEY_encapsulate(3).
1526+
*/
1527+
static VALUE
1528+
ossl_pkey_encapsulate(VALUE self)
1529+
{
1530+
EVP_PKEY *pkey;
1531+
EVP_PKEY_CTX *ctx;
1532+
VALUE ciphertext, shared_secret;
1533+
size_t ciphertextlen, shared_secretlen;
1534+
int state;
1535+
1536+
GetPKey(self, pkey);
1537+
ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
1538+
if (!ctx)
1539+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
1540+
if (EVP_PKEY_encapsulate_init(ctx, NULL) <= 0) {
1541+
EVP_PKEY_CTX_free(ctx);
1542+
ossl_raise(ePKeyError, "EVP_PKEY_encapsulate_init");
1543+
}
1544+
if (EVP_PKEY_encapsulate(ctx, NULL, &ciphertextlen, NULL, &shared_secretlen) <= 0) {
1545+
EVP_PKEY_CTX_free(ctx);
1546+
ossl_raise(ePKeyError, "EVP_PKEY_encapsulate");
1547+
}
1548+
if (ciphertextlen > LONG_MAX || shared_secretlen > LONG_MAX) {
1549+
EVP_PKEY_CTX_free(ctx);
1550+
rb_raise(ePKeyError, "encapsulated data would be too large");
1551+
}
1552+
ciphertext = ossl_str_new(NULL, (long)ciphertextlen, &state);
1553+
if (state) {
1554+
EVP_PKEY_CTX_free(ctx);
1555+
rb_jump_tag(state);
1556+
}
1557+
shared_secret = ossl_str_new(NULL, (long)shared_secretlen, &state);
1558+
if (state) {
1559+
EVP_PKEY_CTX_free(ctx);
1560+
rb_jump_tag(state);
1561+
}
1562+
if (EVP_PKEY_encapsulate(ctx,
1563+
(unsigned char *)RSTRING_PTR(ciphertext),
1564+
&ciphertextlen,
1565+
(unsigned char *)RSTRING_PTR(shared_secret),
1566+
&shared_secretlen) <= 0) {
1567+
EVP_PKEY_CTX_free(ctx);
1568+
ossl_raise(ePKeyError, "EVP_PKEY_encapsulate");
1569+
}
1570+
EVP_PKEY_CTX_free(ctx);
1571+
rb_str_set_len(ciphertext, ciphertextlen);
1572+
rb_str_set_len(shared_secret, shared_secretlen);
1573+
return rb_assoc_new(ciphertext, shared_secret);
1574+
}
1575+
1576+
/*
1577+
* call-seq:
1578+
* pkey.decapsulate(ciphertext) -> shared_secret
1579+
*
1580+
* Performs a key decapsulation operation using the private components of
1581+
* _pkey_.
1582+
*
1583+
* See also the man page EVP_PKEY_decapsulate(3).
1584+
*/
1585+
static VALUE
1586+
ossl_pkey_decapsulate(VALUE self, VALUE ciphertext)
1587+
{
1588+
EVP_PKEY *pkey;
1589+
EVP_PKEY_CTX *ctx;
1590+
VALUE shared_secret;
1591+
size_t shared_secretlen;
1592+
int state;
1593+
1594+
GetPKey(self, pkey);
1595+
StringValue(ciphertext);
1596+
1597+
ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
1598+
if (!ctx)
1599+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
1600+
if (EVP_PKEY_decapsulate_init(ctx, NULL) <= 0) {
1601+
EVP_PKEY_CTX_free(ctx);
1602+
ossl_raise(ePKeyError, "EVP_PKEY_decapsulate_init");
1603+
}
1604+
if (EVP_PKEY_decapsulate(ctx, NULL, &shared_secretlen,
1605+
(unsigned char *)RSTRING_PTR(ciphertext),
1606+
RSTRING_LEN(ciphertext)) <= 0) {
1607+
EVP_PKEY_CTX_free(ctx);
1608+
ossl_raise(ePKeyError, "EVP_PKEY_decapsulate");
1609+
}
1610+
if (shared_secretlen > LONG_MAX) {
1611+
EVP_PKEY_CTX_free(ctx);
1612+
rb_raise(ePKeyError, "decapsulated data would be too large");
1613+
}
1614+
shared_secret = ossl_str_new(NULL, (long)shared_secretlen, &state);
1615+
if (state) {
1616+
EVP_PKEY_CTX_free(ctx);
1617+
rb_jump_tag(state);
1618+
}
1619+
if (EVP_PKEY_decapsulate(ctx,
1620+
(unsigned char *)RSTRING_PTR(shared_secret),
1621+
&shared_secretlen,
1622+
(unsigned char *)RSTRING_PTR(ciphertext),
1623+
RSTRING_LEN(ciphertext)) <= 0) {
1624+
EVP_PKEY_CTX_free(ctx);
1625+
ossl_raise(ePKeyError, "EVP_PKEY_decapsulate");
1626+
}
1627+
EVP_PKEY_CTX_free(ctx);
1628+
rb_str_set_len(shared_secret, shared_secretlen);
1629+
return shared_secret;
1630+
}
1631+
#endif
1632+
15171633
/*
15181634
* call-seq:
15191635
* pkey.encrypt(data [, options]) -> string
@@ -1769,6 +1885,10 @@ Init_ossl_pkey(void)
17691885
rb_define_method(cPKey, "verify_raw", ossl_pkey_verify_raw, -1);
17701886
rb_define_method(cPKey, "verify_recover", ossl_pkey_verify_recover, -1);
17711887
rb_define_method(cPKey, "derive", ossl_pkey_derive, -1);
1888+
#ifdef HAVE_EVP_PKEY_ENCAPSULATE_INIT
1889+
rb_define_method(cPKey, "encapsulate", ossl_pkey_encapsulate, 0);
1890+
rb_define_method(cPKey, "decapsulate", ossl_pkey_decapsulate, 1);
1891+
#endif
17721892
rb_define_method(cPKey, "encrypt", ossl_pkey_encrypt, -1);
17731893
rb_define_method(cPKey, "decrypt", ossl_pkey_decrypt, -1);
17741894

‎test/openssl/test_pkey.rb‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,28 @@ def test_ml_dsa
285285
assert_equal(true, pub3.verify(nil, sig, "data"))
286286
end
287287

288+
def test_ml_kem
289+
# EVP_PKEY KEM APIs were added in OpenSSL 3.0.
290+
omit "ML-KEM is not supported" unless openssl?(3, 5, 0)
291+
292+
pkey = OpenSSL::PKey.generate_key("ML-KEM-768")
293+
raw_public_key = pkey.raw_public_key
294+
raw_private_key = pkey.raw_private_key
295+
296+
assert_match(/type_name=ML-KEM-768/, pkey.inspect)
297+
assert_equal(1184, raw_public_key.bytesize)
298+
assert_equal(2400, raw_private_key.bytesize)
299+
300+
pubkey = OpenSSL::PKey.new_raw_public_key("ML-KEM-768", raw_public_key)
301+
ciphertext, shared_secret = pubkey.encapsulate
302+
assert_equal(1088, ciphertext.bytesize)
303+
assert_equal(32, shared_secret.bytesize)
304+
assert_equal(shared_secret, pkey.decapsulate(ciphertext))
305+
306+
privkey = OpenSSL::PKey.new_raw_private_key("ML-KEM-768", raw_private_key)
307+
assert_equal(shared_secret, privkey.decapsulate(ciphertext))
308+
end
309+
288310
def test_raw_initialize_errors
289311
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("foo123", "xxx") }
290312
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("ED25519", "xxx") }

0 commit comments

Comments
 (0)