Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9ee96bd
[hpke] initialize OSSL::HPKE::Context
sylph01 Dec 2, 2023
e7e1f17
[hpke] check for openssl/hpke.h
sylph01 Dec 3, 2023
ec8a3a2
[hpke] allocate/initialize context
sylph01 Dec 3, 2023
afe4bff
[hpke-a] experiment: try reopening class with a ruby file
sylph01 Dec 3, 2023
34ea7a3
[hpke] HPKE.keygen
sylph01 Dec 3, 2023
5301f44
[hpke] create HPKE::Context
sylph01 Dec 3, 2023
aaaac90
[hpke] encap
sylph01 Dec 3, 2023
d8b0dda
set constant buffers to a high enough number
sylph01 Dec 3, 2023
d3b0ddf
[hpke] seal
sylph01 Dec 3, 2023
64748ed
[hpke] decap
sylph01 Dec 3, 2023
2f5d645
[hpke] open and export, but getting different values?
sylph01 Dec 3, 2023
024c8f0
[hpke] debug: keygen_pub
sylph01 Dec 3, 2023
3d0b6e3
[hpke] debug: assume fixed ikme
sylph01 Dec 3, 2023
1efad02
lots of debug prints
sylph01 Dec 4, 2023
5411682
[hpke] :tada:
sylph01 Dec 4, 2023
e1f743e
[hpke] fix encap so that it returns a string with correct length
sylph01 Dec 4, 2023
e68494b
[hpke] do not assume fixed ikm_e
sylph01 Dec 4, 2023
012f241
Set buffer size of OSSL_HPKE_keygen to 133
sylph01 Dec 7, 2023
4a1a787
Remove debug method keygen_pub
sylph01 Dec 7, 2023
1f5e54c
Use of HPKE::Suite
sylph01 Dec 7, 2023
0977513
Use OSSL_HPKE_get_ciphertext_size to determine ciphertext size
sylph01 Dec 7, 2023
efb922d
Allocate memory based on OSSL_HPKE_get_public_encap_size
sylph01 Dec 7, 2023
e798bf2
Now that most of the things work, comment out the debug prints
sylph01 Dec 7, 2023
eb533a9
[hpke] macro to remove code when OpenSSL is less than 3.2.0
sylph01 Dec 8, 2023
b73af54
Fix the guards against older OpenSSL
sylph01 Dec 8, 2023
f7c4278
Fix initialize API
sylph01 Dec 8, 2023
b181210
Change class structure, fix initialize API
sylph01 Dec 13, 2023
0212ddb
Apply rename: ossl_pkey_new -> ossl_pkey_wrap
sylph01 Jun 5, 2026
798b929
Re-add include and init
sylph01 Jun 5, 2026
9a7cb45
Ensure String inputs are String with StringValue
sylph01 Jun 5, 2026
e823e6a
Use rb_str_new instead of direct malloc/free
sylph01 Jun 5, 2026
45e9c65
No-op instead of raising inside GC-free
sylph01 Jun 5, 2026
c07515f
Guard against re-initialization
sylph01 Jun 5, 2026
49ec640
Styling fix
sylph01 Jun 5, 2026
cc6686a
Add static to HPKE-internal functions
sylph01 Jun 5, 2026
e15b4d7
Add license header
sylph01 Jun 5, 2026
9d9e453
Remove todo comment
sylph01 Jun 5, 2026
a447ff3
Add test code
sylph01 Jun 5, 2026
fb5d270
Remove conflicting attr_reader
sylph01 Jun 5, 2026
9ba8033
Add propq "fips=yes" when FIPS is enabled
sylph01 Jun 5, 2026
c60f0cf
Change behavior of tests based on FIPS-compatibility
sylph01 Jun 5, 2026
bd4cde3
Omit HPKE altogether under FIPS mode
sylph01 Jun 5, 2026
9ec86d7
Reorder statements in extconf.rb
sylph01 Jun 5, 2026
2f8cf4b
Remove comment
sylph01 Jun 5, 2026
161d7d7
Fix comment
sylph01 Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ext/openssl/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ def find_openssl_library

# added in 3.2.0
have_func("SSL_get0_group_name(NULL)", ssl_h)
have_header("openssl/hpke.h")

# added in 3.4.0
have_func("TS_VERIFY_CTX_set0_certs(NULL, NULL)", ts_h)
Expand Down
1 change: 1 addition & 0 deletions ext/openssl/ossl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,7 @@ Init_openssl(void)
Init_ossl_digest();
Init_ossl_engine();
Init_ossl_hmac();
Init_ossl_hpke_ctx();
Init_ossl_kdf();
Init_ossl_ns_spki();
Init_ossl_ocsp();
Expand Down
4 changes: 4 additions & 0 deletions ext/openssl/ossl.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
#include <openssl/evp.h>
#include <openssl/dh.h>
#include "openssl_missing.h"
#ifdef HAVE_OPENSSL_HPKE_H
#include <openssl/hpke.h>
#endif

