diff options
author | qwyng <ikusawasi@gmail.com> | 2023-06-02 21:12:21 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2023-06-17 03:08:11 +0900 |
commit | 189c167e4033f8923a12432e01f8f11345f22f48 (patch) | |
tree | 3f35f7f68e76a24d0f70d3bbe36d859301700a45 | |
parent | 58ce7fa4b90ca179307142f45687f7d802aca548 (diff) | |
download | ruby-openssl-189c167e4033f8923a12432e01f8f11345f22f48.tar.gz |
add OpenSSL Provider support
-rw-r--r-- | ext/openssl/ossl.c | 1 | ||||
-rw-r--r-- | ext/openssl/ossl.h | 5 | ||||
-rw-r--r-- | ext/openssl/ossl_provider.c | 211 | ||||
-rw-r--r-- | ext/openssl/ossl_provider.h | 5 | ||||
-rw-r--r-- | test/openssl/test_provider.rb | 67 |
5 files changed, 289 insertions, 0 deletions
diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index c3b9f47a..0ffb817f 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1255,6 +1255,7 @@ Init_openssl(void) Init_ossl_x509(); Init_ossl_ocsp(); Init_ossl_engine(); + Init_ossl_provider(); Init_ossl_asn1(); Init_ossl_kdf(); diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 8add914f..68d42b71 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -62,6 +62,10 @@ # define OSSL_USE_ENGINE #endif +#if OSSL_OPENSSL_PREREQ(3, 0, 0) +# define OSSL_USE_PROVIDER +#endif + /* * Common Module */ @@ -188,6 +192,7 @@ extern VALUE dOSSL; #endif #include "ossl_x509.h" #include "ossl_engine.h" +#include "ossl_provider.h" #include "ossl_kdf.h" void Init_openssl(void); diff --git a/ext/openssl/ossl_provider.c b/ext/openssl/ossl_provider.c new file mode 100644 index 00000000..981c6ccd --- /dev/null +++ b/ext/openssl/ossl_provider.c @@ -0,0 +1,211 @@ +/* + * This program is licensed under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +#ifdef OSSL_USE_PROVIDER +# include <openssl/provider.h> + +#define NewProvider(klass) \ + TypedData_Wrap_Struct((klass), &ossl_provider_type, 0) +#define SetProvider(obj, provider) do { \ + if (!(provider)) { \ + ossl_raise(rb_eRuntimeError, "Provider wasn't initialized."); \ + } \ + RTYPEDDATA_DATA(obj) = (provider); \ +} while(0) +#define GetProvider(obj, provider) do { \ + TypedData_Get_Struct((obj), OSSL_PROVIDER, &ossl_provider_type, (provider)); \ + if (!(provider)) { \ + ossl_raise(rb_eRuntimeError, "PROVIDER wasn't initialized."); \ + } \ +} while (0) + +static const rb_data_type_t ossl_provider_type = { + "OpenSSL/Provider", + { + 0, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +/* + * Classes + */ +/* Document-class: OpenSSL::Provider + * + * This class is the access to openssl's Provider + * See also, https://www.openssl.org/docs/manmaster/man7/provider.html + */ +static VALUE cProvider; +/* Document-class: OpenSSL::Provider::ProviderError + * + * This is the generic exception for OpenSSL::Provider related errors + */ +static VALUE eProviderError; + +/* + * call-seq: + * OpenSSL::Provider.load(name) -> provider + * + * This method loads and initializes a provider + */ +static VALUE +ossl_provider_s_load(VALUE klass, VALUE name) +{ + OSSL_PROVIDER *provider = NULL; + VALUE obj; + + const char *provider_name_ptr = StringValueCStr(name); + + provider = OSSL_PROVIDER_load(NULL, provider_name_ptr); + if (provider == NULL) { + ossl_raise(eProviderError, "Failed to load %s provider", provider_name_ptr); + } + obj = NewProvider(klass); + SetProvider(obj, provider); + + return obj; +} + +struct ary_with_state { VALUE ary; int state; }; +struct rb_push_provider_name_args { OSSL_PROVIDER *prov; VALUE ary; }; + +static VALUE +rb_push_provider_name(VALUE rb_push_provider_name_args) +{ + struct rb_push_provider_name_args *args = (struct rb_push_provider_name_args *)rb_push_provider_name_args; + + VALUE name = rb_str_new2(OSSL_PROVIDER_get0_name(args->prov)); + return rb_ary_push(args->ary, name); +} + +static int +push_provider(OSSL_PROVIDER *prov, void *cbdata) +{ + struct ary_with_state *ary_with_state = (struct ary_with_state *)cbdata; + struct rb_push_provider_name_args args = { prov, ary_with_state->ary }; + + rb_protect(rb_push_provider_name, (VALUE)&args, &ary_with_state->state); + if (ary_with_state->state) { + return 0; + } else { + return 1; + } +} + +/* + * call-seq: + * OpenSSL::Provider.provider_names -> [provider_name, ...] + * + * Returns an array of currently loaded provider names. + */ +static VALUE +ossl_provider_s_provider_names(VALUE klass) +{ + VALUE ary = rb_ary_new(); + struct ary_with_state cbdata = { ary, 0 }; + + int result = OSSL_PROVIDER_do_all(NULL, &push_provider, (void*)&cbdata); + if (result != 1 ) { + if (cbdata.state) { + rb_jump_tag(cbdata.state); + } else { + ossl_raise(eProviderError, "Failed to load provider names"); + } + } + + return ary; +} + +/* + * call-seq: + * provider.unload -> true + * + * This method unloads this provider. + * + * if provider unload fails or already unloaded, it raises OpenSSL::Provider::ProviderError + */ +static VALUE +ossl_provider_unload(VALUE self) +{ + OSSL_PROVIDER *prov; + if (RTYPEDDATA_DATA(self) == NULL) { + ossl_raise(eProviderError, "Provider already unloaded."); + } + GetProvider(self, prov); + + int result = OSSL_PROVIDER_unload(prov); + + if (result != 1) { + ossl_raise(eProviderError, "Failed to unload provider"); + } + RTYPEDDATA_DATA(self) = NULL; + return Qtrue; +} + +/* + * call-seq: + * provider.name -> string + * + * Get the name of this provider. + * + * if this provider is already unloaded, it raises OpenSSL::Provider::ProviderError + */ +static VALUE +ossl_provider_get_name(VALUE self) +{ + OSSL_PROVIDER *prov; + if (RTYPEDDATA_DATA(self) == NULL) { + ossl_raise(eProviderError, "Provider already unloaded."); + } + GetProvider(self, prov); + + return rb_str_new2(OSSL_PROVIDER_get0_name(prov)); +} + +/* + * call-seq: + * provider.inspect -> string + * + * Pretty prints this provider. + */ +static VALUE +ossl_provider_inspect(VALUE self) +{ + OSSL_PROVIDER *prov; + if (RTYPEDDATA_DATA(self) == NULL ) { + return rb_sprintf("#<%"PRIsVALUE" unloaded provider>", rb_obj_class(self)); + } + GetProvider(self, prov); + + return rb_sprintf("#<%"PRIsVALUE" name=\"%s\">", + rb_obj_class(self), OSSL_PROVIDER_get0_name(prov)); +} + +void +Init_ossl_provider(void) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); + eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); +#endif + + cProvider = rb_define_class_under(mOSSL, "Provider", rb_cObject); + eProviderError = rb_define_class_under(cProvider, "ProviderError", eOSSLError); + + rb_undef_alloc_func(cProvider); + rb_define_singleton_method(cProvider, "load", ossl_provider_s_load, 1); + rb_define_singleton_method(cProvider, "provider_names", ossl_provider_s_provider_names, 0); + + rb_define_method(cProvider, "unload", ossl_provider_unload, 0); + rb_define_method(cProvider, "name", ossl_provider_get_name, 0); + rb_define_method(cProvider, "inspect", ossl_provider_inspect, 0); +} +#else +void +Init_ossl_provider(void) +{ +} +#endif diff --git a/ext/openssl/ossl_provider.h b/ext/openssl/ossl_provider.h new file mode 100644 index 00000000..1d69cb1e --- /dev/null +++ b/ext/openssl/ossl_provider.h @@ -0,0 +1,5 @@ +#if !defined(OSSL_PROVIDER_H) +#define OSSL_PROVIDER_H + +void Init_ossl_provider(void); +#endif diff --git a/test/openssl/test_provider.rb b/test/openssl/test_provider.rb new file mode 100644 index 00000000..3040a4be --- /dev/null +++ b/test/openssl/test_provider.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true +require_relative 'utils' +if defined?(OpenSSL) && defined?(OpenSSL::Provider) && !OpenSSL.fips_mode + +class OpenSSL::TestProvider < OpenSSL::TestCase + def test_openssl_provider_name_inspect + with_openssl <<-'end;' + provider = OpenSSL::Provider.load("default") + assert_equal("default", provider.name) + assert_not_nil(provider.inspect) + end; + end + + def test_openssl_provider_names + with_openssl <<-'end;' + legacy_provider = OpenSSL::Provider.load("legacy") + assert_equal(2, OpenSSL::Provider.provider_names.size) + assert_includes(OpenSSL::Provider.provider_names, "legacy") + + assert_equal(true, legacy_provider.unload) + assert_equal(1, OpenSSL::Provider.provider_names.size) + assert_not_includes(OpenSSL::Provider.provider_names, "legacy") + end; + end + + def test_unloaded_openssl_provider + with_openssl <<-'end;' + default_provider = OpenSSL::Provider.load("default") + assert_equal(true, default_provider.unload) + assert_raise(OpenSSL::Provider::ProviderError) { default_provider.name } + assert_raise(OpenSSL::Provider::ProviderError) { default_provider.unload } + end; + end + + def test_openssl_legacy_provider + with_openssl(<<-'end;') + OpenSSL::Provider.load("legacy") + algo = "RC4" + data = "a" * 1000 + key = OpenSSL::Random.random_bytes(16) + + # default provider does not support RC4 + cipher = OpenSSL::Cipher.new(algo) + cipher.encrypt + cipher.key = key + encrypted = cipher.update(data) + cipher.final + + other_cipher = OpenSSL::Cipher.new(algo) + other_cipher.decrypt + other_cipher.key = key + decrypted = other_cipher.update(encrypted) + other_cipher.final + + assert_equal(data, decrypted) + end; + end + + private + + # this is required because OpenSSL::Provider methods change global state + def with_openssl(code, **opts) + assert_separately([{ "OSSL_MDEBUG" => nil }, "-ropenssl"], <<~"end;", **opts) + #{code} + end; + end +end + +end |