diff options
author | Hiroshi SHIBATA <hsbt@ruby-lang.org> | 2021-11-11 11:00:30 +0900 |
---|---|---|
committer | nagachika <nagachika@ruby-lang.org> | 2021-11-22 10:51:35 +0900 |
commit | e27381d289cbdbdca434bcc957c2cd1beab1c82c (patch) | |
tree | c50fe7f308ee10afc2e797b2f4760acc49ea64fc /lib/rubygems | |
parent | e262272b6a50c1a92cdcfee684e82f9242ef8171 (diff) |
Merge RubyGems 3.2.30 and Bundler 2.2.30
Diffstat (limited to 'lib/rubygems')
-rw-r--r-- | lib/rubygems/commands/cert_command.rb | 19 | ||||
-rw-r--r-- | lib/rubygems/commands/setup_command.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/core_ext/tcpsocket_init.rb | 4 | ||||
-rw-r--r-- | lib/rubygems/installer.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/package.rb | 58 | ||||
-rw-r--r-- | lib/rubygems/query_utils.rb | 8 | ||||
-rw-r--r-- | lib/rubygems/remote_fetcher.rb | 4 | ||||
-rw-r--r-- | lib/rubygems/request.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/request/connection_pools.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/request/http_pool.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/request_set.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/s3_uri_signer.rb | 9 | ||||
-rw-r--r-- | lib/rubygems/security.rb | 64 | ||||
-rw-r--r-- | lib/rubygems/security/policy.rb | 8 | ||||
-rw-r--r-- | lib/rubygems/security/signer.rb | 7 | ||||
-rw-r--r-- | lib/rubygems/source.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/source/git.rb | 4 | ||||
-rw-r--r-- | lib/rubygems/specification.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/user_interaction.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/util.rb | 2 |
20 files changed, 119 insertions, 86 deletions
diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index bdfeb0ba6e..867cb07cca 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -43,6 +43,11 @@ class Gem::Commands::CertCommand < Gem::Command options[:key] = open_private_key(key_file) end + add_option('-A', '--key-algorithm ALGORITHM', + 'Select which key algorithm to use for --build') do |algorithm, options| + options[:key_algorithm] = algorithm + end + add_option('-s', '--sign CERT', 'Signs CERT with the key from -K', 'and the certificate from -C') do |cert_file, options| @@ -89,14 +94,14 @@ class Gem::Commands::CertCommand < Gem::Command def open_private_key(key_file) check_openssl passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE'] - key = OpenSSL::PKey::RSA.new File.read(key_file), passphrase + key = OpenSSL::PKey.read File.read(key_file), passphrase raise OptionParser::InvalidArgument, "#{key_file}: private key not found" unless key.private? key rescue Errno::ENOENT raise OptionParser::InvalidArgument, "#{key_file}: does not exist" - rescue OpenSSL::PKey::RSAError - raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA key" + rescue OpenSSL::PKey::PKeyError, ArgumentError + raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA, DSA, or EC key" end def execute @@ -170,7 +175,8 @@ class Gem::Commands::CertCommand < Gem::Command raise Gem::CommandLineError, "Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation - key = Gem::Security.create_key + algorithm = options[:key_algorithm] || Gem::Security::DEFAULT_KEY_ALGORITHM + key = Gem::Security.create_key(algorithm) key_path = Gem::Security.write key, "gem-private_key.pem", 0600, passphrase return key, key_path @@ -255,13 +261,14 @@ For further reading on signing gems see `ri Gem::Security`. key_file = File.join Gem.default_key_path key = File.read key_file passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE'] - options[:key] = OpenSSL::PKey::RSA.new key, passphrase + options[:key] = OpenSSL::PKey.read key, passphrase + rescue Errno::ENOENT alert_error \ "--private-key not specified and ~/.gem/gem-private_key.pem does not exist" terminate_interaction 1 - rescue OpenSSL::PKey::RSAError + rescue OpenSSL::PKey::PKeyError alert_error \ "--private-key not specified and ~/.gem/gem-private_key.pem is not valid" diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 7ef0da4f36..92c9b93fed 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -618,7 +618,7 @@ abort "#{deprecation_message}" end def regenerate_plugins - require "rubygems/commands/pristine_command" + require_relative "pristine_command" say "Regenerating plugins" args = %w[--all --only-plugins --silent] diff --git a/lib/rubygems/core_ext/tcpsocket_init.rb b/lib/rubygems/core_ext/tcpsocket_init.rb index 3d9740c579..2a79b63bd6 100644 --- a/lib/rubygems/core_ext/tcpsocket_init.rb +++ b/lib/rubygems/core_ext/tcpsocket_init.rb @@ -11,10 +11,10 @@ module CoreExtensions IPV4_DELAY_SECONDS = 0.1 def initialize(host, serv, *rest) - mutex = Mutex.new + mutex = Thread::Mutex.new addrs = [] threads = [] - cond_var = ConditionVariable.new + cond_var = Thread::ConditionVariable.new Addrinfo.foreach(host, serv, nil, :STREAM) do |addr| Thread.report_on_exception = false if defined? Thread.report_on_exception = () diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 26ebfaa846..cf5ed7b502 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -68,7 +68,7 @@ class Gem::Installer @path_warning = false - @install_lock = Mutex.new + @install_lock = Thread::Mutex.new class << self ## diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index c4b29e4176..94705914af 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -71,6 +71,13 @@ class Gem::Package end end + class SymlinkError < Error + def initialize(name, destination, destination_dir) + super "installing symlink '%s' pointing to parent path %s of %s is not allowed" % + [name, destination, destination_dir] + end + end + class NonSeekableIO < Error; end class TooLongFileName < Error; end @@ -400,13 +407,21 @@ EOM # extracted. def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: - directories = [] if dir_mode + directories = [] open_tar_gz io do |tar| tar.each do |entry| next unless File.fnmatch pattern, entry.full_name, File::FNM_DOTMATCH destination = install_location entry.full_name, destination_dir + if entry.symlink? + link_target = entry.header.linkname + real_destination = link_target.start_with?("/") ? link_target : File.expand_path(link_target, File.dirname(destination)) + + raise Gem::Package::SymlinkError.new(entry.full_name, real_destination, destination_dir) unless + normalize_path(real_destination).start_with? normalize_path(destination_dir + '/') + end + FileUtils.rm_rf destination mkdir_options = {} @@ -417,9 +432,11 @@ EOM else File.dirname destination end - directories << mkdir if directories - mkdir_p_safe mkdir, mkdir_options, destination_dir, entry.full_name + unless directories.include?(mkdir) + FileUtils.mkdir_p mkdir, **mkdir_options + directories << mkdir + end File.open destination, 'wb' do |out| out.write entry.read @@ -432,8 +449,7 @@ EOM end end - if directories - directories.uniq! + if dir_mode File.chmod(dir_mode, *directories) end end @@ -466,21 +482,11 @@ EOM raise Gem::Package::PathError.new(filename, destination_dir) if filename.start_with? '/' - destination_dir = File.expand_path(File.realpath(destination_dir)) - destination = File.expand_path(File.join(destination_dir, filename)) + destination_dir = File.realpath(destination_dir) + destination = File.expand_path(filename, destination_dir) raise Gem::Package::PathError.new(destination, destination_dir) unless - destination.start_with? destination_dir + '/' - - begin - real_destination = File.expand_path(File.realpath(destination)) - rescue - # it's fine if the destination doesn't exist, because rm -rf'ing it can't cause any damage - nil - else - raise Gem::Package::PathError.new(real_destination, destination_dir) unless - real_destination.start_with? destination_dir + '/' - end + normalize_path(destination).start_with? normalize_path(destination_dir + '/') destination.tap(&Gem::UNTAINT) destination @@ -494,22 +500,6 @@ EOM end end - def mkdir_p_safe(mkdir, mkdir_options, destination_dir, file_name) - destination_dir = File.realpath(File.expand_path(destination_dir)) - parts = mkdir.split(File::SEPARATOR) - parts.reduce do |path, basename| - path = File.realpath(path) unless path == "" - path = File.expand_path(path + File::SEPARATOR + basename) - lstat = File.lstat path rescue nil - if !lstat || !lstat.directory? - unless normalize_path(path).start_with? normalize_path(destination_dir) and (FileUtils.mkdir path, **mkdir_options rescue false) - raise Gem::Package::PathError.new(file_name, destination_dir) - end - end - path - end - end - ## # Loads a Gem::Specification from the TarEntry +entry+ diff --git a/lib/rubygems/query_utils.rb b/lib/rubygems/query_utils.rb index ea0f260ab4..0acd5bf9c8 100644 --- a/lib/rubygems/query_utils.rb +++ b/lib/rubygems/query_utils.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'rubygems/local_remote_options' -require 'rubygems/spec_fetcher' -require 'rubygems/version_option' -require 'rubygems/text' +require_relative 'local_remote_options' +require_relative 'spec_fetcher' +require_relative 'version_option' +require_relative 'text' module Gem::QueryUtils diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 22ad89b970..de6f88f39a 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -72,7 +72,7 @@ class Gem::RemoteFetcher # fetching the gem. def initialize(proxy=nil, dns=nil, headers={}) - require 'rubygems/core_ext/tcpsocket_init' if Gem.configuration.ipv4_fallback_enabled + require_relative 'core_ext/tcpsocket_init' if Gem.configuration.ipv4_fallback_enabled require 'net/http' require 'stringio' require 'uri' @@ -81,7 +81,7 @@ class Gem::RemoteFetcher @proxy = proxy @pools = {} - @pool_lock = Mutex.new + @pool_lock = Thread::Mutex.new @cert_files = Gem::Request.get_cert_files @headers = headers diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index 72e25e20ab..d6100c914b 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -96,8 +96,10 @@ class Gem::Request return unless cert case error_number when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED then + require 'time' "Certificate #{cert.subject} expired at #{cert.not_after.iso8601}" when OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID then + require 'time' "Certificate #{cert.subject} not valid until #{cert.not_before.iso8601}" when OpenSSL::X509::V_ERR_CERT_REJECTED then "Certificate #{cert.subject} is rejected" diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb index 7f3988952c..a4c2929b38 100644 --- a/lib/rubygems/request/connection_pools.rb +++ b/lib/rubygems/request/connection_pools.rb @@ -11,7 +11,7 @@ class Gem::Request::ConnectionPools # :nodoc: @proxy_uri = proxy_uri @cert_files = cert_files @pools = {} - @pool_mutex = Mutex.new + @pool_mutex = Thread::Mutex.new end def pool_for(uri) diff --git a/lib/rubygems/request/http_pool.rb b/lib/rubygems/request/http_pool.rb index 9985bbafa6..f028516db8 100644 --- a/lib/rubygems/request/http_pool.rb +++ b/lib/rubygems/request/http_pool.rb @@ -12,7 +12,7 @@ class Gem::Request::HTTPPool # :nodoc: @http_args = http_args @cert_files = cert_files @proxy_uri = proxy_uri - @queue = SizedQueue.new 1 + @queue = Thread::SizedQueue.new 1 @queue << nil end diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index cff960708c..9286c54221 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -151,7 +151,7 @@ class Gem::RequestSet @prerelease = options[:prerelease] requests = [] - download_queue = Queue.new + download_queue = Thread::Queue.new # Create a thread-safe list of gems to download sorted_requests.each do |req| diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb index bba9afc9ff..4d1deee997 100644 --- a/lib/rubygems/s3_uri_signer.rb +++ b/lib/rubygems/s3_uri_signer.rb @@ -1,5 +1,4 @@ -require 'digest' -require 'rubygems/openssl' +require_relative 'openssl' ## # S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems @@ -87,7 +86,7 @@ class Gem::S3URISigner "AWS4-HMAC-SHA256", date_time, credential_info, - Digest::SHA256.hexdigest(canonical_request), + OpenSSL::Digest::SHA256.hexdigest(canonical_request), ].join("\n") end @@ -140,8 +139,8 @@ class Gem::S3URISigner def ec2_metadata_credentials_json require 'net/http' - require 'rubygems/request' - require 'rubygems/request/connection_pools' + require_relative 'request' + require_relative 'request/connection_pools' require 'json' iam_info = ec2_metadata_request(EC2_IAM_INFO) diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb index 28f705549c..8240a1a059 100644 --- a/lib/rubygems/security.rb +++ b/lib/rubygems/security.rb @@ -152,6 +152,7 @@ require_relative 'openssl' # certificate for EMAIL_ADDR # -C, --certificate CERT Signing certificate for --sign # -K, --private-key KEY Key for --sign or --build +# -A, --key-algorithm ALGORITHM Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA. # -s, --sign CERT Signs CERT with the key from -K # and the certificate from -C # -d, --days NUMBER_OF_DAYS Days before the certificate expires @@ -317,7 +318,6 @@ require_relative 'openssl' # * Honor extension restrictions # * Might be better to store the certificate chain as a PKCS#7 or PKCS#12 # file, instead of an array embedded in the metadata. -# * Flexible signature and key algorithms, not hard-coded to RSA and SHA1. # # == Original author # @@ -337,17 +337,19 @@ module Gem::Security DIGEST_NAME = 'SHA256' # :nodoc: ## - # Algorithm for creating the key pair used to sign gems + # Length of keys created by RSA and DSA keys - KEY_ALGORITHM = - if defined?(OpenSSL::PKey::RSA) - OpenSSL::PKey::RSA - end + RSA_DSA_KEY_LENGTH = 3072 ## - # Length of keys created by KEY_ALGORITHM + # Default algorithm to use when building a key pair - KEY_LENGTH = 3072 + DEFAULT_KEY_ALGORITHM = 'RSA' + + ## + # Named curve used for Elliptic Curve + + EC_NAME = 'secp384r1' ## # Cipher used to encrypt the key pair used to sign gems. @@ -400,7 +402,7 @@ module Gem::Security serial = 1) cert = OpenSSL::X509::Certificate.new - cert.public_key = key.public_key + cert.public_key = get_public_key(key) cert.version = 2 cert.serial = serial @@ -419,6 +421,24 @@ module Gem::Security end ## + # Gets the right public key from a PKey instance + + def self.get_public_key(key) + return key.public_key unless key.is_a?(OpenSSL::PKey::EC) + + ec_key = OpenSSL::PKey::EC.new(key.group.curve_name) + ec_key.public_key = key.public_key + ec_key + end + + ## + # In Ruby 2.3 EC doesn't implement the private_key? but not the private? method + + if defined?(OpenSSL::PKey::EC) && Gem::Version.new(String.new(RUBY_VERSION)) < Gem::Version.new("2.4.0") + OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?) + end + + ## # Creates a self-signed certificate with an issuer and subject from +email+, # a subject alternative name of +email+ and the given +extensions+ for the # +key+. @@ -459,11 +479,25 @@ module Gem::Security end ## - # Creates a new key pair of the specified +length+ and +algorithm+. The - # default is a 3072 bit RSA key. - - def self.create_key(length = KEY_LENGTH, algorithm = KEY_ALGORITHM) - algorithm.new length + # Creates a new key pair of the specified +algorithm+. RSA, DSA, and EC + # are supported. + + def self.create_key(algorithm) + if defined?(OpenSSL::PKey) + case algorithm.downcase + when 'dsa' + OpenSSL::PKey::DSA.new(RSA_DSA_KEY_LENGTH) + when 'rsa' + OpenSSL::PKey::RSA.new(RSA_DSA_KEY_LENGTH) + when 'ec' + domain_key = OpenSSL::PKey::EC.new(EC_NAME) + domain_key.generate_key + domain_key + else + raise Gem::Security::Exception, + "#{algorithm} algorithm not found. RSA, DSA, and EC algorithms are supported." + end + end end ## @@ -492,7 +526,7 @@ module Gem::Security raise Gem::Security::Exception, "incorrect signing key for re-signing " + "#{expired_certificate.subject}" unless - expired_certificate.public_key.to_pem == private_key.public_key.to_pem + expired_certificate.public_key.to_pem == get_public_key(private_key).to_pem unless expired_certificate.subject.to_s == expired_certificate.issuer.to_s diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb index 9683e55b32..3c3cb647ee 100644 --- a/lib/rubygems/security/policy.rb +++ b/lib/rubygems/security/policy.rb @@ -115,9 +115,11 @@ class Gem::Security::Policy raise Gem::Security::Exception, 'missing key or signature' end + public_key = Gem::Security.get_public_key(key) + raise Gem::Security::Exception, "certificate #{signer.subject} does not match the signing key" unless - signer.public_key.to_pem == key.public_key.to_pem + signer.public_key.to_pem == public_key.to_pem true end @@ -164,9 +166,9 @@ class Gem::Security::Policy end save_cert = OpenSSL::X509::Certificate.new File.read path - save_dgst = digester.digest save_cert.public_key.to_s + save_dgst = digester.digest save_cert.public_key.to_pem - pkey_str = root.public_key.to_s + pkey_str = root.public_key.to_pem cert_dgst = digester.digest pkey_str raise Gem::Security::Exception, diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb index c5c2c4f220..968cf88973 100644 --- a/lib/rubygems/security/signer.rb +++ b/lib/rubygems/security/signer.rb @@ -83,8 +83,8 @@ class Gem::Security::Signer @digest_name = Gem::Security::DIGEST_NAME @digest_algorithm = Gem::Security.create_digest(@digest_name) - if @key && !@key.is_a?(OpenSSL::PKey::RSA) - @key = OpenSSL::PKey::RSA.new(File.read(@key), @passphrase) + if @key && !@key.is_a?(OpenSSL::PKey::PKey) + @key = OpenSSL::PKey.read(File.read(@key), @passphrase) end if @cert_chain @@ -177,8 +177,7 @@ class Gem::Security::Signer disk_cert = File.read(disk_cert_path) rescue nil disk_key_path = File.join(Gem.default_key_path) - disk_key = - OpenSSL::PKey::RSA.new(File.read(disk_key_path), @passphrase) rescue nil + disk_key = OpenSSL::PKey.read(File.read(disk_key_path), @passphrase) rescue nil return unless disk_key diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index 816385d139..b5bd6b80e6 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rubygems/text" +require_relative "text" ## # A Source knows how to list and fetch gems from a RubyGems marshal index. # diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb index 9876adc24e..cda5aa8073 100644 --- a/lib/rubygems/source/git.rb +++ b/lib/rubygems/source/git.rb @@ -225,7 +225,7 @@ class Gem::Source::Git < Gem::Source # A hash for the git gem based on the git repository URI. def uri_hash # :nodoc: - require 'digest' # required here to avoid deadlocking in Gem.activate_bin_path (because digest is a gem on 2.5+) + require_relative '../openssl' normalized = if @repository =~ %r{^\w+://(\w+@)?} @@ -235,6 +235,6 @@ class Gem::Source::Git < Gem::Source @repository end - Digest::SHA1.hexdigest normalized + OpenSSL::Digest::SHA1.hexdigest normalized end end diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index aaa3b8ce7b..37fa9d0a8e 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -105,7 +105,7 @@ class Gem::Specification < Gem::BasicSpecification # rubocop:disable Style/MutableConstant LOAD_CACHE = {} # :nodoc: # rubocop:enable Style/MutableConstant - LOAD_CACHE_MUTEX = Mutex.new + LOAD_CACHE_MUTEX = Thread::Mutex.new private_constant :LOAD_CACHE if defined? private_constant diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb index 61d50a9ff8..0ab44fbf6c 100644 --- a/lib/rubygems/user_interaction.rb +++ b/lib/rubygems/user_interaction.rb @@ -543,7 +543,7 @@ class Gem::StreamUI # A progress reporter that behaves nicely with threaded downloading. class ThreadedDownloadReporter - MUTEX = Mutex.new + MUTEX = Thread::Mutex.new ## # The current file name being displayed diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index 2a55305172..4363c5adce 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/deprecate' +require_relative 'deprecate' ## # This module contains various utility methods as module methods. |