#ifndef LIBRESSL_VERSION_NUMBER
# define OSSL_IS_LIBRESSL 0
Expand Down Expand Up @@ -192,6 +195,7 @@ extern VALUE dOSSL;
#include "ossl_digest.h"
#include "ossl_engine.h"
#include "ossl_hmac.h"
#include "ossl_hpke_ctx.h"
#include "ossl_kdf.h"
#include "ossl_ns_spki.h"
#include "ossl_ocsp.h"
Expand Down
315 changes: 315 additions & 0 deletions ext/openssl/ossl_hpke_ctx.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
/*
* Ruby/OpenSSL Project
* Copyright (C) 2026 Ruby/OpenSSL Project Authors
*/
#include "ossl.h"

VALUE mHPKE;
VALUE cContext;
VALUE cSenderContext;
VALUE cReceiverContext;
VALUE eHPKEError;

static void
ossl_hpke_ctx_free(void *ptr)
{
#if OSSL_OPENSSL_PREREQ(3, 2, 0)
OSSL_HPKE_CTX_free(ptr);
#endif
}

/* public */
const rb_data_type_t ossl_hpke_ctx_type = {
"OpenSSL/HPKE_CTX",
{
0, ossl_hpke_ctx_free,
},
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};

static VALUE
ossl_hpke_ctx_new_sender(VALUE self, VALUE mode, VALUE suite)
{
#if !OSSL_OPENSSL_PREREQ(3, 2, 0)
ossl_raise(eHPKEError, "OpenSSL 3.2.0 required");
#else
OSSL_HPKE_CTX *sctx;
VALUE kem_id, kdf_id, aead_id, mode_table, mode_id;

if (RTYPEDDATA_DATA(self))
ossl_raise(eHPKEError, "HPKE context is already initialized");

kem_id = rb_iv_get(suite, "@kem_id");
kdf_id = rb_iv_get(suite, "@kdf_id");
aead_id = rb_iv_get(suite, "@aead_id");

rb_iv_set(self, "@kem_id", kem_id);
rb_iv_set(self, "@kdf_id", kdf_id);
rb_iv_set(self, "@aead_id", aead_id);

OSSL_HPKE_SUITE hpke_suite = {
NUM2INT(kem_id), NUM2INT(kdf_id), NUM2INT(aead_id)
};
mode_table = rb_const_get_at(cContext, rb_intern("MODES"));
mode_id = rb_funcall(mode_table, rb_intern("[]"), 1, mode);

const char *propq = EVP_default_properties_is_fips_enabled(NULL) ? "fips=yes" : NULL;

if((sctx = OSSL_HPKE_CTX_new(NUM2INT(mode_id), hpke_suite, OSSL_HPKE_ROLE_SENDER, NULL, propq)) == NULL) {
ossl_raise(eHPKEError, "could not create ctx");
}

RTYPEDDATA_DATA(self) = sctx;
return self;
#endif
}

static VALUE
ossl_hpke_ctx_new_receiver(VALUE self, VALUE mode, VALUE suite)
{
#if !OSSL_OPENSSL_PREREQ(3, 2, 0)
ossl_raise(eHPKEError, "OpenSSL 3.2.0 required");
#else
OSSL_HPKE_CTX *rctx;
VALUE kem_id, kdf_id, aead_id, mode_table, mode_id;

if (RTYPEDDATA_DATA(self))
ossl_raise(eHPKEError, "HPKE context is already initialized");

kem_id = rb_iv_get(suite, "@kem_id");
kdf_id = rb_iv_get(suite, "@kdf_id");
aead_id = rb_iv_get(suite, "@aead_id");

rb_iv_set(self, "@kem_id", kem_id);
rb_iv_set(self, "@kdf_id", kdf_id);
rb_iv_set(self, "@aead_id", aead_id);

OSSL_HPKE_SUITE hpke_suite = {
NUM2INT(kem_id), NUM2INT(kdf_id), NUM2INT(aead_id)
};
mode_table = rb_const_get_at(cContext, rb_intern("MODES"));
mode_id = rb_funcall(mode_table, rb_intern("[]"), 1, mode);

const char *propq = EVP_default_properties_is_fips_enabled(NULL) ? "fips=yes" : NULL;

if((rctx = OSSL_HPKE_CTX_new(NUM2INT(mode_id), hpke_suite, OSSL_HPKE_ROLE_RECEIVER, NULL, propq)) == NULL) {
ossl_raise(eHPKEError, "could not create ctx");
}

RTYPEDDATA_DATA(self) = rctx;
return self;
#endif
}

