Skip to content

Commit 189c167

Browse files
QWYNGrhenium
authored andcommitted
add OpenSSL Provider support
1 parent 58ce7fa commit 189c167

File tree

5 files changed

+289
-0
lines changed

5 files changed

+289
-0
lines changed

ext/openssl/ossl.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,7 @@ Init_openssl(void)
12551255
Init_ossl_x509();
12561256
Init_ossl_ocsp();
12571257
Init_ossl_engine();
1258+
Init_ossl_provider();
12581259
Init_ossl_asn1();
12591260
Init_ossl_kdf();
12601261

ext/openssl/ossl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@
6262
# define OSSL_USE_ENGINE
6363
#endif
6464

65+
#if OSSL_OPENSSL_PREREQ(3, 0, 0)
66+
# define OSSL_USE_PROVIDER
67+
#endif
68+
6569
/*
6670
* Common Module
6771
*/
@@ -188,6 +192,7 @@ extern VALUE dOSSL;
188192
#endif
189193
#include "ossl_x509.h"
190194
#include "ossl_engine.h"
195+
#include "ossl_provider.h"
191196
#include "ossl_kdf.h"
192197

193198
void Init_openssl(void);

ext/openssl/ossl_provider.c

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* This program is licensed under the same licence as Ruby.
3+
* (See the file 'LICENCE'.)
4+
*/
5+
#include "ossl.h"
6+
7+
#ifdef OSSL_USE_PROVIDER
8+
# include <openssl/provider.h>
9+
10+
#define NewProvider(klass) \
11+
TypedData_Wrap_Struct((klass), &ossl_provider_type, 0)
12+
#define SetProvider(obj, provider) do { \
13+
if (!(provider)) { \
14+
ossl_raise(rb_eRuntimeError, "Provider wasn't initialized."); \
15+
} \
16+
RTYPEDDATA_DATA(obj) = (provider); \
17+
} while(0)
18+
#define GetProvider(obj, provider) do { \
19+
TypedData_Get_Struct((obj), OSSL_PROVIDER, &ossl_provider_type, (provider)); \
20+
if (!(provider)) { \
21+
ossl_raise(rb_eRuntimeError, "PROVIDER wasn't initialized."); \
22+
} \
23+
} while (0)
24+
25+
static const rb_data_type_t ossl_provider_type = {
26+
"OpenSSL/Provider",
27+
{
28+
0,
29+
},
30+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
31+
};
32+
33+
/*
34+
* Classes
35+
*/
36+
/* Document-class: OpenSSL::Provider
37+
*
38+
* This class is the access to openssl's Provider
39+
* See also, https://www.openssl.org/docs/manmaster/man7/provider.html
40+
*/
41+
static VALUE cProvider;
42+
/* Document-class: OpenSSL::Provider::ProviderError
43+
*
44+
* This is the generic exception for OpenSSL::Provider related errors
45+
*/
46+
static VALUE eProviderError;
47+
48+
/*
49+
* call-seq:
50+
* OpenSSL::Provider.load(name) -> provider
51+
*
52+
* This method loads and initializes a provider
53+
*/
54+
static VALUE
55+
ossl_provider_s_load(VALUE klass, VALUE name)
56+
{
57+
OSSL_PROVIDER *provider = NULL;
58+
VALUE obj;
59+
60+
const char *provider_name_ptr = StringValueCStr(name);
61+
62+
provider = OSSL_PROVIDER_load(NULL, provider_name_ptr);
63+
if (provider == NULL) {
64+
ossl_raise(eProviderError, "Failed to load %s provider", provider_name_ptr);
65+
}
66+
obj = NewProvider(klass);
67+
SetProvider(obj, provider);
68+
69+
return obj;
70+
}
71+
72+
struct ary_with_state { VALUE ary; int state; };
73+
struct rb_push_provider_name_args { OSSL_PROVIDER *prov; VALUE ary; };
74+
75+
static VALUE
76+
rb_push_provider_name(VALUE rb_push_provider_name_args)
77+
{
78+
struct rb_push_provider_name_args *args = (struct rb_push_provider_name_args *)rb_push_provider_name_args;
79+
80+
VALUE name = rb_str_new2(OSSL_PROVIDER_get0_name(args->prov));
81+
return rb_ary_push(args->ary, name);
82+
}
83+
84+
static int
85+
push_provider(OSSL_PROVIDER *prov, void *cbdata)
86+
{
87+
struct ary_with_state *ary_with_state = (struct ary_with_state *)cbdata;
88+
struct rb_push_provider_name_args args = { prov, ary_with_state->ary };
89+
90+
rb_protect(rb_push_provider_name, (VALUE)&args, &ary_with_state->state);
91+
if (ary_with_state->state) {
92+
return 0;
93+
} else {
94+
return 1;
95+
}
96+
}
97+
98+
/*
99+
* call-seq:
100+
* OpenSSL::Provider.provider_names -> [provider_name, ...]
101+
*
102+
* Returns an array of currently loaded provider names.
103+
*/
104+
static VALUE
105+
ossl_provider_s_provider_names(VALUE klass)
106+
{
107+
VALUE ary = rb_ary_new();
108+
struct ary_with_state cbdata = { ary, 0 };
109+
110+
int result = OSSL_PROVIDER_do_all(NULL, &push_provider, (void*)&cbdata);
111+
if (result != 1 ) {
112+
if (cbdata.state) {
113+
rb_jump_tag(cbdata.state);
114+
} else {
115+
ossl_raise(eProviderError, "Failed to load provider names");
116+
}
117+
}
118+
119+
return ary;
120+
}
121+
122+
/*
123+
* call-seq:
124+
* provider.unload -> true
125+
*
126+
* This method unloads this provider.
127+
*
128+
* if provider unload fails or already unloaded, it raises OpenSSL::Provider::ProviderError
129+
*/
130+
static VALUE
131+
ossl_provider_unload(VALUE self)
132+
{
133+
OSSL_PROVIDER *prov;
134+
if (RTYPEDDATA_DATA(self) == NULL) {
135+
ossl_raise(eProviderError, "Provider already unloaded.");
136+
}
137+
GetProvider(self, prov);
138+
139+
int result = OSSL_PROVIDER_unload(prov);
140+
141+
if (result != 1) {
142+
ossl_raise(eProviderError, "Failed to unload provider");
143+
}
144+
RTYPEDDATA_DATA(self) = NULL;
145+
return Qtrue;
146+
}
147+
148+
/*
149+
* call-seq:
150+
* provider.name -> string
151+
*
152+
* Get the name of this provider.
153+
*
154+
* if this provider is already unloaded, it raises OpenSSL::Provider::ProviderError
155+
*/
156+
static VALUE
157+
ossl_provider_get_name(VALUE self)
158+
{
159+
OSSL_PROVIDER *prov;
160+
if (RTYPEDDATA_DATA(self) == NULL) {
161+
ossl_raise(eProviderError, "Provider already unloaded.");
162+
}
163+
GetProvider(self, prov);
164+
165+
return rb_str_new2(OSSL_PROVIDER_get0_name(prov));
166+
}
167+
168+
/*
169+
* call-seq:
170+
* provider.inspect -> string
171+
*
172+
* Pretty prints this provider.
173+
*/
174+
static VALUE
175+
ossl_provider_inspect(VALUE self)
176+
{
177+
OSSL_PROVIDER *prov;
178+
if (RTYPEDDATA_DATA(self) == NULL ) {
179+
return rb_sprintf("#<%"PRIsVALUE" unloaded provider>", rb_obj_class(self));
180+
}
181+
GetProvider(self, prov);
182+
183+
return rb_sprintf("#<%"PRIsVALUE" name=\"%s\">",
184+
rb_obj_class(self), OSSL_PROVIDER_get0_name(prov));
185+
}
186+
187+
void
188+
Init_ossl_provider(void)
189+
{
190+
#if 0
191+
mOSSL = rb_define_module("OpenSSL");
192+
eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
193+
#endif
194+
195+
cProvider = rb_define_class_under(mOSSL, "Provider", rb_cObject);
196+
eProviderError = rb_define_class_under(cProvider, "ProviderError", eOSSLError);
197+
198+
rb_undef_alloc_func(cProvider);
199+
rb_define_singleton_method(cProvider, "load", ossl_provider_s_load, 1);
200+
rb_define_singleton_method(cProvider, "provider_names", ossl_provider_s_provider_names, 0);
201+
202+
rb_define_method(cProvider, "unload", ossl_provider_unload, 0);
203+
rb_define_method(cProvider, "name", ossl_provider_get_name, 0);
204+
rb_define_method(cProvider, "inspect", ossl_provider_inspect, 0);
205+
}
206+
#else
207+
void
208+
Init_ossl_provider(void)
209+
{
210+
}
211+
#endif

