Add Hybrid Public Key Encryption (HPKE) API Support#1061
Conversation
from now on this needs OpenSSL 3.2 to compile
works only with hpke.h that exposes OSSL_HPKE_CTX
The current longest possible public key size is 133 bytes, according to RFC 9180 section 7.1
- HPKE::Context.new that takes mode, role, and suite - HPKE::Context now keeps track of which KEM/KDF/AEAD it uses under instance variable - HPKE.keygen_with_suite
In this patch I also moved the `attr_reader` definitions of kem/kdf/aead_ids into C code
I am very iffy about this. Is there a safer way to handle this allocation?
The last version was not working.... - Sender and Receiver contexts get different classes - Sender gets only sender APIs, Receiver gets only receiver APIs - Sender and Receiver need Suite to initialize - Removed old Context initialization API
- Remove debug functions - 2 space indentation -> 4 space indentation
It's obvious for people who would edit this code...
|
Thanks for working on this! Regarding the Ruby API:
Do you think if it makes sense to have this as an overload to I'm ambivalent about maintaining our own name list in ruby/openssl. I wonder if we could use
This seems useful to me, since there is no straightforward way to map from HPKE KEM IDs to OpenSSL algorithm object names. However I'm less sure about
That sounds like a good plan to me. |
rhenium
left a comment
There was a problem hiding this comment.
For style, please add newlines at the end of files and break long lines.
| #include "openssl_missing.h" | ||
| #ifdef HAVE_OPENSSL_HPKE_H | ||
| #include <openssl/hpke.h> | ||
| #endif |
There was a problem hiding this comment.
This can be moved to ossl_hpke_ctx.c since it's the only file requiring this.
(We probably should do the same to pkcs12.h/pkcs7.h/ts.h/ocsp.h/etc.)
| 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; |
There was a problem hiding this comment.
EVP_default_properties_is_fips_enabled() == 1 enables "fips=yes" as the default query so that it doesn't have to be explicitly specified.
I think we should not need any FIPS-mode-specific handling in the extension.
| 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 |
There was a problem hiding this comment.
These can be local to the .c file.
| class Context | ||
| # supports only base mode for now | ||
| MODES = { | ||
| base: 0x00 | ||
| }.freeze | ||
|
|
||
| attr_reader :kem_id, :kdf_id, :aead_id | ||
| end | ||
|
|
||
| class Suite | ||
| attr_reader :kem_id, :kdf_id, :aead_id |
There was a problem hiding this comment.
Can this class be moved to the extension? I'd prefer not to introduce circular dependency between openssl.so and openssl.rb.
| static void | ||
| ossl_hpke_ctx_free(void *ptr) | ||
| { | ||
| #if OSSL_OPENSSL_PREREQ(3, 2, 0) |
There was a problem hiding this comment.
If no useful feature can be provided, OpenSSL::HPKE should not be defined at all. Please take a look at ossl_ocsp.c as an example.
Also HAVE_OPENSSL_HPKE_H should be used instead of the version number, in case it is merged to LibreSSL or AWS-LC.
| { | ||
| 0, ossl_hpke_ctx_free, | ||
| }, | ||
| 0, 0, RUBY_TYPED_FREE_IMMEDIATELY |
There was a problem hiding this comment.
| 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | |
| 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, |
| StringValue(label); | ||
| labellen = RSTRING_LEN(label); | ||
|
|
||
| secret_obj = rb_str_new(0, NUM2INT(secretlen)); |
There was a problem hiding this comment.
Nitpicking, but NUM2INT(secretlen) can invoke Ruby code (secretlen.to_int) and potentially mutate label or return an unstable value.
Also, Ruby Strings use long as the length, so NUM2LONG() is probably better fit here, though it likely doesn't matter in practice. It also calls .to_int.
| #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 |
There was a problem hiding this comment.
The list in RFC 9180 appears to be the initial content of the registry and I'd expect OpenSSL to implement new KEMs in the future, once published as an RFC: https://www.iana.org/assignments/hpke/hpke.xhtml
Is there any way to avoid hardcoding the maximum size?
This patch introduces Hybrid Public Key Encryption (HPKE; RFC 9180) through OpenSSL's HPKE APIs ( https://docs.openssl.org/3.5/man3/OSSL_HPKE_CTX_new/ ), added in OpenSSL 3.2.0.
Usage
APIs
OpenSSL::HPKE::Suitenew: Instantiate cipher suite with KEM, KDF, and AEAD identifiers listed in RFC 9180new_with_names: Instantiate cipher suite with pre-defined names. Uses the list of KEMs, KDFs, AEADs listed in RFC 9180.OpenSSL::HPKEkeygen: GenerateOpenSSL::PKeyprivate key with the specified KEM, KDF, and AEAD ID.OSSL_HPKE_keygen()API.keygen_with_suite: GenerateOpenSSL::PKeyprivate key with the specified cipher suiteThese are more like utility functions so if they look extraneous they can be removed in favor of using
OpenSSL::PKeyto generate corresponding keys.OpenSSL::HPKE::Context::SenderandOpenSSL::HPKE::Context::Receivernew: Instantiate HPKE Context.:basemode only; I wanted to let the maintainers see this pull request before adding:auth,:psk, and:auth_pskmodesOpenSSL::HPKE::Context::Senderencap: Encapsulates key into the specified public key. Takes receiver's public key andinfo(application context information)seal: Using the encapsulated key, seal message into ciphertext. Takesaad(additional authenticated data) and ciphertext itself.OpenSSL::HPKE::Context::Receiverdecap: Decapsulates the key using the private key. Takes the encapsulation, private key, andinfo(application context information).open: Using the decapsulated key, decrypt the ciphertext. Takesaadand ciphertext.Availability