static VALUE
ossl_hpke_encap(VALUE self, VALUE pub, VALUE info)
{
#if !OSSL_OPENSSL_PREREQ(3, 2, 0)
ossl_raise(eHPKEError, "OpenSSL 3.2.0 required");
#else
VALUE enc_obj;
size_t enclen;
OSSL_HPKE_CTX *sctx;
size_t publen;
size_t infolen;
OSSL_HPKE_SUITE suite = {
NUM2INT(rb_iv_get(self, "@kem_id")),
NUM2INT(rb_iv_get(self, "@kdf_id")),
NUM2INT(rb_iv_get(self, "@aead_id"))
};

GetHpkeCtx(self, sctx);

StringValue(pub);
StringValue(info);
publen = RSTRING_LEN(pub);
infolen = RSTRING_LEN(info);

enclen = OSSL_HPKE_get_public_encap_size(suite);
enc_obj = rb_str_new(0, enclen);

if (OSSL_HPKE_encap(sctx, (unsigned char *)RSTRING_PTR(enc_obj), &enclen, (unsigned char*)RSTRING_PTR(pub), publen, (unsigned char*)RSTRING_PTR(info), infolen) != 1) {
if (EVP_default_properties_is_fips_enabled(NULL))
ossl_raise(eHPKEError, "could not encap; HPKE is not supported by the FIPS provider");
ossl_raise(eHPKEError, "could not encap");
}

rb_str_resize(enc_obj, enclen);
return enc_obj;
#endif
}

static VALUE
ossl_hpke_seal(VALUE self, VALUE aad, VALUE pt)
{
#if !OSSL_OPENSSL_PREREQ(3, 2, 0)
ossl_raise(eHPKEError, "OpenSSL 3.2.0 required");
#else
VALUE ct_obj;
OSSL_HPKE_CTX *sctx;
OSSL_HPKE_SUITE suite = {
NUM2INT(rb_iv_get(self, "@kem_id")),
NUM2INT(rb_iv_get(self, "@kdf_id")),
NUM2INT(rb_iv_get(self, "@aead_id"))
};
size_t ctlen, aadlen, ptlen;

StringValue(aad);
StringValue(pt);
aadlen = RSTRING_LEN(aad);
ptlen = RSTRING_LEN(pt);
ctlen = OSSL_HPKE_get_ciphertext_size(suite, ptlen);

ct_obj = rb_str_new(0, ctlen);

GetHpkeCtx(self, sctx);

if (OSSL_HPKE_seal(sctx, (unsigned char *)RSTRING_PTR(ct_obj), &ctlen, (unsigned char*)RSTRING_PTR(aad), aadlen, (unsigned char*)RSTRING_PTR(pt), ptlen) != 1) {
ossl_raise(eHPKEError, "could not seal");
}

return ct_obj;
#endif
}

static VALUE
ossl_hpke_decap(VALUE self, VALUE enc, VALUE priv, VALUE info)
{
#if !OSSL_OPENSSL_PREREQ(3, 2, 0)
ossl_raise(eHPKEError, "OpenSSL 3.2.0 required");
#else
OSSL_HPKE_CTX *rctx;
EVP_PKEY *pkey;
size_t enclen;
size_t infolen;

GetHpkeCtx(self, rctx);
GetPKey(priv, pkey);

StringValue(enc);
StringValue(info);
enclen = RSTRING_LEN(enc);
infolen = RSTRING_LEN(info);

if (OSSL_HPKE_decap(rctx, (unsigned char *)RSTRING_PTR(enc), enclen, pkey, (unsigned char *)RSTRING_PTR(info), infolen) != 1) {
if (EVP_default_properties_is_fips_enabled(NULL))
ossl_raise(eHPKEError, "could not decap; HPKE is not supported by the FIPS provider");
ossl_raise(eHPKEError, "could not decap");
}

return Qtrue;
#endif
}

static VALUE
ossl_hpke_open(VALUE self, VALUE aad, VALUE ct)
{
#if !OSSL_OPENSSL_PREREQ(3, 2, 0)
ossl_raise(eHPKEError, "OpenSSL 3.2.0 required");
#else
VALUE pt_obj;
OSSL_HPKE_CTX *rctx;
size_t ptlen, aadlen, ctlen;

StringValue(aad);
StringValue(ct);
aadlen = RSTRING_LEN(aad);
ctlen = RSTRING_LEN(ct);
ptlen = ctlen;

pt_obj = rb_str_new(0, ptlen);

GetHpkeCtx(self, rctx);

if (OSSL_HPKE_open(rctx, (unsigned char *)RSTRING_PTR(pt_obj), &ptlen, (unsigned char*)RSTRING_PTR(aad), aadlen, (unsigned char*)RSTRING_PTR(ct), ctlen) != 1) {
ossl_raise(eHPKEError, "could not open");
}

rb_str_resize(pt_obj, ptlen);

return pt_obj;
#endif
}

