From f831bb66bcccca4b79a9e61bbf0e6d9ff2d3590d Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 29 Apr 2025 16:30:31 +0900 Subject: [PATCH 1/4] ossl.h: include in ossl.h Move the #include from ossl_provider.c to ossl.h. As OpenSSL 3 provider functions will be used in multiple source files, having it in the common header file is convenient. --- ext/openssl/ossl.h | 1 + ext/openssl/ossl_provider.c | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 9b20829b3..22471d208 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -71,6 +71,7 @@ #if OSSL_OPENSSL_PREREQ(3, 0, 0) # define OSSL_USE_PROVIDER +# include #endif /* diff --git a/ext/openssl/ossl_provider.c b/ext/openssl/ossl_provider.c index d1f6c5d42..529a5e1c7 100644 --- a/ext/openssl/ossl_provider.c +++ b/ext/openssl/ossl_provider.c @@ -5,8 +5,6 @@ #include "ossl.h" #ifdef OSSL_USE_PROVIDER -# include - #define NewProvider(klass) \ TypedData_Wrap_Struct((klass), &ossl_provider_type, 0) #define SetProvider(obj, provider) do { \ From bd3e32270ed8f2c57735035d9451c6118154df28 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 29 Apr 2025 16:34:33 +0900 Subject: [PATCH 2/4] pkey: handle EVP_PKEY_KEYMGMT return by EVP_PKEY_id() For algorithms implemented solely in an OpenSSL 3 provider, without an associated EVP_PKEY_METHOD, EVP_PKEY_id() returns a special value EVP_PKEY_KEYMGMT. Let OpenSSL::PKey::PKey#oid raise an exception as necessary. Update PKey#inspect to include the string returned by EVP_PKEY_get0_type_name(), if available. --- ext/openssl/ossl_pkey.c | 24 +++++++++++++++++++----- test/openssl/test_pkey.rb | 13 +++---------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index dc83255f0..facf2a81f 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -711,6 +711,10 @@ ossl_pkey_oid(VALUE self) GetPKey(self, pkey); nid = EVP_PKEY_id(pkey); +#ifdef OSSL_USE_PROVIDER + if (nid == EVP_PKEY_KEYMGMT) + ossl_raise(ePKeyError, "EVP_PKEY_id"); +#endif return rb_str_new_cstr(OBJ_nid2sn(nid)); } @@ -724,13 +728,23 @@ static VALUE ossl_pkey_inspect(VALUE self) { EVP_PKEY *pkey; - int nid; GetPKey(self, pkey); - nid = EVP_PKEY_id(pkey); - return rb_sprintf("#<%"PRIsVALUE":%p oid=%s>", - rb_class_name(CLASS_OF(self)), (void *)self, - OBJ_nid2sn(nid)); + VALUE str = rb_sprintf("#<%"PRIsVALUE":%p", + rb_obj_class(self), (void *)self); + int nid = EVP_PKEY_id(pkey); +#ifdef OSSL_USE_PROVIDER + if (nid != EVP_PKEY_KEYMGMT) +#endif + rb_str_catf(str, " oid=%s", OBJ_nid2sn(nid)); +#ifdef OSSL_USE_PROVIDER + rb_str_catf(str, " type_name=%s", EVP_PKEY_get0_type_name(pkey)); + const OSSL_PROVIDER *prov = EVP_PKEY_get0_provider(pkey); + if (prov) + rb_str_catf(str, " provider=%s", OSSL_PROVIDER_get0_name(prov)); +#endif + rb_str_catf(str, ">"); + return str; } /* diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 8444cfdcd..83902bb37 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -8,16 +8,7 @@ def test_generic_oid_inspect_rsa assert_instance_of OpenSSL::PKey::RSA, rsa assert_equal "rsaEncryption", rsa.oid assert_match %r{oid=rsaEncryption}, rsa.inspect - end - - def test_generic_oid_inspect_x25519 - omit_on_fips - - # X25519 private key - x25519 = OpenSSL::PKey.generate_key("X25519") - assert_instance_of OpenSSL::PKey::PKey, x25519 - assert_equal "X25519", x25519.oid - assert_match %r{oid=X25519}, x25519.inspect + assert_match %r{type_name=RSA}, rsa.inspect if openssl?(3, 0, 0) end def test_s_generate_parameters @@ -152,6 +143,8 @@ def test_x25519 alice = OpenSSL::PKey.read(alice_pem) bob = OpenSSL::PKey.read(bob_pem) assert_instance_of OpenSSL::PKey::PKey, alice + assert_equal "X25519", alice.oid + assert_match %r{oid=X25519}, alice.inspect 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) From e730e457cc00d240d9cafc60ba6a21793a420cc9 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 6 Jun 2025 02:44:09 +0900 Subject: [PATCH 3/4] pkey: use EVP_PKEY_new_raw_{private,public}_key_ex() if available Algorithms implemented only in OpenSSL 3 providers may not have a corresponding NID. The *_ex() variants have been added in OpenSSL 3.0 to handle such algorithms, by taking algorithm names as a string. --- ext/openssl/ossl_pkey.c | 57 ++++++++++++++++++++++++++++----------- test/openssl/test_pkey.rb | 19 +++++++++++++ 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index facf2a81f..b00a3648d 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -635,6 +635,29 @@ ossl_pkey_initialize_copy(VALUE self, VALUE other) } #endif +#ifndef OSSL_USE_PROVIDER +static int +lookup_pkey_type(VALUE type) +{ + const EVP_PKEY_ASN1_METHOD *ameth; + int pkey_id; + + StringValue(type); + /* + * XXX: EVP_PKEY_asn1_find_str() looks up a PEM type string. Should we use + * OBJ_txt2nid() instead (and then somehow check if the NID is an acceptable + * EVP_PKEY type)? + * It is probably fine, though, since it can handle all algorithms that + * support raw keys in 1.1.1: { X25519, X448, ED25519, ED448, HMAC }. + */ + ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type)); + if (!ameth) + ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type); + EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth); + return pkey_id; +} +#endif + /* * call-seq: * OpenSSL::PKey.new_raw_private_key(algo, string) -> PKey @@ -646,22 +669,23 @@ static VALUE ossl_pkey_new_raw_private_key(VALUE self, VALUE type, VALUE key) { EVP_PKEY *pkey; - const EVP_PKEY_ASN1_METHOD *ameth; - int pkey_id; size_t keylen; - StringValue(type); StringValue(key); - ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type)); - if (!ameth) - ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type); - EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth); - keylen = RSTRING_LEN(key); +#ifdef OSSL_USE_PROVIDER + pkey = EVP_PKEY_new_raw_private_key_ex(NULL, StringValueCStr(type), NULL, + (unsigned char *)RSTRING_PTR(key), + keylen); + if (!pkey) + ossl_raise(ePKeyError, "EVP_PKEY_new_raw_private_key_ex"); +#else + int pkey_id = lookup_pkey_type(type); pkey = EVP_PKEY_new_raw_private_key(pkey_id, NULL, (unsigned char *)RSTRING_PTR(key), keylen); if (!pkey) ossl_raise(ePKeyError, "EVP_PKEY_new_raw_private_key"); +#endif return ossl_pkey_new(pkey); } @@ -677,22 +701,23 @@ static VALUE ossl_pkey_new_raw_public_key(VALUE self, VALUE type, VALUE key) { EVP_PKEY *pkey; - const EVP_PKEY_ASN1_METHOD *ameth; - int pkey_id; size_t keylen; - StringValue(type); StringValue(key); - ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type)); - if (!ameth) - ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type); - EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth); - keylen = RSTRING_LEN(key); +#ifdef OSSL_USE_PROVIDER + pkey = EVP_PKEY_new_raw_public_key_ex(NULL, StringValueCStr(type), NULL, + (unsigned char *)RSTRING_PTR(key), + keylen); + if (!pkey) + ossl_raise(ePKeyError, "EVP_PKEY_new_raw_public_key_ex"); +#else + int pkey_id = lookup_pkey_type(type); pkey = EVP_PKEY_new_raw_public_key(pkey_id, NULL, (unsigned char *)RSTRING_PTR(key), keylen); if (!pkey) ossl_raise(ePKeyError, "EVP_PKEY_new_raw_public_key"); +#endif return ossl_pkey_new(pkey); } diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 83902bb37..71f5da81d 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -161,6 +161,25 @@ def test_x25519 bob.raw_public_key.unpack1("H*") end + def test_ml_dsa + # AWS-LC also supports ML-DSA, but it's implemented in a different way + return unless openssl?(3, 5, 0) + + pkey = OpenSSL::PKey.generate_key("ML-DSA-44") + assert_match(/type_name=ML-DSA-44/, pkey.inspect) + sig = pkey.sign(nil, "data") + assert_equal(2420, sig.bytesize) + assert_equal(true, pkey.verify(nil, sig, "data")) + + pub2 = OpenSSL::PKey.read(pkey.public_to_der) + assert_equal(true, pub2.verify(nil, sig, "data")) + + raw_public_key = pkey.raw_public_key + assert_equal(1312, raw_public_key.bytesize) + pub3 = OpenSSL::PKey.new_raw_public_key("ML-DSA-44", raw_public_key) + assert_equal(true, pub3.verify(nil, sig, "data")) + end + def test_raw_initialize_errors assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("foo123", "xxx") } assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("ED25519", "xxx") } From 6b57cf9cc93a0a4e1b5b89a1bbd27b3566d3711d Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 21 Jun 2025 01:23:28 +0900 Subject: [PATCH 4/4] .github/workflows/test.yml: add a workaround for Windows jobs Currently, RI2 Ruby 3.2 and ruby-loco mswin builds contain OpenSSL 3.4 DLLs while mingw-w64 and vcpkg provide OpenSSL 3.5 headers. Overwrite the builtin DLLs with 3.5 ones so that we can compile and test using the same OpenSSL version. The root cause has been fixed in RI2 Ruby 3.4. --- .github/workflows/test.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7eb1c0b85..627a7b5d8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,6 +36,20 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true # `bundle install` and cache + # See https://github.com/oneclick/rubyinstaller2/issues/60 + # The builtin DLLs are preferred over the mingw-w64/vcpkg DLLs. This is a + # temporary workaround until they update the DLLs to OpenSSL 3.5.x. + - name: Update RI2/mswin builtin DLLs + run: | + $dst = "$((Get-Item (Get-Command ruby).Definition).DirectoryName)\ruby_builtin_dlls" + if ("${{ matrix.ruby }}" -eq "mswin") { + $src = "C:\vcpkg\installed\x64-windows\bin" + } else { + $src = "$((Get-Item (Get-Command ruby).Definition).DirectoryName)\..\msys64\ucrt64\bin" + } + Copy-Item "$src\libcrypto-3-x64.dll", "$src\libssl-3-x64.dll" $dst + if: ${{ matrix.os == 'windows-latest' && (matrix.ruby == '3.2' || matrix.ruby == '3.3' || matrix.ruby == 'mswin') }} + # Enable the verbose option in mkmf.rb to print the compiling commands. - name: enable mkmf verbose run: echo "MAKEFLAGS=V=1" >> $GITHUB_ENV