ext/openssl/ossl_provider.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#if !defined(OSSL_PROVIDER_H)
2+
#define OSSL_PROVIDER_H
3+
4+
void Init_ossl_provider(void);
5+
#endif

test/openssl/test_provider.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
require_relative 'utils'
3+
if defined?(OpenSSL) && defined?(OpenSSL::Provider) && !OpenSSL.fips_mode
4+
5+
class OpenSSL::TestProvider < OpenSSL::TestCase
6+
def test_openssl_provider_name_inspect
7+
with_openssl <<-'end;'
8+
provider = OpenSSL::Provider.load("default")
9+
assert_equal("default", provider.name)
10+
assert_not_nil(provider.inspect)
11+
end;
12+
end
13+
14+
def test_openssl_provider_names
15+
with_openssl <<-'end;'
16+
legacy_provider = OpenSSL::Provider.load("legacy")
17+
assert_equal(2, OpenSSL::Provider.provider_names.size)
18+
assert_includes(OpenSSL::Provider.provider_names, "legacy")
19+
20+
assert_equal(true, legacy_provider.unload)
21+
assert_equal(1, OpenSSL::Provider.provider_names.size)
22+
assert_not_includes(OpenSSL::Provider.provider_names, "legacy")
23+
end;
24+
end
25+
26+
def test_unloaded_openssl_provider
27+
with_openssl <<-'end;'
28+
default_provider = OpenSSL::Provider.load("default")
29+
assert_equal(true, default_provider.unload)
30+
assert_raise(OpenSSL::Provider::ProviderError) { default_provider.name }
31+
assert_raise(OpenSSL::Provider::ProviderError) { default_provider.unload }
32+
end;
33+
end
34+
35+
def test_openssl_legacy_provider
36+
with_openssl(<<-'end;')
37+
OpenSSL::Provider.load("legacy")
38+
algo = "RC4"
39+
data = "a" * 1000
40+
key = OpenSSL::Random.random_bytes(16)
41+
42+
# default provider does not support RC4
43+
cipher = OpenSSL::Cipher.new(algo)
44+
cipher.encrypt
45+
cipher.key = key
46+
encrypted = cipher.update(data) + cipher.final
47+
48+
other_cipher = OpenSSL::Cipher.new(algo)
49+
other_cipher.decrypt
50+
other_cipher.key = key
51+
decrypted = other_cipher.update(encrypted) + other_cipher.final
52+
53+
assert_equal(data, decrypted)
54+
end;
55+
end
56+
57+
private
58+
59+
# this is required because OpenSSL::Provider methods change global state
60+
def with_openssl(code, **opts)
61+
assert_separately([{ "OSSL_MDEBUG" => nil }, "-ropenssl"], <<~"end;", **opts)
62+
#{code}
63+
end;
64+
end
65+
end
66+
67+
end

0 commit comments

Comments
 (0)