static VALUE
ossl_hpke_export(VALUE self, VALUE secretlen, VALUE label)
{
#if !OSSL_OPENSSL_PREREQ(3, 2, 0)
ossl_raise(eHPKEError, "OpenSSL 3.2.0 required");
#else
VALUE secret_obj;
OSSL_HPKE_CTX *ctx;
size_t labellen;

StringValue(label);
labellen = RSTRING_LEN(label);

secret_obj = rb_str_new(0, NUM2INT(secretlen));

GetHpkeCtx(self, ctx);
if (OSSL_HPKE_export(ctx, (unsigned char *)RSTRING_PTR(secret_obj), NUM2INT(secretlen), (unsigned char*)RSTRING_PTR(label), labellen) != 1) {
ossl_raise(eHPKEError, "could not export");
}

return secret_obj;
#endif
}

/* private */
static VALUE
ossl_hpke_ctx_alloc(VALUE klass)
{
return TypedData_Wrap_Struct(klass, &ossl_hpke_ctx_type, NULL);
}

/* HPKE module method */
static VALUE
ossl_hpke_keygen(VALUE self, VALUE kem_id, VALUE kdf_id, VALUE aead_id)
{
#if !OSSL_OPENSSL_PREREQ(3, 2, 0)
ossl_raise(eHPKEError, "OpenSSL 3.2.0 required");
#else
EVP_PKEY *pkey;
VALUE pkey_obj;
unsigned char pub[133]; // as per RFC9180 section 7.1, the maximum size of Npk possible is 133
size_t publen;
OSSL_HPKE_SUITE hpke_suite = {
NUM2INT(kem_id), NUM2INT(kdf_id), NUM2INT(aead_id)
};
publen = 133; // set it to maximum length first, it will shrink down upon call of OSSL_HPKE_keygen

const char *propq = EVP_default_properties_is_fips_enabled(NULL) ? "fips=yes" : NULL;

if(!OSSL_HPKE_keygen(hpke_suite, pub, &publen, &pkey, NULL, 0, NULL, propq)){
ossl_raise(eHPKEError, "could not keygen");
}

pkey_obj = ossl_pkey_wrap(pkey);

return pkey_obj;
#endif
}

void
Init_ossl_hpke_ctx(void)
{
mHPKE = rb_define_module_under(mOSSL, "HPKE");
cContext = rb_define_class_under(mHPKE, "Context", rb_cObject);
cSenderContext = rb_define_class_under(cContext, "Sender", cContext);
cReceiverContext = rb_define_class_under(cContext, "Receiver", cContext);
eHPKEError = rb_define_class_under(mHPKE, "HPKEError", eOSSLError);

rb_define_module_function(mHPKE, "keygen", ossl_hpke_keygen, 3);

rb_define_method(cSenderContext, "initialize", ossl_hpke_ctx_new_sender, 2);
rb_define_method(cSenderContext, "encap", ossl_hpke_encap, 2);
rb_define_method(cSenderContext, "seal", ossl_hpke_seal, 2);

rb_define_method(cReceiverContext, "initialize", ossl_hpke_ctx_new_receiver, 2);
rb_define_method(cReceiverContext, "decap", ossl_hpke_decap, 3);
rb_define_method(cReceiverContext, "open", ossl_hpke_open, 2);

rb_define_method(cContext, "export", ossl_hpke_export, 2);

rb_define_alloc_func(cContext, ossl_hpke_ctx_alloc);
}
23 changes: 23 additions & 0 deletions ext/openssl/ossl_hpke_ctx.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Ruby/OpenSSL Project
* Copyright (C) 2026 Ruby/OpenSSL Project Authors
*/
#if !defined(OSSL_HPKE_CTX_H)
#define OSSL_HPKE_CTX_H

extern VALUE mHPKE;
extern VALUE cContext;
extern const rb_data_type_t ossl_hpke_ctx_type;

#if OSSL_OPENSSL_PREREQ(3, 2, 0)
#define GetHpkeCtx(obj, ctx) do {\
TypedData_Get_Struct((obj), OSSL_HPKE_CTX, &ossl_hpke_ctx_type, (ctx)); \
if (!(ctx)) { \
rb_raise(rb_eRuntimeError, "OSSL_HPKE_CTX wasn't initialized!");\
} \
} while (0)
#endif

void Init_ossl_hpke_ctx(void);

#endif
1 change: 1 addition & 0 deletions lib/openssl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
require_relative 'openssl/ssl'
require_relative 'openssl/version'
require_relative 'openssl/x509'
require_relative 'openssl/hpke'

module OpenSSL
# :call-seq:
Expand Down
Loading
Loading