Skip to content

Commit 9614b7f

Browse files
authored
Merge pull request #898 from rhenium/ky/pkey-prov-only-keys
pkey: add support for OpenSSL 3 provider-only pkeys
2 parents 328d0dc + 6b57cf9 commit 9614b7f

File tree

5 files changed

+97
-33
lines changed

5 files changed

+97
-33
lines changed

.github/workflows/test.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@ jobs:
3636
ruby-version: ${{ matrix.ruby }}
3737
bundler-cache: true # `bundle install` and cache
3838

39+
# See https://github.com/oneclick/rubyinstaller2/issues/60
40+
# The builtin DLLs are preferred over the mingw-w64/vcpkg DLLs. This is a
41+
# temporary workaround until they update the DLLs to OpenSSL 3.5.x.
42+
- name: Update RI2/mswin builtin DLLs
43+
run: |
44+
$dst = "$((Get-Item (Get-Command ruby).Definition).DirectoryName)\ruby_builtin_dlls"
45+
if ("${{ matrix.ruby }}" -eq "mswin") {
46+
$src = "C:\vcpkg\installed\x64-windows\bin"
47+
} else {
48+
$src = "$((Get-Item (Get-Command ruby).Definition).DirectoryName)\..\msys64\ucrt64\bin"
49+
}
50+
Copy-Item "$src\libcrypto-3-x64.dll", "$src\libssl-3-x64.dll" $dst
51+
if: ${{ matrix.os == 'windows-latest' && (matrix.ruby == '3.2' || matrix.ruby == '3.3' || matrix.ruby == 'mswin') }}
52+
3953
# Enable the verbose option in mkmf.rb to print the compiling commands.
4054
- name: enable mkmf verbose
4155
run: echo "MAKEFLAGS=V=1" >> $GITHUB_ENV

ext/openssl/ossl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171

7272
#if OSSL_OPENSSL_PREREQ(3, 0, 0)
7373
# define OSSL_USE_PROVIDER
74+
# include <openssl/provider.h>
7475
#endif
7576

