diff options
64 files changed, 1469 insertions, 863 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e98354e8..b4c59d4e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: engine: cruby-truffleruby - min_version: 2.6 + min_version: 2.7 test: needs: ruby-versions name: >- @@ -86,6 +86,12 @@ jobs: - libressl-3.7.3 # EOL - libressl-3.8.4 - libressl-3.9.1 + fips-enabled: [ false ] + include: + - { os: ubuntu-latest, ruby: "3.0", openssl: openssl-3.0.10, fips-enabled: true, append-configure: 'enable-fips', name-extra: 'fips' } + - { os: ubuntu-latest, ruby: "3.0", openssl: openssl-3.1.2, fips-enabled: true, append-configure: 'enable-fips', name-extra: 'fips' } + - { os: ubuntu-latest, ruby: "3.0", openssl: openssl-head, git: 'https://github.com/openssl/openssl.git', branch: 'master' } + - { os: ubuntu-latest, ruby: "3.0", openssl: openssl-head, git: 'https://github.com/openssl/openssl.git', branch: 'master', fips-enabled: true, append-configure: 'enable-fips', name-extra: 'fips' } steps: - name: repo checkout uses: actions/checkout@v4 @@ -123,6 +129,21 @@ jobs: make -j4 make install_sw + - name: prepare openssl fips + run: make install_fips + working-directory: tmp/build-openssl/${{ matrix.openssl }} + if: matrix.fips-enabled + + - name: set the open installed directory + run: > + sed -e "s|OPENSSL_DIR|$HOME/.openssl/${{ matrix.openssl }}|" + tool/openssl_fips.cnf.tmpl > tmp/openssl_fips.cnf + if: matrix.fips-enabled + + - name: set openssl config file path for fips. + run: echo "OPENSSL_CONF=$(pwd)/tmp/openssl_fips.cnf" >> $GITHUB_ENV + if: matrix.fips-enabled + - name: load ruby uses: ruby/setup-ruby@v1 with: @@ -145,3 +166,11 @@ jobs: - name: test run: rake test TESTOPTS="-v --no-show-detail-immediately" timeout-minutes: 5 + if: ${{ !matrix.fips-enabled }} + + # Run only the passing tests on the FIPS module as a temporary workaround. + # TODO Fix other tests, and run all the tests on FIPS module. + - name: test on fips module + run: | + rake test_fips TESTOPTS="-v --no-show-detail-immediately" + if: matrix.fips-enabled @@ -6,4 +6,7 @@ group :development do gem "rake" gem "rake-compiler" gem "test-unit", "~> 3.0", ">= 3.4.6" + gem "test-unit-ruby-core" + # In the case of Ruby whose rdoc is not a default gem. + gem "rdoc" end @@ -1,3 +1,43 @@ +Version 3.2.0 +============= + +Compatibility +------------- + +* Ruby >= 2.7 + - Support for Ruby 2.6 has been removed. Note that Ruby 2.6 reached the + end-of-life in 2022-04. + [[GitHub #639]](https://github.com/ruby/openssl/pull/639) +* OpenSSL >= 1.0.2 or LibreSSL >= 3.1 + +Notable changes +--------------- + +* Add a stub gemspec for JRuby, which depends on the `jruby-openssl` gem. + [[GitHub #598]](https://github.com/ruby/openssl/pull/598) +* Add support for the FIPS module in OpenSSL 3.0/3.1. + [[GitHub #608]](https://github.com/ruby/openssl/pull/608) +* Rework `OpenSSL::PKey` routines for loading DER or PEM encoded keys for better + compatibility with OpenSSL 3.0/3.1 with the FIPS module. + [[GitHub #615]](https://github.com/ruby/openssl/pull/615) + [[GitHub #669]](https://github.com/ruby/openssl/pull/669) +* Add `OpenSSL::Provider` module for loading and unloading OpenSSL 3 providers. + [[GitHub #635]](https://github.com/ruby/openssl/pull/635) +* Add `OpenSSL::PKey.new_raw_private_key`, `.new_raw_public_key`, + `OpenSSL::PKey::PKey#raw_private_key`, and `#raw_public_key` for public key + algorithms that use "raw private/public key", such as X25519 and Ed25519. + [[GitHub #646]](https://github.com/ruby/openssl/pull/646) +* Improve OpenSSL error messages to include additional information when + it is available in OpenSSL's error queue. + [[GitHub #648]](https://github.com/ruby/openssl/pull/648) +* Change `OpenSSL::SSL::SSLContext#ca_file=` and `#ca_path=` to raise + `OpenSSL::SSL::SSLError` instead of printing a warning message. + [[GitHub #659]](https://github.com/ruby/openssl/pull/659) +* Allow `OpenSSL::X509::ExtensionFactory#create_extension` to take OIDs in the + dotted-decimal notation. + [[GitHub #141]](https://github.com/ruby/openssl/pull/141) + + Version 3.1.0 ============= @@ -417,7 +457,7 @@ Security fixes Bug fixes --------- -* Fixed OpenSSL::PKey::*.{new,generate} immediately aborting if the thread is +* Fixed OpenSSL::PKey::\*.{new,generate} immediately aborting if the thread is interrupted. [[Bug #14882]](https://bugs.ruby-lang.org/issues/14882) [[GitHub #205]](https://github.com/ruby/openssl/pull/205) @@ -2,26 +2,53 @@ [![Actions Status](https://github.com/ruby/openssl/workflows/CI/badge.svg)](https://github.com/ruby/openssl/actions?workflow=CI) +**OpenSSL for Ruby** provides access to SSL/TLS and general-purpose +cryptography based on the OpenSSL library. -OpenSSL provides SSL, TLS and general purpose cryptography. It wraps the -OpenSSL library. +OpenSSL for Ruby is sometimes referred to as **openssl** in all lowercase +or **Ruby/OpenSSL** for disambiguation. + +## Compatibility and maintenance policy + +OpenSSL for Ruby is released as a RubyGems gem. At the same time, it is part of +the standard library of Ruby. This is called a [default gem]. + +Each stable branch of OpenSSL for Ruby will remain supported as long as it is +included as a default gem in [supported Ruby branches][Ruby Maintenance Branches]. + +|Version|Maintenance status |Ruby compatibility|OpenSSL compatibility | +|-------|-------------------------------|------------------|--------------------------------------------| +|3.2.x |normal maintenance (Ruby 3.3) |Ruby 2.7+ |OpenSSL 1.0.2-3.1 (current) or LibreSSL 3.1+| +|3.1.x |normal maintenance (Ruby 3.2) |Ruby 2.6+ |OpenSSL 1.0.2-3.1 (current) or LibreSSL 3.1+| +|3.0.x |normal maintenance (Ruby 3.1) |Ruby 2.6+ |OpenSSL 1.0.2-3.1 (current) or LibreSSL 3.1+| +|2.2.x |security maintenance (Ruby 3.0)|Ruby 2.3+ |OpenSSL 1.0.1-1.1.1 or LibreSSL 2.9+ | +|2.1.x |end-of-life (Ruby 2.5-2.7) |Ruby 2.3+ |OpenSSL 1.0.1-1.1.1 or LibreSSL 2.5+ | +|2.0.x |end-of-life (Ruby 2.4) |Ruby 2.3+ |OpenSSL 0.9.8-1.1.1 or LibreSSL 2.3+ | + +[default gem]: https://docs.ruby-lang.org/en/master/standard_library_rdoc.html +[Ruby Maintenance Branches]: https://www.ruby-lang.org/en/downloads/branches/ ## Installation -The openssl gem is available at [rubygems.org](https://rubygems.org/gems/openssl). -You can install with: +> **Note** +> The openssl gem is included with Ruby by default, but you may wish to upgrade +> it to a newer version available at +> [rubygems.org](https://rubygems.org/gems/openssl). + +To upgrade it, you can use RubyGems: ``` gem install openssl ``` -You may need to specify the path where OpenSSL is installed. +In some cases, it may be necessary to specify the path to the installation +directory of the OpenSSL library. ``` gem install openssl -- --with-openssl-dir=/opt/openssl ``` -Alternatively, you can install the gem with `bundler`: +Alternatively, you can install the gem with Bundler: ```ruby # Gemfile @@ -30,7 +57,7 @@ gem 'openssl' gem 'openssl', git: 'https://github.com/ruby/openssl' ``` -After doing `bundle install`, you should have the gem installed in your bundle. +After running `bundle install`, you should have the gem installed in your bundle. ## Usage @@ -40,15 +67,6 @@ Once installed, you can require "openssl" in your application. require "openssl" ``` -**NOTE**: If you are using Ruby 2.3 (and not Bundler), you **must** activate -the gem version of openssl, otherwise the default gem packaged with the Ruby -installation will be used: - -```ruby -gem "openssl" -require "openssl" -``` - ## Documentation See https://ruby.github.io/openssl/. @@ -57,10 +75,9 @@ See https://ruby.github.io/openssl/. Please read our [CONTRIBUTING.md] for instructions. +[CONTRIBUTING.md]: https://github.com/ruby/openssl/tree/master/CONTRIBUTING.md + ## Security Security issues should be reported to ruby-core by following the process described on ["Security at ruby-lang.org"](https://www.ruby-lang.org/en/security/). - - -[CONTRIBUTING.md]: https://github.com/ruby/openssl/tree/master/CONTRIBUTING.md @@ -5,6 +5,8 @@ require 'bundler/gem_tasks' begin require 'rake/extensiontask' Rake::ExtensionTask.new('openssl') + # Run the debug_compiler task before the compile task. + Rake::Task['compile'].prerequisites.unshift :debug_compiler rescue LoadError warn "rake-compiler not installed. Run 'bundle install' to " \ "install testing dependency gems." @@ -16,14 +18,60 @@ Rake::TestTask.new do |t| t.warning = true end +desc 'Run tests for fips' +task :test_fips do + ENV['TEST_RUBY_OPENSSL_FIPS_ENABLED'] = 'true' + Rake::Task['test_fips_internal'].invoke +end + +Rake::TestTask.new(:test_fips_internal) do |t| + t.libs << 'test/openssl' + t.test_files = FileList[ + 'test/openssl/test_fips.rb', + 'test/openssl/test_pkey.rb', + 'test/openssl/test_pkey_dh.rb', + 'test/openssl/test_pkey_ec.rb', + ] + t.warning = true +end + RDoc::Task.new do |rdoc| rdoc.main = "README.md" rdoc.rdoc_files.include("*.md", "lib/**/*.rb", "ext/**/*.c") end task :test => [:compile, :debug] +task :test_fips => [:compile, :debug] + +# Print Ruby and compiler info for debugging purpose. +task :debug_compiler do + ruby '-v' + compiler = RbConfig::CONFIG['CC'] + case compiler + when 'gcc', 'clang' + sh "#{compiler} --version" + else + Rake.rake_output_message "Compiler: #{RbConfig::CONFIG['CC']}" + end +end + task :debug do - ruby "-I./lib -ropenssl -ve'puts OpenSSL::OPENSSL_VERSION, OpenSSL::OPENSSL_LIBRARY_VERSION'" + ruby_code = <<~'EOF' + openssl_version_number_str = OpenSSL::OPENSSL_VERSION_NUMBER.to_s(16) + libressl_version_number_str = (defined? OpenSSL::LIBRESSL_VERSION_NUMBER) ? + OpenSSL::LIBRESSL_VERSION_NUMBER.to_s(16) : "undefined" + providers_str = (defined? OpenSSL::Provider) ? + OpenSSL::Provider.provider_names.join(", ") : "undefined" + puts <<~MESSAGE + OpenSSL::OPENSSL_VERSION: #{OpenSSL::OPENSSL_VERSION} + OpenSSL::OPENSSL_LIBRARY_VERSION: #{OpenSSL::OPENSSL_LIBRARY_VERSION} + OpenSSL::OPENSSL_VERSION_NUMBER: #{openssl_version_number_str} + OpenSSL::LIBRESSL_VERSION_NUMBER: #{libressl_version_number_str} + FIPS enabled: #{OpenSSL.fips_mode} + Providers: #{providers_str} + MESSAGE + EOF + ruby %Q(-I./lib -ropenssl -ve'#{ruby_code}') end task :default => :test diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index bc3e4d3a..56f4a1c3 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -13,20 +13,41 @@ require "mkmf" -dir_config_given = dir_config("openssl").any? +ssl_dirs = nil +if defined?(::TruffleRuby) + # Always respect the openssl prefix chosen by truffle/openssl-prefix + require 'truffle/openssl-prefix' + ssl_dirs = dir_config("openssl", ENV["OPENSSL_PREFIX"]) +else + ssl_dirs = dir_config("openssl") +end +dir_config_given = ssl_dirs.any? + +_, ssl_ldir = ssl_dirs +if ssl_ldir&.split(File::PATH_SEPARATOR)&.none? { |dir| File.directory?(dir) } + # According to the `mkmf.rb#dir_config`, the `--with-openssl-dir=<dir>` uses + # the value of the `File.basename(RbConfig::MAKEFILE_CONFIG["libdir"])` as a + # loaded library directory name. + ruby_ldir_name = File.basename(RbConfig::MAKEFILE_CONFIG["libdir"]) + + raise "OpenSSL library directory could not be found in '#{ssl_ldir}'. " \ + "You might want to fix this error in one of the following ways.\n" \ + " * Recompile OpenSSL by configuring it with --libdir=#{ruby_ldir_name} " \ + " to specify the OpenSSL library directory.\n" \ + " * Recompile Ruby by configuring it with --libdir=<dir> to specify the " \ + "Ruby library directory.\n" \ + " * Compile this openssl gem with --with-openssl-include=<dir> and " \ + "--with-openssl-lib=<dir> options to specify the OpenSSL include and " \ + "library directories." +end + dir_config("kerberos") Logging::message "=== OpenSSL for Ruby configurator ===\n" -## -# Adds -DOSSL_DEBUG for compilation and some more targets when GCC is used -# To turn it on, use: --with-debug or --enable-debug -# -if with_config("debug") or enable_config("debug") - $defs.push("-DOSSL_DEBUG") -end $defs.push("-D""OPENSSL_SUPPRESS_DEPRECATED") +have_func("rb_io_descriptor") have_func("rb_io_maybe_wait(0, Qnil, Qnil, Qnil)", "ruby/io.h") # Ruby 3.1 Logging::message "=== Checking for system dependent stuff... ===\n" @@ -191,6 +212,12 @@ have_func("EVP_PKEY_dup(NULL)", evp_h) Logging::message "=== Checking done. ===\n" +# Append flags from environment variables. +extcflags = ENV["RUBY_OPENSSL_EXTCFLAGS"] +append_cflags(extcflags.split) if extcflags +extldflags = ENV["RUBY_OPENSSL_EXTLDFLAGS"] +append_ldflags(extldflags.split) if extldflags + create_header create_makefile("openssl") Logging::message "Done.\n" diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 6c532aca..00eded55 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -207,7 +207,7 @@ ossl_pem_passwd_cb(char *buf, int max_len, int flag, void *pwd_) while (1) { /* - * when the flag is nonzero, this passphrase + * when the flag is nonzero, this password * will be used to perform encryption; otherwise it will * be used to perform decryption. */ @@ -272,23 +272,28 @@ VALUE ossl_make_error(VALUE exc, VALUE str) { unsigned long e; + const char *data; + int flags; - e = ERR_peek_last_error(); + if (NIL_P(str)) + str = rb_str_new(NULL, 0); + +#ifdef HAVE_ERR_GET_ERROR_ALL + e = ERR_peek_last_error_all(NULL, NULL, NULL, &data, &flags); +#else + e = ERR_peek_last_error_line_data(NULL, NULL, &data, &flags); +#endif if (e) { - const char *msg = ERR_reason_error_string(e); + const char *msg = ERR_reason_error_string(e); - if (NIL_P(str)) { - if (msg) str = rb_str_new_cstr(msg); - } - else { - if (RSTRING_LEN(str)) rb_str_cat2(str, ": "); - rb_str_cat2(str, msg ? msg : "(null)"); - } - ossl_clear_error(); + if (RSTRING_LEN(str)) rb_str_cat_cstr(str, ": "); + rb_str_cat_cstr(str, msg ? msg : "(null)"); + if (flags & ERR_TXT_STRING && data) + rb_str_catf(str, " (%s)", data); + ossl_clear_error(); } - if (NIL_P(str)) str = rb_str_new(0, 0); - return rb_exc_new3(exc, str); + return rb_exc_new_str(exc, str); } void @@ -369,22 +374,6 @@ ossl_get_errors(VALUE _) */ VALUE dOSSL; -#if !defined(HAVE_VA_ARGS_MACRO) -void -ossl_debug(const char *fmt, ...) -{ - va_list args; - - if (dOSSL == Qtrue) { - fprintf(stderr, "OSSL_DEBUG: "); - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - fprintf(stderr, " [CONTEXT N/A]\n"); - } -} -#endif - /* * call-seq: * OpenSSL.debug -> true | false @@ -418,7 +407,11 @@ static VALUE ossl_fips_mode_get(VALUE self) { -#ifdef OPENSSL_FIPS +#if OSSL_OPENSSL_PREREQ(3, 0, 0) + VALUE enabled; + enabled = EVP_default_properties_is_fips_enabled(NULL) ? Qtrue : Qfalse; + return enabled; +#elif defined(OPENSSL_FIPS) VALUE enabled; enabled = FIPS_mode() ? Qtrue : Qfalse; return enabled; @@ -442,8 +435,18 @@ ossl_fips_mode_get(VALUE self) static VALUE ossl_fips_mode_set(VALUE self, VALUE enabled) { - -#ifdef OPENSSL_FIPS +#if OSSL_OPENSSL_PREREQ(3, 0, 0) + if (RTEST(enabled)) { + if (!EVP_default_properties_enable_fips(NULL, 1)) { + ossl_raise(eOSSLError, "Turning on FIPS mode failed"); + } + } else { + if (!EVP_default_properties_enable_fips(NULL, 0)) { + ossl_raise(eOSSLError, "Turning off FIPS mode failed"); + } + } + return enabled; +#elif defined(OPENSSL_FIPS) if (RTEST(enabled)) { int mode = FIPS_mode(); if(!mode && !FIPS_mode_set(1)) /* turning on twice leads to an error */ @@ -460,75 +463,6 @@ ossl_fips_mode_set(VALUE self, VALUE enabled) #endif } -#if defined(OSSL_DEBUG) -#if !defined(LIBRESSL_VERSION_NUMBER) && \ - (OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(OPENSSL_NO_CRYPTO_MDEBUG) || \ - defined(CRYPTO_malloc_debug_init)) -/* - * call-seq: - * OpenSSL.mem_check_start -> nil - * - * Calls CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON). Starts tracking memory - * allocations. See also OpenSSL.print_mem_leaks. - * - * This is available only when built with a capable OpenSSL and --enable-debug - * configure option. - */ -static VALUE -mem_check_start(VALUE self) -{ - CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); - return Qnil; -} - -/* - * call-seq: - * OpenSSL.print_mem_leaks -> true | false - * - * For debugging the Ruby/OpenSSL library. Calls CRYPTO_mem_leaks_fp(stderr). - * Prints detected memory leaks to standard error. This cleans the global state - * up thus you cannot use any methods of the library after calling this. - * - * Returns +true+ if leaks detected, +false+ otherwise. - * - * This is available only when built with a capable OpenSSL and --enable-debug - * configure option. - * - * === Example - * OpenSSL.mem_check_start - * NOT_GCED = OpenSSL::PKey::RSA.new(256) - * - * END { - * GC.start - * OpenSSL.print_mem_leaks # will print the leakage - * } - */ -static VALUE -print_mem_leaks(VALUE self) -{ -#if OPENSSL_VERSION_NUMBER >= 0x10100000 - int ret; -#endif - -#ifndef HAVE_RB_EXT_RACTOR_SAFE - // for Ruby 2.x - void ossl_bn_ctx_free(void); // ossl_bn.c - ossl_bn_ctx_free(); -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x10100000 - ret = CRYPTO_mem_leaks_fp(stderr); - if (ret < 0) - ossl_raise(eOSSLError, "CRYPTO_mem_leaks_fp"); - return ret ? Qfalse : Qtrue; -#else - CRYPTO_mem_leaks_fp(stderr); - return Qnil; -#endif -} -#endif -#endif - #if !defined(HAVE_OPENSSL_110_THREADING_API) /** * Stores locks needed for OpenSSL thread safety @@ -671,23 +605,21 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * * key = OpenSSL::PKey::RSA.new 2048 * - * open 'private_key.pem', 'w' do |io| io.write key.to_pem end - * open 'public_key.pem', 'w' do |io| io.write key.public_key.to_pem end + * File.write 'private_key.pem', key.private_to_pem + * File.write 'public_key.pem', key.public_to_pem * * === Exporting a Key * * Keys saved to disk without encryption are not secure as anyone who gets * ahold of the key may use it unless it is encrypted. In order to securely - * export a key you may export it with a pass phrase. + * export a key you may export it with a password. * * cipher = OpenSSL::Cipher.new 'aes-256-cbc' - * pass_phrase = 'my secure pass phrase goes here' + * password = 'my secure password goes here' * - * key_secure = key.export cipher, pass_phrase + * key_secure = key.private_to_pem cipher, password * - * open 'private.secure.pem', 'w' do |io| - * io.write key_secure - * end + * File.write 'private.secure.pem', key_secure * * OpenSSL::Cipher.ciphers returns a list of available ciphers. * @@ -707,13 +639,13 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * * === Loading an Encrypted Key * - * OpenSSL will prompt you for your pass phrase when loading an encrypted key. - * If you will not be able to type in the pass phrase you may provide it when + * OpenSSL will prompt you for your password when loading an encrypted key. + * If you will not be able to type in the password you may provide it when * loading the key: * * key4_pem = File.read 'private.secure.pem' - * pass_phrase = 'my secure pass phrase goes here' - * key4 = OpenSSL::PKey.read key4_pem, pass_phrase + * password = 'my secure password goes here' + * key4 = OpenSSL::PKey.read key4_pem, password * * == RSA Encryption * @@ -829,45 +761,6 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * decrypted = cipher.update encrypted * decrypted << cipher.final * - * == PKCS #5 Password-based Encryption - * - * PKCS #5 is a password-based encryption standard documented at - * RFC2898[http://www.ietf.org/rfc/rfc2898.txt]. It allows a short password or - * passphrase to be used to create a secure encryption key. If possible, PBKDF2 - * as described above should be used if the circumstances allow it. - * - * PKCS #5 uses a Cipher, a pass phrase and a salt to generate an encryption - * key. - * - * pass_phrase = 'my secure pass phrase goes here' - * salt = '8 octets' - * - * === Encryption - * - * First set up the cipher for encryption - * - * encryptor = OpenSSL::Cipher.new 'aes-256-cbc' - * encryptor.encrypt - * encryptor.pkcs5_keyivgen pass_phrase, salt - * - * Then pass the data you want to encrypt through - * - * encrypted = encryptor.update 'top secret document' - * encrypted << encryptor.final - * - * === Decryption - * - * Use a new Cipher instance set up for decryption - * - * decryptor = OpenSSL::Cipher.new 'aes-256-cbc' - * decryptor.decrypt - * decryptor.pkcs5_keyivgen pass_phrase, salt - * - * Then pass the data you want to decrypt through - * - * plain = decryptor.update encrypted - * plain << decryptor.final - * * == X509 Certificates * * === Creating a Certificate @@ -945,12 +838,12 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * not readable by other users. * * ca_key = OpenSSL::PKey::RSA.new 2048 - * pass_phrase = 'my secure pass phrase goes here' + * password = 'my secure password goes here' * - * cipher = OpenSSL::Cipher.new 'aes-256-cbc' + * cipher = 'aes-256-cbc' * * open 'ca_key.pem', 'w', 0400 do |io| - * io.write ca_key.export(cipher, pass_phrase) + * io.write ca_key.private_to_pem(cipher, password) * end * * === CA Certificate @@ -1170,8 +1063,8 @@ Init_openssl(void) /* * Init main module */ - mOSSL = rb_define_module("OpenSSL"); rb_global_variable(&mOSSL); + mOSSL = rb_define_module("OpenSSL"); rb_define_singleton_method(mOSSL, "fixed_length_secure_compare", ossl_crypto_fixed_length_secure_compare, 2); /* @@ -1190,15 +1083,35 @@ Init_openssl(void) /* * Version number of OpenSSL the ruby OpenSSL extension was built with - * (base 16) + * (base 16). The formats are below. + * + * [OpenSSL 3] <tt>0xMNN00PP0 (major minor 00 patch 0)</tt> + * [OpenSSL before 3] <tt>0xMNNFFPPS (major minor fix patch status)</tt> + * [LibreSSL] <tt>0x20000000 (fixed value)</tt> + * + * See also the man page OPENSSL_VERSION_NUMBER(3). */ rb_define_const(mOSSL, "OPENSSL_VERSION_NUMBER", INT2NUM(OPENSSL_VERSION_NUMBER)); +#if defined(LIBRESSL_VERSION_NUMBER) + /* + * Version number of LibreSSL the ruby OpenSSL extension was built with + * (base 16). The format is <tt>0xMNNFF00f (major minor fix 00 + * status)</tt>. This constant is only defined in LibreSSL cases. + * + * See also the man page LIBRESSL_VERSION_NUMBER(3). + */ + rb_define_const(mOSSL, "LIBRESSL_VERSION_NUMBER", INT2NUM(LIBRESSL_VERSION_NUMBER)); +#endif + /* * Boolean indicating whether OpenSSL is FIPS-capable or not */ rb_define_const(mOSSL, "OPENSSL_FIPS", -#ifdef OPENSSL_FIPS +/* OpenSSL 3 is FIPS-capable even when it is installed without fips option */ +#if OSSL_OPENSSL_PREREQ(3, 0, 0) + Qtrue +#elif defined(OPENSSL_FIPS) Qtrue #else Qfalse @@ -1208,12 +1121,12 @@ Init_openssl(void) rb_define_module_function(mOSSL, "fips_mode", ossl_fips_mode_get, 0); rb_define_module_function(mOSSL, "fips_mode=", ossl_fips_mode_set, 1); + rb_global_variable(&eOSSLError); /* * Generic error, * common for all classes under OpenSSL module */ eOSSLError = rb_define_class_under(mOSSL,"OpenSSLError",rb_eStandardError); - rb_global_variable(&eOSSLError); /* * Init debug core @@ -1254,42 +1167,7 @@ Init_openssl(void) Init_ossl_x509(); Init_ossl_ocsp(); Init_ossl_engine(); + Init_ossl_provider(); Init_ossl_asn1(); Init_ossl_kdf(); - -#if defined(OSSL_DEBUG) - /* - * For debugging Ruby/OpenSSL. Enable only when built with --enable-debug - */ -#if !defined(LIBRESSL_VERSION_NUMBER) && \ - (OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(OPENSSL_NO_CRYPTO_MDEBUG) || \ - defined(CRYPTO_malloc_debug_init)) - rb_define_module_function(mOSSL, "mem_check_start", mem_check_start, 0); - rb_define_module_function(mOSSL, "print_mem_leaks", print_mem_leaks, 0); - -#if defined(CRYPTO_malloc_debug_init) /* <= 1.0.2 */ - CRYPTO_malloc_debug_init(); -#endif - -#if defined(V_CRYPTO_MDEBUG_ALL) /* <= 1.0.2 */ - CRYPTO_set_mem_debug_options(V_CRYPTO_MDEBUG_ALL); -#endif - -#if OPENSSL_VERSION_NUMBER < 0x10100000 /* <= 1.0.2 */ - { - int i; - /* - * See crypto/ex_data.c; call def_get_class() immediately to avoid - * allocations. 15 is the maximum number that is used as the class index - * in OpenSSL 1.0.2. - */ - for (i = 0; i <= 15; i++) { - if (CRYPTO_get_ex_new_index(i, 0, (void *)"ossl-mdebug-dummy", 0, 0, 0) < 0) - rb_raise(rb_eRuntimeError, "CRYPTO_get_ex_new_index for " - "class index %d failed", i); - } - } -#endif -#endif -#endif } diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index facb80aa..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 */ @@ -157,7 +161,6 @@ VALUE ossl_to_der_if_possible(VALUE); */ extern VALUE dOSSL; -#if defined(HAVE_VA_ARGS_MACRO) #define OSSL_Debug(...) do { \ if (dOSSL == Qtrue) { \ fprintf(stderr, "OSSL_DEBUG: "); \ @@ -166,11 +169,6 @@ extern VALUE dOSSL; } \ } while (0) -#else -void ossl_debug(const char *, ...); -#define OSSL_Debug ossl_debug -#endif - /* * Include all parts */ @@ -194,6 +192,7 @@ void ossl_debug(const char *, ...); #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_bn.c b/ext/openssl/ossl_bn.c index bf2bac36..ce0d3ec7 100644 --- a/ext/openssl/ossl_bn.c +++ b/ext/openssl/ossl_bn.c @@ -41,7 +41,7 @@ static const rb_data_type_t ossl_bn_type = { { 0, ossl_bn_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index 046d5a8e..506a0e71 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -42,7 +42,7 @@ static const rb_data_type_t ossl_cipher_type = { { 0, ossl_cipher_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* diff --git a/ext/openssl/ossl_config.c b/ext/openssl/ossl_config.c index 0bac0274..0e598b4d 100644 --- a/ext/openssl/ossl_config.c +++ b/ext/openssl/ossl_config.c @@ -22,7 +22,7 @@ static const rb_data_type_t ossl_config_type = { { 0, nconf_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; CONF * diff --git a/ext/openssl/ossl_digest.c b/ext/openssl/ossl_digest.c index fc326ec1..16aeeb81 100644 --- a/ext/openssl/ossl_digest.c +++ b/ext/openssl/ossl_digest.c @@ -35,7 +35,7 @@ static const rb_data_type_t ossl_digest_type = { { 0, ossl_digest_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* diff --git a/ext/openssl/ossl_engine.c b/ext/openssl/ossl_engine.c index 1abde7f7..9e86321d 100644 --- a/ext/openssl/ossl_engine.c +++ b/ext/openssl/ossl_engine.c @@ -78,7 +78,7 @@ static const rb_data_type_t ossl_engine_type = { { 0, ossl_engine_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* diff --git a/ext/openssl/ossl_hmac.c b/ext/openssl/ossl_hmac.c index 1a5f471a..c485ba7e 100644 --- a/ext/openssl/ossl_hmac.c +++ b/ext/openssl/ossl_hmac.c @@ -42,7 +42,7 @@ static const rb_data_type_t ossl_hmac_type = { { 0, ossl_hmac_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c index 0d25a730..48b161d4 100644 --- a/ext/openssl/ossl_kdf.c +++ b/ext/openssl/ossl_kdf.c @@ -21,7 +21,7 @@ static VALUE mKDF, eKDF; * (https://tools.ietf.org/html/rfc2898#section-5.2). * * === Parameters - * pass :: The passphrase. + * pass :: The password. * salt :: The salt. Salts prevent attacks based on dictionaries of common * passwords and attacks based on rainbow tables. It is a public * value that can be safely stored along with the password (e.g. diff --git a/ext/openssl/ossl_ns_spki.c b/ext/openssl/ossl_ns_spki.c index 9b114736..9bed1f33 100644 --- a/ext/openssl/ossl_ns_spki.c +++ b/ext/openssl/ossl_ns_spki.c @@ -50,7 +50,7 @@ static const rb_data_type_t ossl_netscape_spki_type = { { 0, ossl_netscape_spki_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c index 9c8d768d..df986bb3 100644 --- a/ext/openssl/ossl_ocsp.c +++ b/ext/openssl/ossl_ocsp.c @@ -86,7 +86,7 @@ static const rb_data_type_t ossl_ocsp_request_type = { { 0, ossl_ocsp_request_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static void @@ -100,7 +100,7 @@ static const rb_data_type_t ossl_ocsp_response_type = { { 0, ossl_ocsp_response_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static void @@ -114,7 +114,7 @@ static const rb_data_type_t ossl_ocsp_basicresp_type = { { 0, ossl_ocsp_basicresp_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static void @@ -128,7 +128,7 @@ static const rb_data_type_t ossl_ocsp_singleresp_type = { { 0, ossl_ocsp_singleresp_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static void @@ -142,7 +142,7 @@ static const rb_data_type_t ossl_ocsp_certid_type = { { 0, ossl_ocsp_certid_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* @@ -1701,7 +1701,7 @@ Init_ossl_ocsp(void) * require 'net/http' * * http_response = - * Net::HTTP.start ocsp_uri.hostname, ocsp.port do |http| + * Net::HTTP.start ocsp_uri.hostname, ocsp_uri.port do |http| * http.post ocsp_uri.path, request.to_der, * 'content-type' => 'application/ocsp-request' * end diff --git a/ext/openssl/ossl_pkcs12.c b/ext/openssl/ossl_pkcs12.c index fb947df1..164b2da4 100644 --- a/ext/openssl/ossl_pkcs12.c +++ b/ext/openssl/ossl_pkcs12.c @@ -44,7 +44,7 @@ static const rb_data_type_t ossl_pkcs12_type = { { 0, ossl_pkcs12_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index c8f7924e..aeeb4bf5 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -65,7 +65,7 @@ const rb_data_type_t ossl_pkcs7_type = { { 0, ossl_pkcs7_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static void @@ -79,7 +79,7 @@ static const rb_data_type_t ossl_pkcs7_signer_info_type = { { 0, ossl_pkcs7_signer_info_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static void @@ -93,7 +93,7 @@ static const rb_data_type_t ossl_pkcs7_recip_info_type = { { 0, ossl_pkcs7_recip_info_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 47625667..013412c2 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -35,7 +35,7 @@ const rb_data_type_t ossl_evp_pkey_type = { { 0, ossl_evp_pkey_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -82,31 +82,62 @@ ossl_pkey_new(EVP_PKEY *pkey) #if OSSL_OPENSSL_PREREQ(3, 0, 0) # include <openssl/decoder.h> -EVP_PKEY * -ossl_pkey_read_generic(BIO *bio, VALUE pass) +static EVP_PKEY * +ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass) { void *ppass = (void *)pass; OSSL_DECODER_CTX *dctx; EVP_PKEY *pkey = NULL; int pos = 0, pos2; - dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "DER", NULL, NULL, 0, NULL, NULL); + dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, input_type, NULL, NULL, + selection, NULL, NULL); if (!dctx) goto out; - if (OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb, ppass) != 1) - goto out; - - /* First check DER */ - if (OSSL_DECODER_from_bio(dctx, bio) == 1) + if (OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb, + ppass) != 1) goto out; + while (1) { + if (OSSL_DECODER_from_bio(dctx, bio) == 1) + goto out; + if (BIO_eof(bio)) + break; + pos2 = BIO_tell(bio); + if (pos2 < 0 || pos2 <= pos) + break; + ossl_clear_error(); + pos = pos2; + } + out: OSSL_BIO_reset(bio); + OSSL_DECODER_CTX_free(dctx); + return pkey; +} - /* Then check PEM; multiple OSSL_DECODER_from_bio() calls may be needed */ - if (OSSL_DECODER_CTX_set_input_type(dctx, "PEM") != 1) - goto out; +EVP_PKEY * +ossl_pkey_read_generic(BIO *bio, VALUE pass) +{ + EVP_PKEY *pkey = NULL; + /* First check DER, then check PEM. */ + const char *input_types[] = {"DER", "PEM"}; + int input_type_num = (int)(sizeof(input_types) / sizeof(char *)); /* - * First check for private key formats. This is to keep compatibility with - * ruby/openssl < 3.0 which decoded the following as a private key. + * Non-zero selections to try to decode. + * + * See EVP_PKEY_fromdata(3) - Selections to see all the selections. + * + * This is a workaround for the decoder failing to decode or returning + * bogus keys with selection 0, if a key management provider is different + * from a decoder provider. The workaround is to avoid using selection 0. + * + * Affected OpenSSL versions: >= 3.1.0, <= 3.1.2, or >= 3.0.0, <= 3.0.10 + * Fixed OpenSSL versions: 3.2, next release of the 3.1.z and 3.0.z + * + * See https://github.com/openssl/openssl/pull/21519 for details. + * + * First check for private key formats (EVP_PKEY_KEYPAIR). This is to keep + * compatibility with ruby/openssl < 3.0 which decoded the following as a + * private key. * * $ openssl ecparam -name prime256v1 -genkey -outform PEM * -----BEGIN EC PARAMETERS----- @@ -124,36 +155,28 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass) * * Note that normally, the input is supposed to contain a single decodable * PEM block only, so this special handling should not create a new problem. + * + * Note that we need to create the OSSL_DECODER_CTX variable each time when + * we use the different selection as a workaround. + * See https://github.com/openssl/openssl/issues/20657 for details. */ - OSSL_DECODER_CTX_set_selection(dctx, EVP_PKEY_KEYPAIR); - while (1) { - if (OSSL_DECODER_from_bio(dctx, bio) == 1) - goto out; - if (BIO_eof(bio)) - break; - pos2 = BIO_tell(bio); - if (pos2 < 0 || pos2 <= pos) - break; - ossl_clear_error(); - pos = pos2; - } - - OSSL_BIO_reset(bio); - OSSL_DECODER_CTX_set_selection(dctx, 0); - while (1) { - if (OSSL_DECODER_from_bio(dctx, bio) == 1) - goto out; - if (BIO_eof(bio)) - break; - pos2 = BIO_tell(bio); - if (pos2 < 0 || pos2 <= pos) - break; - ossl_clear_error(); - pos = pos2; + int selections[] = { + EVP_PKEY_KEYPAIR, + EVP_PKEY_KEY_PARAMETERS, + EVP_PKEY_PUBLIC_KEY + }; + int selection_num = (int)(sizeof(selections) / sizeof(int)); + int i, j; + + for (i = 0; i < input_type_num; i++) { + for (j = 0; j < selection_num; j++) { + pkey = ossl_pkey_read(bio, input_types[i], selections[j], pass); + if (pkey) { + goto out; + } + } } - out: - OSSL_DECODER_CTX_free(dctx); return pkey; } #else @@ -260,9 +283,9 @@ struct pkey_blocking_generate_arg { EVP_PKEY_CTX *ctx; EVP_PKEY *pkey; int state; - int yield: 1; - int genparam: 1; - int interrupted: 1; + unsigned int yield: 1; + unsigned int genparam: 1; + unsigned int interrupted: 1; }; static VALUE @@ -612,6 +635,72 @@ ossl_pkey_initialize_copy(VALUE self, VALUE other) } #endif +#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY +/* + * call-seq: + * OpenSSL::PKey.new_raw_private_key(algo, string) -> PKey + * + * See the OpenSSL documentation for EVP_PKEY_new_raw_private_key() + */ + +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); + + 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"); + + return ossl_pkey_new(pkey); +} +#endif + +#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY +/* + * call-seq: + * OpenSSL::PKey.new_raw_public_key(algo, string) -> PKey + * + * See the OpenSSL documentation for EVP_PKEY_new_raw_public_key() + */ + +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); + + 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"); + + return ossl_pkey_new(pkey); +} +#endif + /* * call-seq: * pkey.oid -> string @@ -793,6 +882,18 @@ ossl_pkey_private_to_der(int argc, VALUE *argv, VALUE self) * * Serializes the private key to PEM-encoded PKCS #8 format. See #private_to_der * for more details. + * + * An unencrypted PEM-encoded key will look like: + * + * -----BEGIN PRIVATE KEY----- + * [...] + * -----END PRIVATE KEY----- + * + * An encrypted PEM-encoded key will look like: + * + * -----BEGIN ENCRYPTED PRIVATE KEY----- + * [...] + * -----END ENCRYPTED PRIVATE KEY----- */ static VALUE ossl_pkey_private_to_pem(int argc, VALUE *argv, VALUE self) @@ -800,6 +901,35 @@ ossl_pkey_private_to_pem(int argc, VALUE *argv, VALUE self) return do_pkcs8_export(argc, argv, self, 0); } +#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY +/* + * call-seq: + * pkey.raw_private_key => string + * + * See the OpenSSL documentation for EVP_PKEY_get_raw_private_key() + */ + +static VALUE +ossl_pkey_raw_private_key(VALUE self) +{ + EVP_PKEY *pkey; + VALUE str; + size_t len; + + GetPKey(self, pkey); + if (EVP_PKEY_get_raw_private_key(pkey, NULL, &len) != 1) + ossl_raise(ePKeyError, "EVP_PKEY_get_raw_private_key"); + str = rb_str_new(NULL, len); + + if (EVP_PKEY_get_raw_private_key(pkey, (unsigned char *)RSTRING_PTR(str), &len) != 1) + ossl_raise(ePKeyError, "EVP_PKEY_get_raw_private_key"); + + rb_str_set_len(str, len); + + return str; +} +#endif + VALUE ossl_pkey_export_spki(VALUE self, int to_der) { @@ -842,6 +972,12 @@ ossl_pkey_public_to_der(VALUE self) * pkey.public_to_pem -> string * * Serializes the public key to PEM-encoded X.509 SubjectPublicKeyInfo format. + * + * A PEM-encoded key will look like: + * + * -----BEGIN PUBLIC KEY----- + * [...] + * -----END PUBLIC KEY----- */ static VALUE ossl_pkey_public_to_pem(VALUE self) @@ -849,6 +985,35 @@ ossl_pkey_public_to_pem(VALUE self) return ossl_pkey_export_spki(self, 0); } +#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY +/* + * call-seq: + * pkey.raw_public_key => string + * + * See the OpenSSL documentation for EVP_PKEY_get_raw_public_key() + */ + +static VALUE +ossl_pkey_raw_public_key(VALUE self) +{ + EVP_PKEY *pkey; + VALUE str; + size_t len; + + GetPKey(self, pkey); + if (EVP_PKEY_get_raw_public_key(pkey, NULL, &len) != 1) + ossl_raise(ePKeyError, "EVP_PKEY_get_raw_public_key"); + str = rb_str_new(NULL, len); + + if (EVP_PKEY_get_raw_public_key(pkey, (unsigned char *)RSTRING_PTR(str), &len) != 1) + ossl_raise(ePKeyError, "EVP_PKEY_get_raw_public_key"); + + rb_str_set_len(str, len); + + return str; +} +#endif + /* * call-seq: * pkey.compare?(another_pkey) -> true | false @@ -1586,6 +1751,10 @@ Init_ossl_pkey(void) rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1); rb_define_module_function(mPKey, "generate_parameters", ossl_pkey_s_generate_parameters, -1); rb_define_module_function(mPKey, "generate_key", ossl_pkey_s_generate_key, -1); +#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY + rb_define_module_function(mPKey, "new_raw_private_key", ossl_pkey_new_raw_private_key, 2); + rb_define_module_function(mPKey, "new_raw_public_key", ossl_pkey_new_raw_public_key, 2); +#endif rb_define_alloc_func(cPKey, ossl_pkey_alloc); rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0); @@ -1601,6 +1770,10 @@ Init_ossl_pkey(void) rb_define_method(cPKey, "private_to_pem", ossl_pkey_private_to_pem, -1); rb_define_method(cPKey, "public_to_der", ossl_pkey_public_to_der, 0); rb_define_method(cPKey, "public_to_pem", ossl_pkey_public_to_pem, 0); +#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY + rb_define_method(cPKey, "raw_private_key", ossl_pkey_raw_private_key, 0); + rb_define_method(cPKey, "raw_public_key", ossl_pkey_raw_public_key, 0); +#endif rb_define_method(cPKey, "compare?", ossl_pkey_compare, 1); rb_define_method(cPKey, "sign", ossl_pkey_sign, -1); diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 83c41378..a231814a 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -216,9 +216,20 @@ ossl_dh_is_private(VALUE self) * dh.to_pem -> aString * dh.to_s -> aString * - * Encodes this DH to its PEM encoding. Note that any existing per-session - * public/private keys will *not* get encoded, just the Diffie-Hellman - * parameters will be encoded. + * Serializes the DH parameters to a PEM-encoding. + * + * Note that any existing per-session public/private keys will *not* get + * encoded, just the Diffie-Hellman parameters will be encoded. + * + * PEM-encoded parameters will look like: + * + * -----BEGIN DH PARAMETERS----- + * [...] + * -----END DH PARAMETERS----- + * + * See also #public_to_pem (X.509 SubjectPublicKeyInfo) and + * #private_to_pem (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) for + * serialization with the private or public key components. */ static VALUE ossl_dh_export(VALUE self) @@ -244,10 +255,14 @@ ossl_dh_export(VALUE self) * call-seq: * dh.to_der -> aString * - * Encodes this DH to its DER encoding. Note that any existing per-session - * public/private keys will *not* get encoded, just the Diffie-Hellman - * parameters will be encoded. - + * Serializes the DH parameters to a DER-encoding + * + * Note that any existing per-session public/private keys will *not* get + * encoded, just the Diffie-Hellman parameters will be encoded. + * + * See also #public_to_der (X.509 SubjectPublicKeyInfo) and + * #private_to_der (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) for + * serialization with the private or public key components. */ static VALUE ossl_dh_to_der(VALUE self) diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c index b097f8c9..058ce738 100644 --- a/ext/openssl/ossl_pkey_dsa.c +++ b/ext/openssl/ossl_pkey_dsa.c @@ -211,16 +211,58 @@ ossl_dsa_is_private(VALUE self) * dsa.to_pem([cipher, password]) -> aString * dsa.to_s([cipher, password]) -> aString * - * Encodes this DSA to its PEM encoding. + * Serializes a private or public key to a PEM-encoding. * - * === Parameters - * * _cipher_ is an OpenSSL::Cipher. - * * _password_ is a string containing your password. + * [When the key contains public components only] * - * === Examples - * DSA.to_pem -> aString - * DSA.to_pem(cipher, 'mypassword') -> aString + * Serializes it into an X.509 SubjectPublicKeyInfo. + * The parameters _cipher_ and _password_ are ignored. * + * A PEM-encoded key will look like: + * + * -----BEGIN PUBLIC KEY----- + * [...] + * -----END PUBLIC KEY----- + * + * Consider using #public_to_pem instead. This serializes the key into an + * X.509 SubjectPublicKeyInfo regardless of whether it is a public key + * or a private key. + * + * [When the key contains private components, and no parameters are given] + * + * Serializes it into a traditional \OpenSSL DSAPrivateKey. + * + * A PEM-encoded key will look like: + * + * -----BEGIN DSA PRIVATE KEY----- + * [...] + * -----END DSA PRIVATE KEY----- + * + * [When the key contains private components, and _cipher_ and _password_ are given] + * + * Serializes it into a traditional \OpenSSL DSAPrivateKey and encrypts it in + * OpenSSL's traditional PEM encryption format. + * _cipher_ must be a cipher name understood by OpenSSL::Cipher.new or an + * instance of OpenSSL::Cipher. + * + * An encrypted PEM-encoded key will look like: + * + * -----BEGIN DSA PRIVATE KEY----- + * Proc-Type: 4,ENCRYPTED + * DEK-Info: AES-128-CBC,733F5302505B34701FC41F5C0746E4C0 + * + * [...] + * -----END DSA PRIVATE KEY----- + * + * Note that this format uses MD5 to derive the encryption key, and hence + * will not be available on FIPS-compliant systems. + * + * <b>This method is kept for compatibility.</b> + * This should only be used when the traditional, non-standard \OpenSSL format + * is required. + * + * Consider using #public_to_pem (X.509 SubjectPublicKeyInfo) or #private_to_pem + * (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) instead. */ static VALUE ossl_dsa_export(int argc, VALUE *argv, VALUE self) @@ -238,8 +280,15 @@ ossl_dsa_export(int argc, VALUE *argv, VALUE self) * call-seq: * dsa.to_der -> aString * - * Encodes this DSA to its DER encoding. + * Serializes a private or public key to a DER-encoding. + * + * See #to_pem for details. + * + * <b>This method is kept for compatibility.</b> + * This should only be used when the traditional, non-standard \OpenSSL format + * is required. * + * Consider using #public_to_der or #private_to_der instead. */ static VALUE ossl_dsa_to_der(VALUE self) diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index 92842f95..4b3a1fd0 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -400,13 +400,61 @@ static VALUE ossl_ec_key_is_private(VALUE self) /* * call-seq: - * key.export([cipher, pass_phrase]) => String - * key.to_pem([cipher, pass_phrase]) => String + * key.export([cipher, password]) => String + * key.to_pem([cipher, password]) => String * - * Outputs the EC key in PEM encoding. If _cipher_ and _pass_phrase_ are given - * they will be used to encrypt the key. _cipher_ must be an OpenSSL::Cipher - * instance. Note that encryption will only be effective for a private key, - * public keys will always be encoded in plain text. + * Serializes a private or public key to a PEM-encoding. + * + * [When the key contains public components only] + * + * Serializes it into an X.509 SubjectPublicKeyInfo. + * The parameters _cipher_ and _password_ are ignored. + * + * A PEM-encoded key will look like: + * + * -----BEGIN PUBLIC KEY----- + * [...] + * -----END PUBLIC KEY----- + * + * Consider using #public_to_pem instead. This serializes the key into an + * X.509 SubjectPublicKeyInfo regardless of whether it is a public key + * or a private key. + * + * [When the key contains private components, and no parameters are given] + * + * Serializes it into a SEC 1/RFC 5915 ECPrivateKey. + * + * A PEM-encoded key will look like: + * + * -----BEGIN EC PRIVATE KEY----- + * [...] + * -----END EC PRIVATE KEY----- + * + * [When the key contains private components, and _cipher_ and _password_ are given] + * + * Serializes it into a SEC 1/RFC 5915 ECPrivateKey + * and encrypts it in OpenSSL's traditional PEM encryption format. + * _cipher_ must be a cipher name understood by OpenSSL::Cipher.new or an + * instance of OpenSSL::Cipher. + * + * An encrypted PEM-encoded key will look like: + * + * -----BEGIN EC PRIVATE KEY----- + * Proc-Type: 4,ENCRYPTED + * DEK-Info: AES-128-CBC,733F5302505B34701FC41F5C0746E4C0 + * + * [...] + * -----END EC PRIVATE KEY----- + * + * Note that this format uses MD5 to derive the encryption key, and hence + * will not be available on FIPS-compliant systems. + * + * <b>This method is kept for compatibility.</b> + * This should only be used when the SEC 1/RFC 5915 ECPrivateKey format is + * required. + * + * Consider using #public_to_pem (X.509 SubjectPublicKeyInfo) or #private_to_pem + * (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) instead. */ static VALUE ossl_ec_key_export(int argc, VALUE *argv, VALUE self) @@ -426,7 +474,15 @@ ossl_ec_key_export(int argc, VALUE *argv, VALUE self) * call-seq: * key.to_der => String * - * See the OpenSSL documentation for i2d_ECPrivateKey_bio() + * Serializes a private or public key to a DER-encoding. + * + * See #to_pem for details. + * + * <b>This method is kept for compatibility.</b> + * This should only be used when the SEC 1/RFC 5915 ECPrivateKey format is + * required. + * + * Consider using #public_to_der or #private_to_der instead. */ static VALUE ossl_ec_key_to_der(VALUE self) @@ -530,7 +586,7 @@ static const rb_data_type_t ossl_ec_group_type = { { 0, ossl_ec_group_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -1115,7 +1171,7 @@ static const rb_data_type_t ossl_ec_point_type = { { 0, ossl_ec_point_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 072adabe..389f76f3 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -50,8 +50,8 @@ VALUE eRSAError; /* * call-seq: * RSA.new -> rsa - * RSA.new(encoded_key [, passphrase]) -> rsa - * RSA.new(encoded_key) { passphrase } -> rsa + * RSA.new(encoded_key [, password ]) -> rsa + * RSA.new(encoded_key) { password } -> rsa * RSA.new(size [, exponent]) -> rsa * * Generates or loads an \RSA keypair. @@ -61,9 +61,9 @@ VALUE eRSAError; * #set_crt_params. * * If called with a String, tries to parse as DER or PEM encoding of an \RSA key. - * Note that, if _passphrase_ is not specified but the key is encrypted with a - * passphrase, \OpenSSL will prompt for it. - * See also OpenSSL::PKey.read which can parse keys of any kinds. + * Note that if _password_ is not specified, but the key is encrypted with a + * password, \OpenSSL will prompt for it. + * See also OpenSSL::PKey.read which can parse keys of any kind. * * If called with a number, generates a new key pair. This form works as an * alias of RSA.generate. @@ -71,7 +71,7 @@ VALUE eRSAError; * Examples: * OpenSSL::PKey::RSA.new 2048 * OpenSSL::PKey::RSA.new File.read 'rsa.pem' - * OpenSSL::PKey::RSA.new File.read('rsa.pem'), 'my pass phrase' + * OpenSSL::PKey::RSA.new File.read('rsa.pem'), 'my password' */ static VALUE ossl_rsa_initialize(int argc, VALUE *argv, VALUE self) @@ -217,13 +217,61 @@ can_export_rsaprivatekey(VALUE self) /* * call-seq: - * rsa.export([cipher, pass_phrase]) => PEM-format String - * rsa.to_pem([cipher, pass_phrase]) => PEM-format String - * rsa.to_s([cipher, pass_phrase]) => PEM-format String + * rsa.export([cipher, password]) => PEM-format String + * rsa.to_pem([cipher, password]) => PEM-format String + * rsa.to_s([cipher, password]) => PEM-format String * - * Outputs this keypair in PEM encoding. If _cipher_ and _pass_phrase_ are - * given they will be used to encrypt the key. _cipher_ must be an - * OpenSSL::Cipher instance. + * Serializes a private or public key to a PEM-encoding. + * + * [When the key contains public components only] + * + * Serializes it into an X.509 SubjectPublicKeyInfo. + * The parameters _cipher_ and _password_ are ignored. + * + * A PEM-encoded key will look like: + * + * -----BEGIN PUBLIC KEY----- + * [...] + * -----END PUBLIC KEY----- + * + * Consider using #public_to_pem instead. This serializes the key into an + * X.509 SubjectPublicKeyInfo regardless of whether the key is a public key + * or a private key. + * + * [When the key contains private components, and no parameters are given] + * + * Serializes it into a PKCS #1 RSAPrivateKey. + * + * A PEM-encoded key will look like: + * + * -----BEGIN RSA PRIVATE KEY----- + * [...] + * -----END RSA PRIVATE KEY----- + * + * [When the key contains private components, and _cipher_ and _password_ are given] + * + * Serializes it into a PKCS #1 RSAPrivateKey + * and encrypts it in OpenSSL's traditional PEM encryption format. + * _cipher_ must be a cipher name understood by OpenSSL::Cipher.new or an + * instance of OpenSSL::Cipher. + * + * An encrypted PEM-encoded key will look like: + * + * -----BEGIN RSA PRIVATE KEY----- + * Proc-Type: 4,ENCRYPTED + * DEK-Info: AES-128-CBC,733F5302505B34701FC41F5C0746E4C0 + * + * [...] + * -----END RSA PRIVATE KEY----- + * + * Note that this format uses MD5 to derive the encryption key, and hence + * will not be available on FIPS-compliant systems. + * + * <b>This method is kept for compatibility.</b> + * This should only be used when the PKCS #1 RSAPrivateKey format is required. + * + * Consider using #public_to_pem (X.509 SubjectPublicKeyInfo) or #private_to_pem + * (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) instead. */ static VALUE ossl_rsa_export(int argc, VALUE *argv, VALUE self) @@ -238,7 +286,14 @@ ossl_rsa_export(int argc, VALUE *argv, VALUE self) * call-seq: * rsa.to_der => DER-format String * - * Outputs this keypair in DER encoding. + * Serializes a private or public key to a DER-encoding. + * + * See #to_pem for details. + * + * <b>This method is kept for compatibility.</b> + * This should only be used when the PKCS #1 RSAPrivateKey format is required. + * + * Consider using #public_to_der or #private_to_der instead. */ static VALUE ossl_rsa_to_der(VALUE self) 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/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index f6399266..236d455f 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -77,7 +77,7 @@ static const rb_data_type_t ossl_sslctx_type = { { ossl_sslctx_mark, ossl_sslctx_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -885,9 +885,9 @@ ossl_sslctx_setup(VALUE self) if (ca_path && !SSL_CTX_load_verify_dir(ctx, ca_path)) ossl_raise(eSSLError, "SSL_CTX_load_verify_dir"); #else - if(ca_file || ca_path){ - if (!SSL_CTX_load_verify_locations(ctx, ca_file, ca_path)) - rb_warning("can't set verify locations"); + if (ca_file || ca_path) { + if (!SSL_CTX_load_verify_locations(ctx, ca_file, ca_path)) + ossl_raise(eSSLError, "SSL_CTX_load_verify_locations"); } #endif @@ -1553,6 +1553,10 @@ ossl_ssl_mark(void *ptr) { SSL *ssl = ptr; rb_gc_mark((VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)); + + // Note: this reference is stored as @verify_callback so we don't need to mark it. + // However we do need to ensure GC compaction won't move it, hence why + // we call rb_gc_mark here. rb_gc_mark((VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_vcb_idx)); } @@ -1567,7 +1571,7 @@ const rb_data_type_t ossl_ssl_type = { { ossl_ssl_mark, ossl_ssl_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -1646,6 +1650,8 @@ ossl_ssl_initialize(int argc, VALUE *argv, VALUE self) SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void *)self); SSL_set_info_callback(ssl, ssl_info_cb); verify_cb = rb_attr_get(v_ctx, id_i_verify_callback); + // We don't need to trigger a write barrier because it's already + // an instance variable of this object. SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void *)verify_cb); rb_call_super(0, NULL); @@ -1653,6 +1659,17 @@ ossl_ssl_initialize(int argc, VALUE *argv, VALUE self) return self; } +#ifndef HAVE_RB_IO_DESCRIPTOR +static int +io_descriptor_fallback(VALUE io) +{ + rb_io_t *fptr; + GetOpenFile(io, fptr); + return fptr->fd; +} +#define rb_io_descriptor io_descriptor_fallback +#endif + static VALUE ossl_ssl_setup(VALUE self) { @@ -1668,8 +1685,8 @@ ossl_ssl_setup(VALUE self) GetOpenFile(io, fptr); rb_io_check_readable(fptr); rb_io_check_writable(fptr); - if (!SSL_set_fd(ssl, TO_SOCKET(fptr->fd))) - ossl_raise(eSSLError, "SSL_set_fd"); + if (!SSL_set_fd(ssl, TO_SOCKET(rb_io_descriptor(io)))) + ossl_raise(eSSLError, "SSL_set_fd"); return Qtrue; } @@ -1709,21 +1726,25 @@ no_exception_p(VALUE opts) #endif static void -io_wait_writable(rb_io_t *fptr) +io_wait_writable(VALUE io) { #ifdef HAVE_RB_IO_MAYBE_WAIT - rb_io_maybe_wait_writable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT); + rb_io_maybe_wait_writable(errno, io, RUBY_IO_TIMEOUT_DEFAULT); #else + rb_io_t *fptr; + GetOpenFile(io, fptr); rb_io_wait_writable(fptr->fd); #endif } static void -io_wait_readable(rb_io_t *fptr) +io_wait_readable(VALUE io) { #ifdef HAVE_RB_IO_MAYBE_WAIT - rb_io_maybe_wait_readable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT); + rb_io_maybe_wait_readable(errno, io, RUBY_IO_TIMEOUT_DEFAULT); #else + rb_io_t *fptr; + GetOpenFile(io, fptr); rb_io_wait_readable(fptr->fd); #endif } @@ -1732,75 +1753,74 @@ static VALUE ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts) { SSL *ssl; - rb_io_t *fptr; int ret, ret2; VALUE cb_state; int nonblock = opts != Qfalse; -#if defined(SSL_R_CERTIFICATE_VERIFY_FAILED) - unsigned long err; -#endif rb_ivar_set(self, ID_callback_state, Qnil); GetSSL(self, ssl); - GetOpenFile(rb_attr_get(self, id_i_io), fptr); - for(;;){ - ret = func(ssl); + VALUE io = rb_attr_get(self, id_i_io); + for (;;) { + ret = func(ssl); - cb_state = rb_attr_get(self, ID_callback_state); + cb_state = rb_attr_get(self, ID_callback_state); if (!NIL_P(cb_state)) { - /* must cleanup OpenSSL error stack before re-raising */ - ossl_clear_error(); - rb_jump_tag(NUM2INT(cb_state)); - } + /* must cleanup OpenSSL error stack before re-raising */ + ossl_clear_error(); + rb_jump_tag(NUM2INT(cb_state)); + } - if (ret > 0) - break; + if (ret > 0) + break; - switch((ret2 = ssl_get_error(ssl, ret))){ - case SSL_ERROR_WANT_WRITE: + switch ((ret2 = ssl_get_error(ssl, ret))) { + case SSL_ERROR_WANT_WRITE: if (no_exception_p(opts)) { return sym_wait_writable; } write_would_block(nonblock); - io_wait_writable(fptr); + io_wait_writable(io); continue; - case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_READ: if (no_exception_p(opts)) { return sym_wait_readable; } read_would_block(nonblock); - io_wait_readable(fptr); + io_wait_readable(io); continue; - case SSL_ERROR_SYSCALL: + case SSL_ERROR_SYSCALL: #ifdef __APPLE__ /* See ossl_ssl_write_internal() */ if (errno == EPROTOTYPE) continue; #endif - if (errno) rb_sys_fail(funcname); - ossl_raise(eSSLError, "%s SYSCALL returned=%d errno=%d peeraddr=%"PRIsVALUE" state=%s", - funcname, ret2, errno, peeraddr_ip_str(self), SSL_state_string_long(ssl)); - + if (errno) rb_sys_fail(funcname); + /* fallthrough */ + default: { + VALUE error_append = Qnil; #if defined(SSL_R_CERTIFICATE_VERIFY_FAILED) - case SSL_ERROR_SSL: - err = ERR_peek_last_error(); - if (ERR_GET_LIB(err) == ERR_LIB_SSL && - ERR_GET_REASON(err) == SSL_R_CERTIFICATE_VERIFY_FAILED) { - const char *err_msg = ERR_reason_error_string(err), - *verify_msg = X509_verify_cert_error_string(SSL_get_verify_result(ssl)); - if (!err_msg) - err_msg = "(null)"; - if (!verify_msg) - verify_msg = "(null)"; - ossl_clear_error(); /* let ossl_raise() not append message */ - ossl_raise(eSSLError, "%s returned=%d errno=%d peeraddr=%"PRIsVALUE" state=%s: %s (%s)", - funcname, ret2, errno, peeraddr_ip_str(self), SSL_state_string_long(ssl), - err_msg, verify_msg); - } + unsigned long err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_SSL && + ERR_GET_REASON(err) == SSL_R_CERTIFICATE_VERIFY_FAILED) { + const char *err_msg = ERR_reason_error_string(err), + *verify_msg = X509_verify_cert_error_string(SSL_get_verify_result(ssl)); + if (!err_msg) + err_msg = "(null)"; + if (!verify_msg) + verify_msg = "(null)"; + ossl_clear_error(); /* let ossl_raise() not append message */ + error_append = rb_sprintf(": %s (%s)", err_msg, verify_msg); + } #endif - /* fallthrough */ - default: - ossl_raise(eSSLError, "%s returned=%d errno=%d peeraddr=%"PRIsVALUE" state=%s", - funcname, ret2, errno, peeraddr_ip_str(self), SSL_state_string_long(ssl)); - } + ossl_raise(eSSLError, + "%s%s returned=%d errno=%d peeraddr=%"PRIsVALUE" state=%s%"PRIsVALUE, + funcname, + ret2 == SSL_ERROR_SYSCALL ? " SYSCALL" : "", + ret2, + errno, + peeraddr_ip_str(self), + SSL_state_string_long(ssl), + error_append); + } + } } return self; @@ -1906,8 +1926,7 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) SSL *ssl; int ilen; VALUE len, str; - rb_io_t *fptr; - VALUE io, opts = Qnil; + VALUE opts = Qnil; if (nonblock) { rb_scan_args(argc, argv, "11:", &len, &str, &opts); @@ -1932,8 +1951,7 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) if (ilen == 0) return str; - io = rb_attr_get(self, id_i_io); - GetOpenFile(io, fptr); + VALUE io = rb_attr_get(self, id_i_io); rb_str_locktmp(str); for (;;) { @@ -1953,7 +1971,7 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) if (no_exception_p(opts)) { return sym_wait_writable; } write_would_block(nonblock); } - io_wait_writable(fptr); + io_wait_writable(io); continue; case SSL_ERROR_WANT_READ: if (nonblock) { @@ -1961,7 +1979,7 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) if (no_exception_p(opts)) { return sym_wait_readable; } read_would_block(nonblock); } - io_wait_readable(fptr); + io_wait_readable(io); continue; case SSL_ERROR_SYSCALL: if (!ERR_peek_error()) { @@ -2027,14 +2045,14 @@ ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts) SSL *ssl; rb_io_t *fptr; int num, nonblock = opts != Qfalse; - VALUE tmp, io; + VALUE tmp; GetSSL(self, ssl); if (!ssl_started(ssl)) rb_raise(eSSLError, "SSL session is not started yet"); tmp = rb_str_new_frozen(StringValue(str)); - io = rb_attr_get(self, id_i_io); + VALUE io = rb_attr_get(self, id_i_io); GetOpenFile(io, fptr); /* SSL_write(3ssl) manpage states num == 0 is undefined */ @@ -2050,12 +2068,12 @@ ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts) case SSL_ERROR_WANT_WRITE: if (no_exception_p(opts)) { return sym_wait_writable; } write_would_block(nonblock); - io_wait_writable(fptr); + io_wait_writable(io); continue; case SSL_ERROR_WANT_READ: if (no_exception_p(opts)) { return sym_wait_readable; } read_would_block(nonblock); - io_wait_readable(fptr); + io_wait_readable(io); continue; case SSL_ERROR_SYSCALL: #ifdef __APPLE__ diff --git a/ext/openssl/ossl_ssl_session.c b/ext/openssl/ossl_ssl_session.c index 139a474b..c5df902c 100644 --- a/ext/openssl/ossl_ssl_session.c +++ b/ext/openssl/ossl_ssl_session.c @@ -19,7 +19,7 @@ const rb_data_type_t ossl_ssl_session_type = { { 0, ossl_ssl_session_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE ossl_ssl_session_alloc(VALUE klass) diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index b33ff10c..f698bdc7 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -83,7 +83,7 @@ static const rb_data_type_t ossl_ts_req_type = { { 0, ossl_ts_req_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static void @@ -97,7 +97,7 @@ static const rb_data_type_t ossl_ts_resp_type = { { 0, ossl_ts_resp_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static void @@ -111,7 +111,7 @@ static const rb_data_type_t ossl_ts_token_info_type = { { 0, ossl_ts_token_info_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE diff --git a/ext/openssl/ossl_x509attr.c b/ext/openssl/ossl_x509attr.c index 60846cfe..d1d8bb5e 100644 --- a/ext/openssl/ossl_x509attr.c +++ b/ext/openssl/ossl_x509attr.c @@ -41,7 +41,7 @@ static const rb_data_type_t ossl_x509attr_type = { { 0, ossl_x509attr_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index 94435416..aa6b9bb7 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -41,7 +41,7 @@ static const rb_data_type_t ossl_x509_type = { { 0, ossl_x509_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* diff --git a/ext/openssl/ossl_x509crl.c b/ext/openssl/ossl_x509crl.c index 6c1d9153..80e29f9d 100644 --- a/ext/openssl/ossl_x509crl.c +++ b/ext/openssl/ossl_x509crl.c @@ -41,7 +41,7 @@ static const rb_data_type_t ossl_x509crl_type = { { 0, ossl_x509crl_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* diff --git a/ext/openssl/ossl_x509ext.c b/ext/openssl/ossl_x509ext.c index e54102c7..192d09bd 100644 --- a/ext/openssl/ossl_x509ext.c +++ b/ext/openssl/ossl_x509ext.c @@ -55,7 +55,7 @@ static const rb_data_type_t ossl_x509ext_type = { { 0, ossl_x509ext_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* @@ -108,7 +108,7 @@ static const rb_data_type_t ossl_x509extfactory_type = { { 0, ossl_x509extfactory_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -209,15 +209,16 @@ ossl_x509extfactory_create_ext(int argc, VALUE *argv, VALUE self) int nid; VALUE rconf; CONF *conf; + const char *oid_cstr = NULL; rb_scan_args(argc, argv, "21", &oid, &value, &critical); - StringValueCStr(oid); StringValue(value); if(NIL_P(critical)) critical = Qfalse; - nid = OBJ_ln2nid(RSTRING_PTR(oid)); - if(!nid) nid = OBJ_sn2nid(RSTRING_PTR(oid)); - if(!nid) ossl_raise(eX509ExtError, "unknown OID `%"PRIsVALUE"'", oid); + oid_cstr = StringValueCStr(oid); + nid = OBJ_ln2nid(oid_cstr); + if (nid != NID_undef) + oid_cstr = OBJ_nid2sn(nid); valstr = rb_str_new2(RTEST(critical) ? "critical," : ""); rb_str_append(valstr, value); @@ -228,7 +229,12 @@ ossl_x509extfactory_create_ext(int argc, VALUE *argv, VALUE self) rconf = rb_iv_get(self, "@config"); conf = NIL_P(rconf) ? NULL : GetConfig(rconf); X509V3_set_nconf(ctx, conf); - ext = X509V3_EXT_nconf_nid(conf, ctx, nid, RSTRING_PTR(valstr)); + +#if OSSL_OPENSSL_PREREQ(1, 1, 0) || OSSL_IS_LIBRESSL + ext = X509V3_EXT_nconf(conf, ctx, oid_cstr, RSTRING_PTR(valstr)); +#else + ext = X509V3_EXT_nconf(conf, ctx, (char *)oid_cstr, RSTRING_PTR(valstr)); +#endif X509V3_set_ctx_nodb(ctx); if (!ext){ ossl_raise(eX509ExtError, "%"PRIsVALUE" = %"PRIsVALUE, oid, valstr); diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c index 13a2b2c0..9591912f 100644 --- a/ext/openssl/ossl_x509name.c +++ b/ext/openssl/ossl_x509name.c @@ -46,7 +46,7 @@ static const rb_data_type_t ossl_x509name_type = { { 0, ossl_x509name_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c index 77a7d3f2..f0581851 100644 --- a/ext/openssl/ossl_x509req.c +++ b/ext/openssl/ossl_x509req.c @@ -41,7 +41,7 @@ static const rb_data_type_t ossl_x509req_type = { { 0, ossl_x509req_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* diff --git a/ext/openssl/ossl_x509revoked.c b/ext/openssl/ossl_x509revoked.c index 10b8aa4a..108447c8 100644 --- a/ext/openssl/ossl_x509revoked.c +++ b/ext/openssl/ossl_x509revoked.c @@ -41,7 +41,7 @@ static const rb_data_type_t ossl_x509rev_type = { { 0, ossl_x509rev_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* diff --git a/ext/openssl/ossl_x509store.c b/ext/openssl/ossl_x509store.c index 7c546187..f27381ca 100644 --- a/ext/openssl/ossl_x509store.c +++ b/ext/openssl/ossl_x509store.c @@ -116,6 +116,9 @@ static void ossl_x509store_mark(void *ptr) { X509_STORE *store = ptr; + // Note: this reference is stored as @verify_callback so we don't need to mark it. + // However we do need to ensure GC compaction won't move it, hence why + // we call rb_gc_mark here. rb_gc_mark((VALUE)X509_STORE_get_ex_data(store, store_ex_verify_cb_idx)); } @@ -130,7 +133,7 @@ static const rb_data_type_t ossl_x509store_type = { { ossl_x509store_mark, ossl_x509store_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* @@ -187,8 +190,9 @@ ossl_x509store_set_vfy_cb(VALUE self, VALUE cb) X509_STORE *store; GetX509Store(self, store); - X509_STORE_set_ex_data(store, store_ex_verify_cb_idx, (void *)cb); rb_iv_set(self, "@verify_callback", cb); + // We don't need to trigger a write barrier because `rb_iv_set` did it. + X509_STORE_set_ex_data(store, store_ex_verify_cb_idx, (void *)cb); return cb; } @@ -507,6 +511,9 @@ static void ossl_x509stctx_mark(void *ptr) { X509_STORE_CTX *ctx = ptr; + // Note: this reference is stored as @verify_callback so we don't need to mark it. + // However we do need to ensure GC compaction won't move it, hence why + // we call rb_gc_mark here. rb_gc_mark((VALUE)X509_STORE_CTX_get_ex_data(ctx, stctx_ex_verify_cb_idx)); } @@ -526,7 +533,7 @@ static const rb_data_type_t ossl_x509stctx_type = { { ossl_x509stctx_mark, ossl_x509stctx_free, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -614,8 +621,8 @@ ossl_x509stctx_verify(VALUE self) X509_STORE_CTX *ctx; GetX509StCtx(self, ctx); - X509_STORE_CTX_set_ex_data(ctx, stctx_ex_verify_cb_idx, - (void *)rb_iv_get(self, "@verify_callback")); + VALUE cb = rb_iv_get(self, "@verify_callback"); + X509_STORE_CTX_set_ex_data(ctx, stctx_ex_verify_cb_idx, (void *)cb); switch (X509_verify_cert(ctx)) { case 1: diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index aa4adee3..4df90746 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -93,9 +93,7 @@ module OpenSSL::Buffering nil else size = @rbuffer.size unless size - ret = @rbuffer[0, size] - @rbuffer[0, size] = "" - ret + @rbuffer.slice!(0, size) end end @@ -106,8 +104,7 @@ module OpenSSL::Buffering # # Get the next 8bit byte from `ssl`. Returns `nil` on EOF def getbyte - byte = read(1) - byte && byte.unpack1("C") + read(1)&.ord end ## diff --git a/lib/openssl/digest.rb b/lib/openssl/digest.rb index 2ff8398e..0f35ddad 100644 --- a/lib/openssl/digest.rb +++ b/lib/openssl/digest.rb @@ -18,13 +18,9 @@ module OpenSSL # Return the hash value computed with _name_ Digest. _name_ is either the # long name or short name of a supported digest algorithm. # - # === Examples + # === Example # # OpenSSL::Digest.digest("SHA256", "abc") - # - # which is equivalent to: - # - # OpenSSL::Digest.digest('SHA256', "abc") def self.digest(name, data) super(data, name) diff --git a/lib/openssl/ssl.rb b/lib/openssl/ssl.rb index ea8bb2a1..e557b8b4 100644 --- a/lib/openssl/ssl.rb +++ b/lib/openssl/ssl.rb @@ -34,21 +34,21 @@ module OpenSSL } if defined?(OpenSSL::PKey::DH) - DEFAULT_2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_ + DH_ffdhe2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_ -----BEGIN DH PARAMETERS----- -MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY -JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab -VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6 -YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 -1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD -7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg== +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== -----END DH PARAMETERS----- _end_of_pem_ - private_constant :DEFAULT_2048 + private_constant :DH_ffdhe2048 DEFAULT_TMP_DH_CALLBACK = lambda { |ctx, is_export, keylen| # :nodoc: warn "using default DH parameters." if $VERBOSE - DEFAULT_2048 + DH_ffdhe2048 } end @@ -494,7 +494,7 @@ YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 unless ctx.session_id_context # see #6137 - session id may not exceed 32 bytes prng = ::Random.new($0.hash) - session_id = prng.bytes(16).unpack('H*')[0] + session_id = prng.bytes(16).unpack1('H*') @ctx.session_id_context = session_id end @start_immediately = true diff --git a/lib/openssl/version.rb b/lib/openssl/version.rb index 4163f550..9315a793 100644 --- a/lib/openssl/version.rb +++ b/lib/openssl/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module OpenSSL - VERSION = "3.1.0" + VERSION = "3.2.0" end diff --git a/openssl.gemspec b/openssl.gemspec index 8d83b691..2765f554 100644 --- a/openssl.gemspec +++ b/openssl.gemspec @@ -1,21 +1,27 @@ Gem::Specification.new do |spec| spec.name = "openssl" - spec.version = "3.1.0" + spec.version = "3.2.0" spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] spec.email = ["ruby-core@ruby-lang.org"] - spec.summary = %q{OpenSSL provides SSL, TLS and general purpose cryptography.} - spec.description = %q{It wraps the OpenSSL library.} + spec.summary = %q{SSL/TLS and general-purpose cryptography for Ruby} + spec.description = %q{OpenSSL for Ruby provides access to SSL/TLS and general-purpose cryptography based on the OpenSSL library.} spec.homepage = "https://github.com/ruby/openssl" spec.license = "Ruby" - spec.files = Dir["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md", "BSDL", "LICENSE.txt"] - spec.require_paths = ["lib"] - spec.extensions = ["ext/openssl/extconf.rb"] + if Gem::Platform === spec.platform and spec.platform =~ 'java' or RUBY_ENGINE == 'jruby' + spec.platform = "java" + spec.files = [] + spec.add_runtime_dependency('jruby-openssl', '~> 0.14') + else + spec.files = Dir["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md", "BSDL", "LICENSE.txt"] + spec.require_paths = ["lib"] + spec.extensions = ["ext/openssl/extconf.rb"] + end spec.extra_rdoc_files = Dir["*.md"] spec.rdoc_options = ["--main", "README.md"] - spec.required_ruby_version = ">= 2.6.0" + spec.required_ruby_version = ">= 2.7.0" spec.metadata["msys2_mingw_dependencies"] = "openssl" end diff --git a/test/openssl/envutil.rb b/test/openssl/envutil.rb deleted file mode 100644 index 1ab36ff3..00000000 --- a/test/openssl/envutil.rb +++ /dev/null @@ -1,287 +0,0 @@ -# -*- coding: us-ascii -*- -require "timeout" -require "rbconfig" -require "pp" - -module EnvUtil - def rubybin - ENV["RUBY"] || RbConfig.ruby - end - module_function :rubybin - - LANG_ENVS = %w"LANG LC_ALL LC_CTYPE" - - def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, - encoding: nil, timeout: 10, reprieve: 1, - stdout_filter: nil, stderr_filter: nil, - rubybin: EnvUtil.rubybin, - **opt) - in_c, in_p = IO.pipe - out_p, out_c = IO.pipe if capture_stdout - err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout - opt[:in] = in_c - opt[:out] = out_c if capture_stdout - opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr - if encoding - out_p.set_encoding(encoding) if out_p - err_p.set_encoding(encoding) if err_p - end - c = "C" - child_env = {} - LANG_ENVS.each {|lc| child_env[lc] = c} - if Array === args and Hash === args.first - child_env.update(args.shift) - end - args = [args] if args.kind_of?(String) - pid = spawn(child_env, rubybin, *args, **opt) - in_c.close - out_c.close if capture_stdout - err_c.close if capture_stderr && capture_stderr != :merge_to_stdout - if block_given? - return yield in_p, out_p, err_p, pid - else - th_stdout = Thread.new { out_p.read } if capture_stdout - th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout - in_p.write stdin_data.to_str unless stdin_data.empty? - in_p.close - if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) - stdout = th_stdout.value if capture_stdout - stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout - else - signal = /mswin|mingw/ =~ RUBY_PLATFORM ? :KILL : :TERM - case pgroup = opt[:pgroup] - when 0, true - pgroup = -pid - when nil, false - pgroup = pid - end - begin - Process.kill signal, pgroup - Timeout.timeout((reprieve unless signal == :KILL)) do - Process.wait(pid) - end - rescue Errno::ESRCH - break - rescue Timeout::Error - raise if signal == :KILL - signal = :KILL - else - break - end while true - bt = caller_locations - raise Timeout::Error, "execution of #{bt.shift.label} expired", bt.map(&:to_s) - end - out_p.close if capture_stdout - err_p.close if capture_stderr && capture_stderr != :merge_to_stdout - Process.wait pid - status = $? - stdout = stdout_filter.call(stdout) if stdout_filter - stderr = stderr_filter.call(stderr) if stderr_filter - return stdout, stderr, status - end - ensure - [th_stdout, th_stderr].each do |th| - th.kill if th - end - [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| - io.close if io && !io.closed? - end - [th_stdout, th_stderr].each do |th| - th.join if th - end - end - module_function :invoke_ruby - - def verbose_warning - class << (stderr = "".dup) - alias write << - end - stderr, $stderr, verbose, $VERBOSE = $stderr, stderr, $VERBOSE, true - yield stderr - return $stderr - ensure - stderr, $stderr, $VERBOSE = $stderr, stderr, verbose - end - module_function :verbose_warning - - def suppress_warning - verbose, $VERBOSE = $VERBOSE, nil - yield - ensure - $VERBOSE = verbose - end - module_function :suppress_warning - - if /darwin/ =~ RUBY_PLATFORM - DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") - DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' - def self.diagnostic_reports(signame, cmd, pid, now) - return unless %w[ABRT QUIT SEGV ILL].include?(signame) - cmd = File.basename(cmd) - path = DIAGNOSTIC_REPORTS_PATH - timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT - pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" - first = true - 30.times do - first ? (first = false) : sleep(0.1) - Dir.glob(pat) do |name| - log = File.read(name) rescue next - if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log - File.unlink(name) - File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil - return log - end - end - end - nil - end - else - def self.diagnostic_reports(signame, cmd, pid, now) - end - end -end - -module Test - module Unit - module Assertions - FailDesc = proc do |status, message = "", out = ""| - pid = status.pid - now = Time.now - faildesc = proc do - if signo = status.termsig - signame = Signal.signame(signo) - sigdesc = "signal #{signo}" - end - log = EnvUtil.diagnostic_reports(signame, EnvUtil.rubybin, pid, now) - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if status.coredump? - sigdesc << " (core dumped)" - end - full_message = '' - if message and !message.empty? - full_message << message << "\n" - end - full_message << "pid #{pid} killed by #{sigdesc}" - if out and !out.empty? - full_message << "\n#{out.gsub(/^/, '| ')}" - full_message << "\n" if /\n\z/ !~ full_message - end - if log - full_message << "\n#{log.gsub(/^/, '| ')}" - end - full_message - end - faildesc - end - - ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV") - - def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) - unless file and line - loc, = caller_locations(1,1) - file ||= loc.path - line ||= loc.lineno - end - line -= 6 # lines until src - src = <<eom -# -*- coding: #{src.encoding}; -*- - require 'test/unit';include Test::Unit::Assertions - END { - puts [Marshal.dump($!)].pack('m')#, "assertions=\#{self._assertions}" - exit - } - def pend(msg = nil) $stdout.syswrite [Marshal.dump(msg.to_s)].pack("m"); exit! 0 end -#{src} - class Test::Unit::Runner - @@stop_auto_run = true - end -eom - args = args.dup - args.insert((Hash === args.first ? 1 : 0), "--disable=gems", *$:.map {|l| "-I#{l}"}) - stdout, stderr, status = EnvUtil.invoke_ruby(args, src, true, true, **opt) - abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig)) - assert(!abort, FailDesc[status, nil, stderr]) - #self._assertions += stdout[/^assertions=(\d+)/, 1].to_i - begin - res = Marshal.load(stdout.unpack("m")[0]) - rescue => marshal_error - ignore_stderr = nil - end - if res.is_a?(String) - pend res - elsif res - if bt = res.backtrace - bt.each do |l| - l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} - end - bt.concat(caller) - else - res.set_backtrace(caller) - end - raise res - end - - # really is it succeed? - unless ignore_stderr - # the body of assert_separately must not output anything to detect error - assert_equal("", stderr, "assert_separately failed with error message") - end - assert_equal(0, status, "assert_separately failed: '#{stderr}'") - raise marshal_error if marshal_error - end - - def assert_warning(pat, msg = nil) - stderr = EnvUtil.verbose_warning { - yield - } - if Regexp === pat - assert_match pat, stderr, msg - else - assert_equal pat, stderr, msg - end - end - - def message msg = nil, ending = ".", &default - proc { - msg = msg.call.chomp(".") if Proc === msg - custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty? - "#{custom_message}#{default.call}#{ending}" - } - end - - # threads should respond to shift method. - # Array can be used. - def assert_join_threads(threads, message = nil) - errs = [] - values = [] - while th = threads.shift - begin - values << th.value - rescue Exception - errs << [th, $!] - end - end - if !errs.empty? - msg = "exceptions on #{errs.length} threads:\n" + - errs.map {|t, err| - "#{t.inspect}:\n" + - err.backtrace.map.with_index {|line, i| - if i == 0 - "#{line}: #{err.message} (#{err.class})" - else - "\tfrom #{line}" - end - }.join("\n") - }.join("\n---\n") - if message - msg = "#{message}\n#{msg}" - end - raise Test::Unit::AssertionFailedError, msg - end - values - end - end - end -end diff --git a/test/openssl/fixtures/pkey/dh1024.pem b/test/openssl/fixtures/pkey/dh1024.pem deleted file mode 100644 index f99c757f..00000000 --- a/test/openssl/fixtures/pkey/dh1024.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN DH PARAMETERS----- -MIGHAoGBAKnKQ8MNK6nYZzLrrcuTsLxuiJGXoOO5gT+tljOTbHBuiktdMTITzIY0 -pFxIvjG05D7HoBZQfrR0c92NGWPkAiCkhQKB8JCbPVzwNLDy6DZ0pmofDKrEsYHG -AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOPeVkJ8ePao0eLAgEC ------END DH PARAMETERS----- diff --git a/test/openssl/fixtures/pkey/dh2048_ffdhe2048.pem b/test/openssl/fixtures/pkey/dh2048_ffdhe2048.pem new file mode 100644 index 00000000..9b182b72 --- /dev/null +++ b/test/openssl/fixtures/pkey/dh2048_ffdhe2048.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== +-----END DH PARAMETERS----- diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb index 3c19b76e..41885fd5 100644 --- a/test/openssl/test_cipher.rb +++ b/test/openssl/test_cipher.rb @@ -205,7 +205,7 @@ class OpenSSL::TestCipher < OpenSSL::TestCase assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct2) } end if has_cipher?("aes-128-ccm") && OpenSSL::Cipher.new("aes-128-ccm").authenticated? && - OpenSSL::OPENSSL_VERSION_NUMBER >= 0x1010103f # version >= 1.1.1c + openssl?(1, 1, 1, 0x03, 0xf) # version >= 1.1.1c def test_aes_gcm # GCM spec Appendix B Test Case 4 diff --git a/test/openssl/test_config.rb b/test/openssl/test_config.rb index 24a215a4..6dbb9c61 100644 --- a/test/openssl/test_config.rb +++ b/test/openssl/test_config.rb @@ -91,22 +91,19 @@ __EOC__ assert_equal('123baz456bar798', c['dollar']['qux']) assert_equal('123baz456bar798.123baz456bar798', c['dollar']['quxx']) - excn = assert_raise(OpenSSL::ConfigError) do + assert_raise_with_message(OpenSSL::ConfigError, /error in line 1: variable has no value/) do OpenSSL::Config.parse("foo = $bar") end - assert_equal("error in line 1: variable has no value", excn.message) - excn = assert_raise(OpenSSL::ConfigError) do + assert_raise_with_message(OpenSSL::ConfigError, /error in line 1: no close brace/) do OpenSSL::Config.parse("foo = $(bar") end - assert_equal("error in line 1: no close brace", excn.message) - excn = assert_raise(OpenSSL::ConfigError) do + assert_raise_with_message(OpenSSL::ConfigError, /error in line 1: missing equal sign/) do OpenSSL::Config.parse("f o =b ar # no space in key") end - assert_equal("error in line 1: missing equal sign", excn.message) - excn = assert_raise(OpenSSL::ConfigError) do + assert_raise_with_message(OpenSSL::ConfigError, /error in line 7: missing close square bracket/) do OpenSSL::Config.parse(<<__EOC__) # comment 1 # comments @@ -117,7 +114,6 @@ __EOC__ [third # section not terminated __EOC__ end - assert_equal("error in line 7: missing close square bracket", excn.message) end def test_s_parse_include diff --git a/test/openssl/test_digest.rb b/test/openssl/test_digest.rb index 84c128c1..b0b5b9be 100644 --- a/test/openssl/test_digest.rb +++ b/test/openssl/test_digest.rb @@ -67,7 +67,7 @@ class OpenSSL::TestDigest < OpenSSL::TestCase end def encode16(str) - str.unpack("H*").first + str.unpack1("H*") end def test_sha2 diff --git a/test/openssl/test_engine.rb b/test/openssl/test_engine.rb index 1ede6ed0..b6025f91 100644 --- a/test/openssl/test_engine.rb +++ b/test/openssl/test_engine.rb @@ -26,7 +26,7 @@ class OpenSSL::TestEngine < OpenSSL::TestCase with_openssl <<-'end;' orig = OpenSSL::Engine.engines pend "'openssl' is already loaded" if orig.any? { |e| e.id == "openssl" } - engine = get_engine + engine = OpenSSL::Engine.by_id("openssl") assert_not_nil(engine) assert_equal(1, OpenSSL::Engine.engines.size - orig.size) end; @@ -34,7 +34,7 @@ class OpenSSL::TestEngine < OpenSSL::TestCase def test_openssl_engine_id_name_inspect with_openssl <<-'end;' - engine = get_engine + engine = OpenSSL::Engine.by_id("openssl") assert_equal("openssl", engine.id) assert_not_nil(engine.name) assert_not_nil(engine.inspect) @@ -43,7 +43,7 @@ class OpenSSL::TestEngine < OpenSSL::TestCase def test_openssl_engine_digest_sha1 with_openssl <<-'end;' - engine = get_engine + engine = OpenSSL::Engine.by_id("openssl") digest = engine.digest("SHA1") assert_not_nil(digest) data = "test" @@ -59,12 +59,21 @@ class OpenSSL::TestEngine < OpenSSL::TestCase end with_openssl(<<-'end;', ignore_stderr: true) - engine = get_engine + engine = OpenSSL::Engine.by_id("openssl") algo = "RC4" data = "a" * 1000 key = OpenSSL::Random.random_bytes(16) - encrypted = crypt_data(data, key, :encrypt) { engine.cipher(algo) } - decrypted = crypt_data(encrypted, key, :decrypt) { OpenSSL::Cipher.new(algo) } + + cipher = engine.cipher(algo) + cipher.encrypt + cipher.key = key + encrypted = cipher.update(data) + cipher.final + + cipher = OpenSSL::Cipher.new(algo) + cipher.decrypt + cipher.key = key + decrypted = cipher.update(encrypted) + cipher.final + assert_equal(data, decrypted) end; end @@ -73,25 +82,10 @@ class OpenSSL::TestEngine < OpenSSL::TestCase # this is required because OpenSSL::Engine methods change global state def with_openssl(code, **opts) - assert_separately([{ "OSSL_MDEBUG" => nil }, "-ropenssl"], <<~"end;", **opts) - require #{__FILE__.dump} - include OpenSSL::TestEngine::Utils + assert_separately(["-ropenssl"], <<~"end;", **opts) #{code} end; end - - module Utils - def get_engine - OpenSSL::Engine.by_id("openssl") - end - - def crypt_data(data, key, mode) - cipher = yield - cipher.send mode - cipher.key = key - cipher.update(data) + cipher.final - end - end end end diff --git a/test/openssl/test_fips.rb b/test/openssl/test_fips.rb index 8cd474f9..4a3dd43a 100644 --- a/test/openssl/test_fips.rb +++ b/test/openssl/test_fips.rb @@ -4,22 +4,46 @@ require_relative 'utils' if defined?(OpenSSL) class OpenSSL::TestFIPS < OpenSSL::TestCase + def test_fips_mode_get_is_true_on_fips_mode_enabled + unless ENV["TEST_RUBY_OPENSSL_FIPS_ENABLED"] + omit "Only for FIPS mode environment" + end + + assert_separately(["-ropenssl"], <<~"end;") + assert OpenSSL.fips_mode == true, ".fips_mode should return true on FIPS mode enabled" + end; + end + + def test_fips_mode_get_is_false_on_fips_mode_disabled + if ENV["TEST_RUBY_OPENSSL_FIPS_ENABLED"] + omit "Only for non-FIPS mode environment" + end + + assert_separately(["-ropenssl"], <<~"end;") + message = ".fips_mode should return false on FIPS mode disabled. " \ + "If you run the test on FIPS mode, please set " \ + "TEST_RUBY_OPENSSL_FIPS_ENABLED=true" + assert OpenSSL.fips_mode == false, message + end; + end + def test_fips_mode_is_reentrant - OpenSSL.fips_mode = false - OpenSSL.fips_mode = false + assert_separately(["-ropenssl"], <<~"end;") + OpenSSL.fips_mode = false + OpenSSL.fips_mode = false + end; end - def test_fips_mode_get - return unless OpenSSL::OPENSSL_FIPS - assert_separately([{ "OSSL_MDEBUG" => nil }, "-ropenssl"], <<~"end;") - require #{__FILE__.dump} + def test_fips_mode_get_with_fips_mode_set + omit('OpenSSL is not FIPS-capable') unless OpenSSL::OPENSSL_FIPS + assert_separately(["-ropenssl"], <<~"end;") begin OpenSSL.fips_mode = true - assert OpenSSL.fips_mode == true, ".fips_mode returns true when .fips_mode=true" + assert OpenSSL.fips_mode == true, ".fips_mode should return true when .fips_mode=true" OpenSSL.fips_mode = false - assert OpenSSL.fips_mode == false, ".fips_mode returns false when .fips_mode=false" + assert OpenSSL.fips_mode == false, ".fips_mode should return false when .fips_mode=false" rescue OpenSSL::OpenSSLError pend "Could not set FIPS mode (OpenSSL::OpenSSLError: \#$!); skipping" end diff --git a/test/openssl/test_ns_spki.rb b/test/openssl/test_ns_spki.rb index 383931b9..d76fc9e5 100644 --- a/test/openssl/test_ns_spki.rb +++ b/test/openssl/test_ns_spki.rb @@ -38,13 +38,13 @@ class OpenSSL::TestNSSPI < OpenSSL::TestCase def test_decode_data spki = OpenSSL::Netscape::SPKI.new(@b64) assert_equal(@b64, spki.to_pem) - assert_equal(@b64.unpack("m").first, spki.to_der) + assert_equal(@b64.unpack1("m"), spki.to_der) assert_equal("MozillaIsMyFriend", spki.challenge) assert_equal(OpenSSL::PKey::RSA, spki.public_key.class) - spki = OpenSSL::Netscape::SPKI.new(@b64.unpack("m").first) + spki = OpenSSL::Netscape::SPKI.new(@b64.unpack1("m")) assert_equal(@b64, spki.to_pem) - assert_equal(@b64.unpack("m").first, spki.to_der) + assert_equal(@b64.unpack1("m"), spki.to_der) assert_equal("MozillaIsMyFriend", spki.challenge) assert_equal(OpenSSL::PKey::RSA, spki.public_key.class) end diff --git a/test/openssl/test_ossl.rb b/test/openssl/test_ossl.rb index e1d86bd4..979669a0 100644 --- a/test/openssl/test_ossl.rb +++ b/test/openssl/test_ossl.rb @@ -60,6 +60,19 @@ class OpenSSL::OSSL < OpenSSL::SSLTestCase assert_operator(a_b_time, :<, a_c_time * 10, "fixed_length_secure_compare timing test failed") assert_operator(a_c_time, :<, a_b_time * 10, "fixed_length_secure_compare timing test failed") end + + def test_error_data + # X509V3_EXT_nconf_nid() called from OpenSSL::X509::ExtensionFactory#create_ext is a function + # that uses ERR_raise_data() to append additional information about the error. + # + # The generated message should look like: + # "subjectAltName = IP:not.a.valid.ip.address: bad ip address (value=not.a.valid.ip.address)" + # "subjectAltName = IP:not.a.valid.ip.address: error in extension (name=subjectAltName, value=IP:not.a.valid.ip.address)" + ef = OpenSSL::X509::ExtensionFactory.new + assert_raise_with_message(OpenSSL::X509::ExtensionError, /value=(IP:)?not.a.valid.ip.address\)/) { + ef.create_ext("subjectAltName", "IP:not.a.valid.ip.address") + } + end end end diff --git a/test/openssl/test_pkcs12.rb b/test/openssl/test_pkcs12.rb index ec676743..e6b91b52 100644 --- a/test/openssl/test_pkcs12.rb +++ b/test/openssl/test_pkcs12.rb @@ -181,7 +181,7 @@ module OpenSSL def test_new_with_no_keys # generated with: # openssl pkcs12 -certpbe PBE-SHA1-3DES -in <@mycert> -nokeys -export - str = <<~EOF.unpack("m").first + str = <<~EOF.unpack1("m") MIIGJAIBAzCCBeoGCSqGSIb3DQEHAaCCBdsEggXXMIIF0zCCBc8GCSqGSIb3 DQEHBqCCBcAwggW8AgEAMIIFtQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQMw DgQIjv5c3OHvnBgCAggAgIIFiMJa8Z/w7errRvCQPXh9dGQz3eJaFq3S2gXD @@ -230,7 +230,7 @@ AA== def test_new_with_no_certs # generated with: # openssl pkcs12 -inkey fixtures/openssl/pkey/rsa-1.pem -nocerts -export - str = <<~EOF.unpack("m").first + str = <<~EOF.unpack1("m") MIIJ7wIBAzCCCbUGCSqGSIb3DQEHAaCCCaYEggmiMIIJnjCCCZoGCSqGSIb3 DQEHAaCCCYsEggmHMIIJgzCCCX8GCyqGSIb3DQEMCgECoIIJbjCCCWowHAYK KoZIhvcNAQwBAzAOBAjX5nN8jyRKwQICCAAEgglIBIRLHfiY1mNHpl3FdX6+ diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 2b99e8f3..aee0546f 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -82,6 +82,9 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase end def test_ed25519 + # Ed25519 is not FIPS-approved. + omit_on_fips + # Test vector from RFC 8032 Section 7.1 TEST 2 priv_pem = <<~EOF -----BEGIN PRIVATE KEY----- @@ -96,9 +99,11 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase begin priv = OpenSSL::PKey.read(priv_pem) pub = OpenSSL::PKey.read(pub_pem) - rescue OpenSSL::PKey::PKeyError + rescue OpenSSL::PKey::PKeyError => e # OpenSSL < 1.1.1 - pend "Ed25519 is not implemented" + pend "Ed25519 is not implemented" unless openssl?(1, 1, 1) + + raise e end assert_instance_of OpenSSL::PKey::PKey, priv assert_instance_of OpenSSL::PKey::PKey, pub @@ -106,6 +111,19 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase assert_equal pub_pem, priv.public_to_pem assert_equal pub_pem, pub.public_to_pem + begin + assert_equal "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", + priv.raw_private_key.unpack1("H*") + assert_equal OpenSSL::PKey.new_raw_private_key("ED25519", priv.raw_private_key).private_to_pem, + priv.private_to_pem + assert_equal "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", + priv.raw_public_key.unpack1("H*") + assert_equal OpenSSL::PKey.new_raw_public_key("ED25519", priv.raw_public_key).public_to_pem, + pub.public_to_pem + rescue NoMethodError + pend "running OpenSSL version does not have raw public key support" + end + sig = [<<~EOF.gsub(/[^0-9a-f]/, "")].pack("H*") 92a009a9f0d4cab8720e820b5f642540 a2b27b5416503f8fb3762223ebdb69da @@ -126,6 +144,32 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase assert_raise(OpenSSL::PKey::PKeyError) { priv.derive(pub) } end + def test_ed25519_not_approved_on_fips + omit_on_non_fips + # Ed25519 is technically allowed in the OpenSSL 3.0 code as a kind of bug. + # So, we need to omit OpenSSL 3.0. + # + # See OpenSSL providers/fips/fipsprov.c PROV_NAMES_ED25519 entries with + # FIPS_DEFAULT_PROPERTIES on openssl-3.0 branch and + # FIPS_UNAPPROVED_PROPERTIES on openssl-3.1 branch. + # + # See also + # https://github.com/openssl/openssl/issues/20758#issuecomment-1639658102 + # for details. + unless openssl?(3, 1, 0, 0) + omit 'Ed25519 is allowed in the OpenSSL 3.0 FIPS code as a kind of bug' + end + + priv_pem = <<~EOF + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIEzNCJso/5banbbDRuwRTg9bijGfNaumJNqM9u1PuKb7 + -----END PRIVATE KEY----- + EOF + assert_raise(OpenSSL::PKey::PKeyError) do + OpenSSL::PKey.read(priv_pem) + end + end + def test_x25519 # Test vector from RFC 7748 Section 6.1 alice_pem = <<~EOF @@ -150,6 +194,32 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase 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) + begin + alice_private = OpenSSL::PKey.new_raw_private_key("X25519", alice.raw_private_key) + bob_public = OpenSSL::PKey.new_raw_public_key("X25519", bob.raw_public_key) + alice_private_raw = alice.raw_private_key.unpack1("H*") + bob_public_raw = bob.raw_public_key.unpack1("H*") + rescue NoMethodError + # OpenSSL < 1.1.1 + pend "running OpenSSL version does not have raw public key support" + end + assert_equal alice_private.private_to_pem, + alice.private_to_pem + assert_equal bob_public.public_to_pem, + bob.public_to_pem + assert_equal "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", + alice_private_raw + assert_equal "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f", + bob_public_raw + end + + def raw_initialize + pend "Ed25519 is not implemented" unless openssl?(1, 1, 1) # >= v1.1.1 + + 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") } + assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_public_key("foo123", "xxx") } + assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_public_key("ED25519", "xxx") } end def test_compare? diff --git a/test/openssl/test_pkey_dh.rb b/test/openssl/test_pkey_dh.rb index 161af189..d32ffaf6 100644 --- a/test/openssl/test_pkey_dh.rb +++ b/test/openssl/test_pkey_dh.rb @@ -18,15 +18,26 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase assert_key(dh) end if ENV["OSSL_TEST_ALL"] - def test_new_break + def test_new_break_on_non_fips + omit_on_fips + assert_nil(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break }) assert_raise(RuntimeError) do OpenSSL::PKey::DH.new(NEW_KEYLEN) { raise } end end + def test_new_break_on_fips + omit_on_non_fips + + # The block argument is not executed in FIPS case. + # See https://github.com/ruby/openssl/issues/692 for details. + assert(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break }) + assert(OpenSSL::PKey::DH.new(NEW_KEYLEN) { raise }) + end + def test_derive_key - params = Fixtures.pkey("dh1024") + params = Fixtures.pkey("dh2048_ffdhe2048") dh1 = OpenSSL::PKey.generate_key(params) dh2 = OpenSSL::PKey.generate_key(params) dh1_pub = OpenSSL::PKey.read(dh1.public_to_der) @@ -44,34 +55,38 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase end def test_DHparams - dh1024 = Fixtures.pkey("dh1024") - dh1024params = dh1024.public_key + dh = Fixtures.pkey("dh2048_ffdhe2048") + dh_params = dh.public_key asn1 = OpenSSL::ASN1::Sequence([ - OpenSSL::ASN1::Integer(dh1024.p), - OpenSSL::ASN1::Integer(dh1024.g) + OpenSSL::ASN1::Integer(dh.p), + OpenSSL::ASN1::Integer(dh.g) ]) key = OpenSSL::PKey::DH.new(asn1.to_der) - assert_same_dh dh1024params, key + assert_same_dh dh_params, key pem = <<~EOF -----BEGIN DH PARAMETERS----- - MIGHAoGBAKnKQ8MNK6nYZzLrrcuTsLxuiJGXoOO5gT+tljOTbHBuiktdMTITzIY0 - pFxIvjG05D7HoBZQfrR0c92NGWPkAiCkhQKB8JCbPVzwNLDy6DZ0pmofDKrEsYHG - AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOPeVkJ8ePao0eLAgEC + MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz + +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a + 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 + YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi + 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD + ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== -----END DH PARAMETERS----- EOF + key = OpenSSL::PKey::DH.new(pem) - assert_same_dh dh1024params, key + assert_same_dh dh_params, key key = OpenSSL::PKey.read(pem) - assert_same_dh dh1024params, key + assert_same_dh dh_params, key - assert_equal asn1.to_der, dh1024.to_der - assert_equal pem, dh1024.export + assert_equal asn1.to_der, dh.to_der + assert_equal pem, dh.export end def test_public_key - dh = Fixtures.pkey("dh1024") + dh = Fixtures.pkey("dh2048_ffdhe2048") public_key = dh.public_key assert_no_key(public_key) #implies public_key.public? is false! assert_equal(dh.to_der, public_key.to_der) @@ -80,7 +95,8 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase def test_generate_key # Deprecated in v3.0.0; incompatible with OpenSSL 3.0 - dh = Fixtures.pkey("dh1024").public_key # creates a copy with params only + # Creates a copy with params only + dh = Fixtures.pkey("dh2048_ffdhe2048").public_key assert_no_key(dh) dh.generate_key! assert_key(dh) @@ -91,7 +107,15 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase end if !openssl?(3, 0, 0) def test_params_ok? - dh0 = Fixtures.pkey("dh1024") + # Skip the tests in old OpenSSL version 1.1.1c or early versions before + # applying the following commits in OpenSSL 1.1.1d to make `DH_check` + # function pass the RFC 7919 FFDHE group texts. + # https://github.com/openssl/openssl/pull/9435 + unless openssl?(1, 1, 1, 4) + pend 'DH check for RFC 7919 FFDHE group texts is not implemented' + end + + dh0 = Fixtures.pkey("dh2048_ffdhe2048") dh1 = OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(dh0.p), @@ -108,7 +132,7 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase def test_dup # Parameters only - dh1 = Fixtures.pkey("dh1024") + dh1 = Fixtures.pkey("dh2048_ffdhe2048") dh2 = dh1.dup assert_equal dh1.to_der, dh2.to_der assert_not_equal nil, dh1.p @@ -125,7 +149,7 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase end # With a key pair - dh3 = OpenSSL::PKey.generate_key(Fixtures.pkey("dh1024")) + dh3 = OpenSSL::PKey.generate_key(Fixtures.pkey("dh2048_ffdhe2048")) dh4 = dh3.dup assert_equal dh3.to_der, dh4.to_der assert_equal dh1.to_der, dh4.to_der # encodes parameters only @@ -136,7 +160,7 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase end def test_marshal - dh = Fixtures.pkey("dh1024") + dh = Fixtures.pkey("dh2048_ffdhe2048") deserialized = Marshal.load(Marshal.dump(dh)) assert_equal dh.to_der, deserialized.to_der diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb index d1059093..3f64a80e 100644 --- a/test/openssl/test_pkey_dsa.rb +++ b/test/openssl/test_pkey_dsa.rb @@ -58,7 +58,7 @@ class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase signature = dsa512.sign("SHA256", data) assert_equal true, dsa512.verify("SHA256", signature, data) - signature0 = (<<~'end;').unpack("m")[0] + signature0 = (<<~'end;').unpack1("m") MCwCFH5h40plgU5Fh0Z4wvEEpz0eE9SnAhRPbkRB8ggsN/vsSEYMXvJwjGg/ 6g== end; diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb index e5fef940..2cb8e287 100644 --- a/test/openssl/test_pkey_ec.rb +++ b/test/openssl/test_pkey_ec.rb @@ -5,20 +5,6 @@ if defined?(OpenSSL) class OpenSSL::TestEC < OpenSSL::PKeyTestCase def test_ec_key - builtin_curves = OpenSSL::PKey::EC.builtin_curves - assert_not_empty builtin_curves - - builtin_curves.each do |curve_name, comment| - # Oakley curves and X25519 are not suitable for signing and causes - # FIPS-selftest failure on some environment, so skip for now. - next if ["Oakley", "X25519"].any? { |n| curve_name.start_with?(n) } - - key = OpenSSL::PKey::EC.generate(curve_name) - assert_predicate key, :private? - assert_predicate key, :public? - assert_nothing_raised { key.check_key } - end - key1 = OpenSSL::PKey::EC.generate("prime256v1") # PKey is immutable in OpenSSL >= 3.0; constructing an empty EC object is @@ -49,6 +35,17 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase end end + def test_builtin_curves + builtin_curves = OpenSSL::PKey::EC.builtin_curves + assert_not_empty builtin_curves + assert_equal 2, builtin_curves[0].size + assert_kind_of String, builtin_curves[0][0] + assert_kind_of String, builtin_curves[0][1] + + builtin_curve_names = builtin_curves.map { |name, comment| name } + assert_include builtin_curve_names, "prime256v1" + end + def test_generate assert_raise(OpenSSL::PKey::ECError) { OpenSSL::PKey::EC.generate("non-existent") } g = OpenSSL::PKey::EC::Group.new("prime256v1") @@ -110,7 +107,7 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase signature = p256.sign("SHA256", data) assert_equal true, p256.verify("SHA256", signature, data) - signature0 = (<<~'end;').unpack("m")[0] + signature0 = (<<~'end;').unpack1("m") MEQCIEOTY/hD7eI8a0qlzxkIt8LLZ8uwiaSfVbjX2dPAvN11AiAQdCYx56Fq QdBp1B4sxJoA8jvODMMklMyBKVmudboA6A== end; @@ -232,6 +229,8 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase end def test_ECPrivateKey_encrypted + omit_on_fips + p256 = Fixtures.pkey("p256") # key = abcdef pem = <<~EOF diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb index b0ae5784..61c55c60 100644 --- a/test/openssl/test_pkey_rsa.rb +++ b/test/openssl/test_pkey_rsa.rb @@ -83,7 +83,7 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase signature = rsa1024.sign("SHA256", data) assert_equal true, rsa1024.verify("SHA256", signature, data) - signature0 = (<<~'end;').unpack("m")[0] + signature0 = (<<~'end;').unpack1("m") oLCgbprPvfhM4pjFQiDTFeWI9Sk+Og7Nh9TmIZ/xSxf2CGXQrptlwo7NQ28+ WA6YQo8jPH4hSuyWIM4Gz4qRYiYRkl5TDMUYob94zm8Si1HxEiS9354tzvqS zS8MLW2BtNPuTubMxTItHGTnOzo9sUg0LAHVFt8kHG2NfKAw/gQ= diff --git a/test/openssl/test_provider.rb b/test/openssl/test_provider.rb new file mode 100644 index 00000000..d0e66785 --- /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(["-ropenssl"], <<~"end;", **opts) + #{code} + end; + end +end + +end diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 91801557..07dc9a34 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -481,6 +481,40 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase } end + def test_ca_file + start_server(ignore_listener_error: true) { |port| + # X509_STORE is shared; setting ca_file to SSLContext affects store + store = OpenSSL::X509::Store.new + assert_equal false, store.verify(@svr_cert) + + ctx = Tempfile.create("ca_cert.pem") { |f| + f.puts(@ca_cert.to_pem) + f.close + + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + ctx.cert_store = store + ctx.ca_file = f.path + ctx.setup + ctx + } + assert_nothing_raised { + server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } + } + assert_equal true, store.verify(@svr_cert) + } + end + + def test_ca_file_not_found + path = Tempfile.create("ca_cert.pem") { |f| f.path } + ctx = OpenSSL::SSL::SSLContext.new + ctx.ca_file = path + # OpenSSL >= 1.1.0: /no certificate or crl found/ + assert_raise(OpenSSL::SSL::SSLError) { + ctx.setup + } + end + def test_finished_messages server_finished = nil server_peer_finished = nil @@ -1043,9 +1077,6 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_connect_certificate_verify_failed_exception_message - # Won't fix on the 3.0 branch - return if openssl?(3, 1, 0) - start_server(ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.set_params diff --git a/test/openssl/test_ssl_session.rb b/test/openssl/test_ssl_session.rb index b243201e..89cf672a 100644 --- a/test/openssl/test_ssl_session.rb +++ b/test/openssl/test_ssl_session.rb @@ -22,7 +22,7 @@ class OpenSSL::TestSSLSession < OpenSSL::SSLTestCase assert_match(/\A-----BEGIN SSL SESSION PARAMETERS-----/, pem) assert_match(/-----END SSL SESSION PARAMETERS-----\Z/, pem) pem.gsub!(/-----(BEGIN|END) SSL SESSION PARAMETERS-----/, '').gsub!(/[\r\n]+/m, '') - assert_equal(session.to_der, pem.unpack('m*')[0]) + assert_equal(session.to_der, pem.unpack1('m')) assert_not_nil(session.to_text) } end diff --git a/test/openssl/test_x509ext.rb b/test/openssl/test_x509ext.rb index 7ad010d1..59a41ed7 100644 --- a/test/openssl/test_x509ext.rb +++ b/test/openssl/test_x509ext.rb @@ -50,24 +50,41 @@ class OpenSSL::TestX509Extension < OpenSSL::TestCase cdp = ef.create_extension("crlDistributionPoints", "@crlDistPts") assert_equal(false, cdp.critical?) assert_equal("crlDistributionPoints", cdp.oid) - assert_match(%{URI:http://www\.example\.com/crl}, cdp.value) - assert_match( - %r{URI:ldap://ldap\.example\.com/cn=ca\?certificateRevocationList;binary}, - cdp.value) + assert_include(cdp.value, "URI:http://www.example.com/crl") + assert_include(cdp.value, + "URI:ldap://ldap.example.com/cn=ca?certificateRevocationList;binary") cdp = ef.create_extension("crlDistributionPoints", "critical, @crlDistPts") assert_equal(true, cdp.critical?) assert_equal("crlDistributionPoints", cdp.oid) - assert_match(%{URI:http://www.example.com/crl}, cdp.value) - assert_match( - %r{URI:ldap://ldap.example.com/cn=ca\?certificateRevocationList;binary}, - cdp.value) + assert_include(cdp.value, "URI:http://www.example.com/crl") + assert_include(cdp.value, + "URI:ldap://ldap.example.com/cn=ca?certificateRevocationList;binary") cp = ef.create_extension("certificatePolicies", "@certPolicies") assert_equal(false, cp.critical?) assert_equal("certificatePolicies", cp.oid) - assert_match(%r{2.23.140.1.2.1}, cp.value) - assert_match(%r{http://cps.example.com}, cp.value) + assert_include(cp.value, "2.23.140.1.2.1") + assert_include(cp.value, "http://cps.example.com") + end + + def test_factory_create_extension_sn_ln + ef = OpenSSL::X509::ExtensionFactory.new + bc_sn = ef.create_extension("basicConstraints", "critical, CA:TRUE, pathlen:2") + bc_ln = ef.create_extension("X509v3 Basic Constraints", "critical, CA:TRUE, pathlen:2") + assert_equal(@basic_constraints.to_der, bc_sn.to_der) + assert_equal(@basic_constraints.to_der, bc_ln.to_der) + end + + def test_factory_create_extension_oid + ef = OpenSSL::X509::ExtensionFactory.new + ef.config = OpenSSL::Config.parse(<<~_end_of_cnf_) + [basic_constraints] + cA = BOOLEAN:TRUE + pathLenConstraint = INTEGER:2 + _end_of_cnf_ + bc_oid = ef.create_extension("2.5.29.19", "ASN1:SEQUENCE:basic_constraints", true) + assert_equal(@basic_constraints.to_der, bc_oid.to_der) end def test_dup diff --git a/test/openssl/utils.rb b/test/openssl/utils.rb index 4ebcb983..f6c84eef 100644 --- a/test/openssl/utils.rb +++ b/test/openssl/utils.rb @@ -1,38 +1,13 @@ # frozen_string_literal: true begin require "openssl" - - # Disable FIPS mode for tests for installations - # where FIPS mode would be enabled by default. - # Has no effect on all other installations. - OpenSSL.fips_mode=false rescue LoadError end -# Compile OpenSSL with crypto-mdebug and run this test suite with OSSL_MDEBUG=1 -# environment variable to enable memory leak check. -if ENV["OSSL_MDEBUG"] == "1" - if OpenSSL.respond_to?(:print_mem_leaks) - OpenSSL.mem_check_start - - END { - GC.start - case OpenSSL.print_mem_leaks - when nil - warn "mdebug: check what is printed" - when true - raise "mdebug: memory leaks detected" - end - } - else - warn "OSSL_MDEBUG=1 is specified but OpenSSL is not built with crypto-mdebug" - end -end - require "test/unit" +require "core_assertions" require "tempfile" require "socket" -require "envutil" if defined?(OpenSSL) @@ -131,11 +106,12 @@ module OpenSSL::TestUtils end end - def openssl?(major = nil, minor = nil, fix = nil, patch = 0) + def openssl?(major = nil, minor = nil, fix = nil, patch = 0, status = 0) return false if OpenSSL::OPENSSL_VERSION.include?("LibreSSL") return true unless major OpenSSL::OPENSSL_VERSION_NUMBER >= - major * 0x10000000 + minor * 0x100000 + fix * 0x1000 + patch * 0x10 + major * 0x10000000 + minor * 0x100000 + fix * 0x1000 + patch * 0x10 + + status * 0x1 end def libressl?(major = nil, minor = nil, fix = nil) @@ -148,6 +124,7 @@ end class OpenSSL::TestCase < Test::Unit::TestCase include OpenSSL::TestUtils extend OpenSSL::TestUtils + include Test::Unit::CoreAssertions def setup if ENV["OSSL_GC_STRESS"] == "1" @@ -162,6 +139,30 @@ class OpenSSL::TestCase < Test::Unit::TestCase # OpenSSL error stack must be empty assert_equal([], OpenSSL.errors) end + + # Omit the tests in FIPS. + # + # For example, the password based encryption used in the PEM format uses MD5 + # for deriving the encryption key from the password, and MD5 is not + # FIPS-approved. + # + # See https://github.com/openssl/openssl/discussions/21830#discussioncomment-6865636 + # for details. + def omit_on_fips + return unless OpenSSL.fips_mode + + omit <<~MESSAGE + Only for OpenSSL non-FIPS with the following possible reasons: + * A testing logic is non-FIPS specific. + * An encryption used in the test is not FIPS-approved. + MESSAGE + end + + def omit_on_non_fips + return if OpenSSL.fips_mode + + omit "Only for OpenSSL FIPS" + end end class OpenSSL::SSLTestCase < OpenSSL::TestCase diff --git a/tool/openssl_fips.cnf.tmpl b/tool/openssl_fips.cnf.tmpl new file mode 100644 index 00000000..be0768d5 --- /dev/null +++ b/tool/openssl_fips.cnf.tmpl @@ -0,0 +1,19 @@ +config_diagnostics = 1 +openssl_conf = openssl_init + +# It seems that the .include needs an absolute path. +.include OPENSSL_DIR/ssl/fipsmodule.cnf + +[openssl_init] +providers = provider_sect +alg_section = algorithm_sect + +[provider_sect] +fips = fips_sect +base = base_sect + +[base_sect] +activate = 1 + +[algorithm_sect] +default_properties = fips=yes |