diff --git a/.github/workflows/install-rubygems.yml b/.github/workflows/install-rubygems.yml index 66a434abb809..74795ac94b16 100644 --- a/.github/workflows/install-rubygems.yml +++ b/.github/workflows/install-rubygems.yml @@ -14,6 +14,7 @@ jobs: strategy: matrix: ruby: [ 2.3.8, 2.4.10, 2.5.8, 2.6.6, 2.7.1, jruby-9.2.11.1 ] + openssl: [true, false] steps: - uses: actions/checkout@v2 - name: Setup ruby @@ -25,6 +26,11 @@ jobs: run: ruby -Ilib -S rake install 2> errors.txt - name: Check rubygems install produced no warnings run: test ! -s errors.txt || (cat errors.txt && exit 1) + - name: Simulate no openssl + run: ruby util/remove_openssl.rb + if: matrix.openssl == false + - name: Run installed rubygems + run: gem list bundler - name: Run bundler installed as a default gem run: bundle --version - name: Check bundler man pages were installed and are properly picked up diff --git a/Manifest.txt b/Manifest.txt index 5434edfd9ab3..ce177fdc1aac 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -401,6 +401,7 @@ lib/rubygems/installer_uninstaller_utils.rb lib/rubygems/local_remote_options.rb lib/rubygems/mock_gem_ui.rb lib/rubygems/name_tuple.rb +lib/rubygems/openssl.rb lib/rubygems/package.rb lib/rubygems/package/digest_io.rb lib/rubygems/package/file_source.rb diff --git a/bundler/lib/bundler/vendored_persistent.rb b/bundler/lib/bundler/vendored_persistent.rb index 045a761dac19..dc9573e025b7 100644 --- a/bundler/lib/bundler/vendored_persistent.rb +++ b/bundler/lib/bundler/vendored_persistent.rb @@ -1,12 +1,5 @@ # frozen_string_literal: true -# We forcibly require OpenSSL, because net/http/persistent will only autoload -# it. On some Rubies, autoload fails but explicit require succeeds. -begin - require "openssl" -rescue LoadError - # some Ruby builds don't have OpenSSL -end module Bundler module Persistent module Net diff --git a/bundler/spec/support/artifice/fail.rb b/bundler/spec/support/artifice/fail.rb index 1059c6df4e85..f69f2eccc650 100644 --- a/bundler/spec/support/artifice/fail.rb +++ b/bundler/spec/support/artifice/fail.rb @@ -1,11 +1,6 @@ # frozen_string_literal: true require "net/http" -begin - require "net/https" -rescue LoadError - nil # net/https or openssl -end # We can't use artifice here because it uses rack diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index 837cb9526f67..e5355d3652c3 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -1,12 +1,6 @@ # frozen_string_literal: true require 'rubygems/command' require 'rubygems/security' -begin - require 'openssl' -rescue LoadError => e - raise unless (e.respond_to?(:path) && e.path == 'openssl') || - e.message =~ / -- openssl$/ -end class Gem::Commands::CertCommand < Gem::Command def initialize diff --git a/lib/rubygems/openssl.rb b/lib/rubygems/openssl.rb new file mode 100644 index 000000000000..39ef91e8880c --- /dev/null +++ b/lib/rubygems/openssl.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +begin + require "openssl" +rescue LoadError => e + raise unless e.path == 'openssl' +end diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index 18bd4eb564ce..75f9e9979a62 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -45,7 +45,7 @@ def self.get_cert_files end def self.configure_connection_for_https(connection, cert_files) - require 'net/https' + require 'openssl' connection.use_ssl = true connection.verify_mode = Gem.configuration.ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER @@ -77,12 +77,6 @@ def self.configure_connection_for_https(connection, cert_files) end connection - rescue LoadError => e - raise unless (e.respond_to?(:path) && e.path == 'openssl') || - e.message =~ / -- openssl$/ - - raise Gem::Exception.new( - 'Unable to require openssl, install OpenSSL and rebuild Ruby (preferred) or use non-HTTPS sources') end def self.verify_certificate(store_context) diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb index 1ea9ff06a46e..c0b88842a0c1 100644 --- a/lib/rubygems/s3_uri_signer.rb +++ b/lib/rubygems/s3_uri_signer.rb @@ -1,6 +1,6 @@ require 'base64' require 'digest' -require 'openssl' +require 'rubygems/openssl' ## # S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb index 93bc6588e6bb..bd6d6ff8b9fc 100644 --- a/lib/rubygems/security.rb +++ b/lib/rubygems/security.rb @@ -7,13 +7,7 @@ require 'rubygems/exceptions' require 'fileutils' - -begin - require 'openssl' -rescue LoadError => e - raise unless (e.respond_to?(:path) && e.path == 'openssl') || - e.message =~ / -- openssl$/ -end +require_relative 'openssl' ## # = Signing gems diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb index db457f1ff92c..43abdb6d9135 100644 --- a/lib/rubygems/security/policy.rb +++ b/lib/rubygems/security/policy.rb @@ -24,8 +24,6 @@ class Gem::Security::Policy # options. def initialize(name, policy = {}, opt = {}) - require 'openssl' - @name = name @opt = opt diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 5e326e081f0b..dd61071490d0 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -2407,7 +2407,7 @@ def test_files # :nodoc: # still have their default values are omitted. def to_ruby - require 'openssl' + require_relative 'openssl' mark_version result = [] result << "# -*- encoding: utf-8 -*-" @@ -2447,7 +2447,7 @@ def to_ruby next if handled.include? attr_name current_value = self.send(attr_name) if current_value != default_value(attr_name) || self.class.required_attribute?(attr_name) - result << " s.#{attr_name} = #{ruby_code current_value}" unless current_value.is_a?(OpenSSL::PKey::RSA) + result << " s.#{attr_name} = #{ruby_code current_value}" unless defined?(OpenSSL::PKey::RSA) && current_value.is_a?(OpenSSL::PKey::RSA) end end diff --git a/test/rubygems/test_bundled_ca.rb b/test/rubygems/test_bundled_ca.rb index 31fa159e9d93..b30264a8d404 100644 --- a/test/rubygems/test_bundled_ca.rb +++ b/test/rubygems/test_bundled_ca.rb @@ -1,6 +1,12 @@ # frozen_string_literal: true require 'rubygems/test_case' -require 'net/https' +require 'net/http' +require 'rubygems/openssl' + +unless defined?(OpenSSL::SSL) + warn 'Skipping bundled certificates tests. openssl not found.' +end + require 'rubygems/request' # = Testing Bundled CA @@ -8,50 +14,48 @@ # The tested hosts are explained in detail here: https://github.com/rubygems/rubygems/commit/5e16a5428f973667cabfa07e94ff939e7a83ebd9 # -if ENV["CI"] || ENV["TEST_SSL"] - class TestBundledCA < Gem::TestCase - THIS_FILE = File.expand_path __FILE__ - - def bundled_certificate_store - store = OpenSSL::X509::Store.new +class TestBundledCA < Gem::TestCase + THIS_FILE = File.expand_path __FILE__ - ssl_cert_glob = - File.expand_path '../../../lib/rubygems/ssl_certs/*/*.pem', THIS_FILE + def bundled_certificate_store + store = OpenSSL::X509::Store.new - Dir[ssl_cert_glob].each do |ssl_cert| - store.add_file ssl_cert - end + ssl_cert_glob = + File.expand_path '../../../lib/rubygems/ssl_certs/*/*.pem', THIS_FILE - store + Dir[ssl_cert_glob].each do |ssl_cert| + store.add_file ssl_cert end - def assert_https(host) - self.assertions += 1 - http = Net::HTTP.new(host, 443) - http.use_ssl = true - http.verify_mode = OpenSSL::SSL::VERIFY_PEER - http.cert_store = bundled_certificate_store - http.get('/') - rescue Errno::ENOENT, Errno::ETIMEDOUT, SocketError - skip "#{host} seems offline, I can't tell whether ssl would work." - rescue OpenSSL::SSL::SSLError => e - # Only fail for certificate verification errors - if e.message =~ /certificate verify failed/ - flunk "#{host} is not verifiable using the included certificates. Error was: #{e.message}" - end - raise - end + store + end - def test_accessing_rubygems - assert_https('rubygems.org') + def assert_https(host) + self.assertions += 1 + http = Net::HTTP.new(host, 443) + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + http.cert_store = bundled_certificate_store + http.get('/') + rescue Errno::ENOENT, Errno::ETIMEDOUT, SocketError + skip "#{host} seems offline, I can't tell whether ssl would work." + rescue OpenSSL::SSL::SSLError => e + # Only fail for certificate verification errors + if e.message =~ /certificate verify failed/ + flunk "#{host} is not verifiable using the included certificates. Error was: #{e.message}" end + raise + end - def test_accessing_fastly - assert_https('rubygems.global.ssl.fastly.net') - end + def test_accessing_rubygems + assert_https('rubygems.org') + end - def test_accessing_new_index - assert_https('fastly.rubygems.org') - end + def test_accessing_fastly + assert_https('rubygems.global.ssl.fastly.net') end -end + + def test_accessing_new_index + assert_https('fastly.rubygems.org') + end +end if defined?(OpenSSL::SSL) diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb index 7912e00fa6b2..803b95e88c10 100644 --- a/test/rubygems/test_gem_dependency_installer.rb +++ b/test/rubygems/test_gem_dependency_installer.rb @@ -840,6 +840,8 @@ def test_install_platform_is_ignored_when_a_file_is_specified assert_equal %w[a-1-cpu-other_platform-1], inst.installed_gems.map {|s| s.full_name } end + require 'rubygems/openssl' + if defined? OpenSSL def test_install_security_policy util_setup_gems diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index 59bde67bfd19..5e9c3b7b8116 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -91,15 +91,12 @@ def test_add_checksums 'SHA512' => { 'metadata.gz' => metadata_sha512, 'data.tar.gz' => Digest::SHA512.hexdigest(tar), - } - } - - if defined?(OpenSSL::Digest) - expected['SHA256'] = { + }, + 'SHA256' => { 'metadata.gz' => metadata_sha256, 'data.tar.gz' => Digest::SHA256.hexdigest(tar), } - end + } assert_equal expected, YAML.load(checksums) end diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb index 39ece4d7e0d3..9f98f8042c9f 100644 --- a/test/rubygems/test_gem_remote_fetcher.rb +++ b/test/rubygems/test_gem_remote_fetcher.rb @@ -9,7 +9,7 @@ end unless defined?(OpenSSL::SSL) - warn 'Skipping Gem::Request tests. openssl not found.' + warn 'Skipping Gem::RemoteFetcher tests. openssl not found.' end require 'rubygems/remote_fetcher' diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index c4da82373a86..7953c3eeff35 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -2431,6 +2431,9 @@ def test_to_ruby end def test_to_ruby_with_rsa_key + require 'rubygems/openssl' + skip 'openssl is missing' unless defined?(OpenSSL::PKey::RSA) + rsa_key = OpenSSL::PKey::RSA.new(2048) @a2.signing_key = rsa_key ruby_code = @a2.to_ruby diff --git a/util/remove_openssl.rb b/util/remove_openssl.rb new file mode 100644 index 000000000000..a98f227f28de --- /dev/null +++ b/util/remove_openssl.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "rbconfig" +require "fileutils" + +class OpensslSimulator + attr_reader :openssl_rb, :openssl_gemspec, :openssl_ext + + def initialize + archdir = RbConfig::CONFIG["archdir"] + rubylibdir = RbConfig::CONFIG["rubylibdir"] + default_specifications_dir = Gem.default_specifications_dir + + @openssl_rb = File.join(rubylibdir, "openssl.rb") + @openssl_gemspec = Dir.glob("#{default_specifications_dir}/openssl-*.gemspec").first + + @openssl_ext = if RUBY_PLATFORM == "java" + File.join(rubylibdir, "jopenssl.jar") + else + File.join(archdir, "openssl.so") + end + end + + def hide_openssl + hide_file openssl_rb + hide_file openssl_ext + hide_file openssl_gemspec if openssl_gemspec + end + + def unhide_openssl + unhide_file openssl_gemspec if openssl_gemspec + unhide_file openssl_ext + unhide_file openssl_rb + end + + private + + def hide_file(file) + FileUtils.mv file, file + "_" + end + + def unhide_file(file) + FileUtils.mv file + "_", file + end +end + +if $0 == __FILE__ + openssl_simulate = OpensslSimulator.new + + if ARGV[0] == "--revert" + openssl_simulate.unhide_openssl + else + openssl_simulate.hide_openssl + end +end