7677
/*

ext/openssl/ossl_pkey.c

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,29 @@ ossl_pkey_initialize_copy(VALUE self, VALUE other)
635635
}
636636
#endif
637637

638+
#ifndef OSSL_USE_PROVIDER
639+
static int
640+
lookup_pkey_type(VALUE type)
641+
{
642+
const EVP_PKEY_ASN1_METHOD *ameth;
643+
int pkey_id;
644+
645+
StringValue(type);
646+
/*
647+
* XXX: EVP_PKEY_asn1_find_str() looks up a PEM type string. Should we use
648+
* OBJ_txt2nid() instead (and then somehow check if the NID is an acceptable
649+
* EVP_PKEY type)?
650+
* It is probably fine, though, since it can handle all algorithms that
651+
* support raw keys in 1.1.1: { X25519, X448, ED25519, ED448, HMAC }.
652+
*/
653+
ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type));
654+
if (!ameth)
655+
ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type);
656+
EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth);
657+
return pkey_id;
658+
}
659+
#endif
660+
638661
/*
639662
* call-seq:
640663
* OpenSSL::PKey.new_raw_private_key(algo, string) -> PKey
@@ -646,22 +669,23 @@ static VALUE
646669
ossl_pkey_new_raw_private_key(VALUE self, VALUE type, VALUE key)
647670
{
648671
EVP_PKEY *pkey;
649-
const EVP_PKEY_ASN1_METHOD *ameth;
650-
int pkey_id;
651672
size_t keylen;
652673

653-
StringValue(type);
654674
StringValue(key);
655-
ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type));
656-
if (!ameth)
657-
ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type);
658-
EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth);
659-
660675
keylen = RSTRING_LEN(key);
661676

677+
#ifdef OSSL_USE_PROVIDER
678+
pkey = EVP_PKEY_new_raw_private_key_ex(NULL, StringValueCStr(type), NULL,
679+
(unsigned char *)RSTRING_PTR(key),
680+
keylen);
681+
if (!pkey)
682+
ossl_raise(ePKeyError, "EVP_PKEY_new_raw_private_key_ex");
683+
#else
684+
int pkey_id = lookup_pkey_type(type);
662685
pkey = EVP_PKEY_new_raw_private_key(pkey_id, NULL, (unsigned char *)RSTRING_PTR(key), keylen);
663686
if (!pkey)
664687
ossl_raise(ePKeyError, "EVP_PKEY_new_raw_private_key");
688+
#endif
665689

666690
return ossl_pkey_new(pkey);
667691
}
@@ -677,22 +701,23 @@ static VALUE
677701
ossl_pkey_new_raw_public_key(VALUE self, VALUE type, VALUE key)
678702
{
679703
EVP_PKEY *pkey;
680-
const EVP_PKEY_ASN1_METHOD *ameth;
681-
int pkey_id;
682704
size_t keylen;
683705

684-
StringValue(type);
685706
StringValue(key);
686-
ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type));
687-
if (!ameth)
688-
ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type);
689-
EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth);
690-
691707
keylen = RSTRING_LEN(key);
692708

709+
#ifdef OSSL_USE_PROVIDER
710+
pkey = EVP_PKEY_new_raw_public_key_ex(NULL, StringValueCStr(type), NULL,
711+
(unsigned char *)RSTRING_PTR(key),
712+
keylen);
713+
if (!pkey)
714+
ossl_raise(ePKeyError, "EVP_PKEY_new_raw_public_key_ex");
715+
#else
716+
int pkey_id = lookup_pkey_type(type);
693717
pkey = EVP_PKEY_new_raw_public_key(pkey_id, NULL, (unsigned char *)RSTRING_PTR(key), keylen);
694718
if (!pkey)
695719
ossl_raise(ePKeyError, "EVP_PKEY_new_raw_public_key");
720+
#endif
696721

697722
return ossl_pkey_new(pkey);
698723
}
@@ -711,6 +736,10 @@ ossl_pkey_oid(VALUE self)
711736

712737
GetPKey(self, pkey);
713738
nid = EVP_PKEY_id(pkey);
739+
#ifdef OSSL_USE_PROVIDER
740+
if (nid == EVP_PKEY_KEYMGMT)
741+
ossl_raise(ePKeyError, "EVP_PKEY_id");
742+
#endif
714743
return rb_str_new_cstr(OBJ_nid2sn(nid));
715744
}
716745

@@ -724,13 +753,23 @@ static VALUE
724753
ossl_pkey_inspect(VALUE self)
725754
{
726755
EVP_PKEY *pkey;
727-
int nid;
728756

729757
GetPKey(self, pkey);
730-
nid = EVP_PKEY_id(pkey);
731-
return rb_sprintf("#<%"PRIsVALUE":%p oid=%s>",
732-
rb_class_name(CLASS_OF(self)), (void *)self,
733-
OBJ_nid2sn(nid));
758+
VALUE str = rb_sprintf("#<%"PRIsVALUE":%p",
759+
rb_obj_class(self), (void *)self);
760+
int nid = EVP_PKEY_id(pkey);
761+
#ifdef OSSL_USE_PROVIDER
762+
if (nid != EVP_PKEY_KEYMGMT)
763+
#endif
764+
rb_str_catf(str, " oid=%s", OBJ_nid2sn(nid));
765+
#ifdef OSSL_USE_PROVIDER
766+
rb_str_catf(str, " type_name=%s", EVP_PKEY_get0_type_name(pkey));
767+
const OSSL_PROVIDER *prov = EVP_PKEY_get0_provider(pkey);
768+
if (prov)
769+
rb_str_catf(str, " provider=%s", OSSL_PROVIDER_get0_name(prov));
770+
#endif
771+
rb_str_catf(str, ">");
772+
return str;
734773
}
735774

736775
/*

ext/openssl/ossl_provider.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
#include "ossl.h"
66

77
#ifdef OSSL_USE_PROVIDER
8-
# include <openssl/provider.h>
9-
108
#define NewProvider(klass) \
119
TypedData_Wrap_Struct((klass), &ossl_provider_type, 0)
1210
#define SetProvider(obj, provider) do { \

test/openssl/test_pkey.rb

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,7 @@ def test_generic_oid_inspect_rsa
88
assert_instance_of OpenSSL::PKey::RSA, rsa
99
assert_equal "rsaEncryption", rsa.oid
1010
assert_match %r{oid=rsaEncryption}, rsa.inspect
11-
end
12-
13-
def test_generic_oid_inspect_x25519
14-
omit_on_fips
15-
16-
# X25519 private key
17-
x25519 = OpenSSL::PKey.generate_key("X25519")
18-
assert_instance_of OpenSSL::PKey::PKey, x25519
19-
assert_equal "X25519", x25519.oid
20-
assert_match %r{oid=X25519}, x25519.inspect
11+
assert_match %r{type_name=RSA}, rsa.inspect if openssl?(3, 0, 0)
2112
end
2213

2314
def test_s_generate_parameters
@@ -152,6 +143,8 @@ def test_x25519
152143
alice = OpenSSL::PKey.read(alice_pem)
153144
bob = OpenSSL::PKey.read(bob_pem)
154145
assert_instance_of OpenSSL::PKey::PKey, alice
146+
assert_equal "X25519", alice.oid
147+
assert_match %r{oid=X25519}, alice.inspect
155148
assert_equal alice_pem, alice.private_to_pem
156149
assert_equal bob_pem, bob.public_to_pem
157150
assert_equal [shared_secret].pack("H*"), alice.derive(bob)
@@ -168,6 +161,25 @@ def test_x25519
168161
bob.raw_public_key.unpack1("H*")
169162
end
170163

164+
def test_ml_dsa
165+
# AWS-LC also supports ML-DSA, but it's implemented in a different way
166+
return unless openssl?(3, 5, 0)
167+
168+
pkey = OpenSSL::PKey.generate_key("ML-DSA-44")
169+
assert_match(/type_name=ML-DSA-44/, pkey.inspect)
170+
sig = pkey.sign(nil, "data")
171+
assert_equal(2420, sig.bytesize)
172+
assert_equal(true, pkey.verify(nil, sig, "data"))
173+
174+
pub2 = OpenSSL::PKey.read(pkey.public_to_der)
175+
assert_equal(true, pub2.verify(nil, sig, "data"))
176+
177+
raw_public_key = pkey.raw_public_key
178+
assert_equal(1312, raw_public_key.bytesize)
179+
pub3 = OpenSSL::PKey.new_raw_public_key("ML-DSA-44", raw_public_key)
180+
assert_equal(true, pub3.verify(nil, sig, "data"))
181+
end
182+
171183
def test_raw_initialize_errors
172184
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("foo123", "xxx") }
173185
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("ED25519", "xxx") }

0 commit comments

Comments
 (0)