diff options
31 files changed, 536 insertions, 259 deletions
diff --git a/lib/bundler/checksum.rb b/lib/bundler/checksum.rb index 0b618d5033..3b03935b57 100644 --- a/lib/bundler/checksum.rb +++ b/lib/bundler/checksum.rb @@ -2,31 +2,194 @@ module Bundler class Checksum + class Store + attr_reader :store + protected :store + + def initialize + @store = {} + end + + def initialize_copy(o) + @store = {} + o.store.each do |k, v| + @store[k] = v.dup + end + end + + def [](spec) + sums = @store[spec.full_name] + + Checksum.new(spec.name, spec.version, spec.platform, sums&.transform_values(&:digest)) + end + + def register(spec, checksums) + register_full_name(spec.full_name, checksums) + end + + def register_triple(name, version, platform, checksums) + register_full_name(GemHelpers.spec_full_name(name, version, platform), checksums) + end + + def delete_full_name(full_name) + @store.delete(full_name) + end + + def register_full_name(full_name, checksums) + sums = (@store[full_name] ||= {}) + + checksums.each do |checksum| + algo = checksum.algo + if multi = sums[algo] + multi.merge(checksum) + else + sums[algo] = Multi.new [checksum] + end + end + rescue SecurityError => e + raise e.exception(<<~MESSAGE) + Bundler found multiple different checksums for #{full_name}. + This means that there are multiple different `#{full_name}.gem` files. + This is a potential security issue, since Bundler could be attempting \ + to install a different gem than what you expect. + + #{e.message} + To resolve this issue: + 1. delete any downloaded gems referenced above + 2. run `bundle install` + + If you are sure that the new checksum is correct, you can \ + remove the `#{full_name}` entry under the lockfile `CHECKSUMS` \ + section and rerun `bundle install`. + + If you wish to continue installing the downloaded gem, and are certain it does not pose a \ + security issue despite the mismatching checksum, do the following: + 1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification + 2. run `bundle install` + MESSAGE + end + + def use(other) + other.store.each do |k, v| + register_full_name k, v.values + end + end + end + + class Single + attr_reader :algo, :digest, :source + def initialize(algo, digest, source) + @algo = algo + @digest = digest + @source = source + end + + def ==(other) + other.is_a?(Single) && other.digest == digest && other.algo == algo && source == other.source + end + + def hash + digest.hash + end + + alias_method :eql?, :== + + def to_s + "#{algo}-#{digest} (from #{source})" + end + end + + class Multi + attr_reader :algo, :digest, :checksums + protected :checksums + + def initialize(checksums) + @checksums = checksums + + unless checksums && checksums.size > 0 + raise ArgumentError, "must provide at least one checksum" + end + + first = checksums.first + @algo = first.algo + @digest = first.digest + end + + def initialize_copy(o) + @checksums = o.checksums.dup + @algo = o.algo + @digest = o.digest + end + + def merge(other) + raise ArgumentError, "cannot merge checksums of different algorithms" unless algo == other.algo + unless digest == other.digest + raise SecurityError, <<~MESSAGE + #{other} + #{self} from: + * #{sources.join("\n* ")} + MESSAGE + end + + case other + when Single + @checksums << other + when Multi + @checksums.concat(other.checksums) + else + raise ArgumentError + end + @checksums.uniq! + + self + end + + def sources + @checksums.map(&:source) + end + + def to_s + "#{algo}-#{digest}" + end + end + attr_reader :name, :version, :platform, :checksums SHA256 = %r{\Asha256-([a-z0-9]{64}|[A-Za-z0-9+\/=]{44})\z}.freeze + private_constant :SHA256 - def initialize(name, version, platform, checksums = []) + def initialize(name, version, platform, checksums = {}) @name = name @version = version @platform = platform || Gem::Platform::RUBY - @checksums = checksums + @checksums = checksums || {} # can expand this validation when we support more hashing algos later - if @checksums.any? && @checksums.all? {|c| c !~ SHA256 } + if [email protected]_a?(::Hash) || (@checksums.any? && [email protected]?("sha256")) + raise ArgumentError, "invalid checksums (#{@checksums.inspect})" + end + if @checksums.any? {|_, checksum| !checksum.is_a?(String) } raise ArgumentError, "invalid checksums (#{@checksums})" end end - def self.digest_from_file_source(file_source) + def self.digests_from_file_source(file_source, digest_algorithms: %w[sha256]) raise ArgumentError, "not a valid file source: #{file_source}" unless file_source.respond_to?(:with_read_io) + digests = digest_algorithms.map do |digest_algorithm| + [digest_algorithm.to_s, Bundler::SharedHelpers.digest(digest_algorithm.upcase).new] + end.to_h + file_source.with_read_io do |io| - digest = Bundler::SharedHelpers.digest(:SHA256).new - digest << io.read(16_384) until io.eof? + until io.eof? + block = io.read(16_384) + digests.each_value {|digest| digest << block } + end + io.rewind - digest end + + digests end def full_name @@ -42,12 +205,51 @@ module Bundler def to_lock out = String.new out << " #{GemHelpers.lock_name(name, version, platform)}" - out << " #{sha256}" if sha256 + checksums.sort_by(&:first).each_with_index do |(algo, checksum), idx| + out << (idx.zero? ? " " : ",") + out << algo << "-" << checksum + end out << "\n" out end + def match?(other) + return false unless match_spec?(other) + match_digests?(other.checksums) + end + + def match_digests?(digests) + return true if checksums.empty? && digests.empty? + + common_algos = checksums.keys & digests.keys + return true if common_algos.empty? + + common_algos.all? do |algo| + checksums[algo] == digests[algo] + end + end + + def merge!(other) + raise ArgumentError, "can't merge checksums for different specs" unless match_spec?(other) + + merge_digests!(other.checksums) + end + + def merge_digests!(digests) + if digests.any? {|_, checksum| !checksum.is_a?(String) } + raise ArgumentError, "invalid checksums (#{digests})" + end + @checksums = @checksums.merge(digests) do |algo, ours, theirs| + if ours != theirs + raise ArgumentError, "Digest mismatch for #{algo}:\n\t* #{ours.inspect}\n\t* #{theirs.inspect}" + end + ours + end + + self + end + private def sha256 diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 14f6746331..26e3126d84 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -15,7 +15,6 @@ module Bundler :dependencies, :locked_deps, :locked_gems, - :locked_checksums, :platforms, :ruby_version, :lockfile, @@ -93,7 +92,6 @@ module Bundler @locked_bundler_version = @locked_gems.bundler_version @locked_ruby_version = @locked_gems.ruby_version @originally_locked_specs = SpecSet.new(@locked_gems.specs) - @locked_checksums = @locked_gems.checksums if unlock != true @locked_deps = @locked_gems.dependencies @@ -114,7 +112,6 @@ module Bundler @originally_locked_specs = @locked_specs @locked_sources = [] @locked_platforms = [] - @locked_checksums = {} end locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) } @@ -753,6 +750,11 @@ module Bundler changes = sources.replace_sources!(@locked_sources) sources.all_sources.each do |source| + # has to be done separately, because we want to keep the locked checksum + # store for a source, even when doing a full update + if @locked_gems && locked_source = @locked_gems.sources.find {|s| s == source } + source.checksum_store&.use(locked_source.checksum_store) + end # If the source is unlockable and the current command allows an unlock of # the source (for example, you are doing a `bundle update <foo>` of a git-pinned # gem), unlock it. For git sources, this means to unlock the revision, which diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb index 863544b1f9..943e33be94 100644 --- a/lib/bundler/endpoint_specification.rb +++ b/lib/bundler/endpoint_specification.rb @@ -125,7 +125,17 @@ module Bundler next unless v case k.to_s when "checksum" - @checksum = v.last + next if Bundler.settings[:disable_checksum_validation] + digest = v.last + if digest.length == 64 + # nothing to do, it's a hexdigest + elsif digest.length == 44 + # transform the bytes from base64 to hex + digest = digest.unpack("m0").first.unpack("H*").first + else + raise ArgumentError, "The given checksum for #{full_name} (#{digest.inspect}) is not a valid SHA256 hexdigest nor base64digest" + end + @checksum = Checksum::Single.new("sha256", digest, "API response from #{@spec_fetcher.uri}") when "rubygems" @required_rubygems_version = Gem::Requirement.new(v) when "ruby" diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 5c1eac415e..08d1ee3437 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -81,7 +81,7 @@ module Bundler :HTTPRequestURITooLong, :HTTPUnauthorized, :HTTPUnprocessableEntity, :HTTPUnsupportedMediaType, :HTTPVersionNotSupported].freeze FAIL_ERRORS = begin - fail_errors = [AuthenticationRequiredError, BadAuthenticationError, AuthenticationForbiddenError, FallbackError] + fail_errors = [AuthenticationRequiredError, BadAuthenticationError, AuthenticationForbiddenError, FallbackError, SecurityError] fail_errors << Gem::Requirement::BadRequirementError fail_errors.concat(NET_ERRORS.map {|e| Net.const_get(e) }) end.freeze @@ -139,7 +139,16 @@ module Bundler fetch_specs(gem_names).each do |name, version, platform, dependencies, metadata| spec = if dependencies - EndpointSpecification.new(name, version, platform, self, dependencies, metadata) + EndpointSpecification.new(name, version, platform, self, dependencies, metadata).tap do |es| + unless index.local_search(es).empty? + # Duplicate spec.full_names, different spec.original_names + # index#<< ensures that the last one added wins, so if we're overriding + # here, make sure to also override the checksum, otherwise downloading the + # specs (even if that version is completely unused) will cause a SecurityError + source.checksum_store.delete_full_name(es.full_name) + end + source.checksum_store.register(es, [es.checksum]) if source && es.checksum + end else RemoteSpecification.new(name, version, platform, self) end diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index a17c8b90e5..2d084e462f 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -67,19 +67,6 @@ module Bundler out end - def materialize_for_checksum(&blk) - # - # See comment about #ruby_platform_materializes_to_ruby_platform? - # If the old lockfile format is present where there is no specific - # platform, then we should skip locking checksums as it is not - # deterministic which platform variant is locked. - # - return unless ruby_platform_materializes_to_ruby_platform? - - s = materialize_for_installation - yield s if block_given? - end - def materialize_for_installation source.local! @@ -126,7 +113,7 @@ module Bundler end def to_s - @__to_s ||= GemHelpers.lock_name(name, version, platform) + @to_s ||= GemHelpers.lock_name(name, version, platform) end def git_version diff --git a/lib/bundler/lockfile_generator.rb b/lib/bundler/lockfile_generator.rb index 52b3b411aa..4d2c2c2a86 100644 --- a/lib/bundler/lockfile_generator.rb +++ b/lib/bundler/lockfile_generator.rb @@ -68,16 +68,11 @@ module Bundler def add_checksums out << "\nCHECKSUMS\n" - definition.resolve.sort_by(&:full_name).each do |spec| - checksum = spec.to_checksum if spec.respond_to?(:to_checksum) - if spec.is_a?(LazySpecification) - spec.materialize_for_checksum do |materialized_spec| - checksum ||= materialized_spec.to_checksum if materialized_spec&.respond_to?(:to_checksum) - end - end - checksum ||= definition.locked_checksums[spec.full_name] - out << checksum.to_lock if checksum + empty_store = Checksum::Store.new + + definition.resolve.sort_by(&:full_name).each do |spec| + out << (spec.source.checksum_store || empty_store)[spec].to_lock end end diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 001de06d53..43d544fd32 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -2,6 +2,28 @@ module Bundler class LockfileParser + class Position + attr_reader :line, :column + def initialize(line, column) + @line = line + @column = column + end + + def advance!(string) + lines = string.count("\n") + if lines > 0 + @line += lines + @column = string.length - string.rindex("\n") + else + @column += string.length + end + end + + def to_s + "#{line}:#{column}" + end + end + attr_reader :sources, :dependencies, :specs, :platforms, :bundler_version, :ruby_version, :checksums BUNDLED = "BUNDLED WITH" @@ -22,7 +44,7 @@ module Bundler Gem::Version.create("1.10") => [BUNDLED].freeze, Gem::Version.create("1.12") => [RUBY].freeze, Gem::Version.create("1.13") => [PLUGIN].freeze, - Gem::Version.create("2.4.0") => [CHECKSUMS].freeze, + Gem::Version.create("2.5.0") => [CHECKSUMS].freeze, }.freeze KNOWN_SECTIONS = SECTIONS_BY_VERSION_INTRODUCED.values.flatten!.freeze @@ -66,15 +88,20 @@ module Bundler @sources = [] @dependencies = {} @parse_method = nil - @checksums = {} @specs = {} + @lockfile_path = begin + SharedHelpers.relative_lockfile_path + rescue GemfileNotFound + "Gemfile.lock" + end + @pos = Position.new(1, 1) if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/) - raise LockfileError, "Your lockfile contains merge conflicts.\n" \ - "Run `git checkout HEAD -- #{SharedHelpers.relative_lockfile_path}` first to get a clean lock." + raise LockfileError, "Your #{@lockfile_path} contains merge conflicts.\n" \ + "Run `git checkout HEAD -- #{@lockfile_path}` first to get a clean lock." end - lockfile.split(/(?:\r?\n)+/) do |line| + lockfile.split(/((?:\r?\n)+)/).each_slice(2) do |line, whitespace| if SOURCE.include?(line) @parse_method = :parse_source parse_source(line) @@ -93,12 +120,15 @@ module Bundler elsif @parse_method send(@parse_method, line) end + @pos.advance!(line) + @pos.advance!(whitespace) end @specs = @specs.values.sort_by!(&:full_name) rescue ArgumentError => e Bundler.ui.debug(e) - raise LockfileError, "Your lockfile is unreadable. Run `rm #{SharedHelpers.relative_lockfile_path}` " \ - "and then `bundle install` to generate a new lockfile." + raise LockfileError, "Your lockfile is unreadable. Run `rm #{@lockfile_path}` " \ + "and then `bundle install` to generate a new lockfile. The error occurred while " \ + "evaluating #{@lockfile_path}:#{@pos}" end def may_include_redundant_platform_specific_gems? @@ -149,7 +179,7 @@ module Bundler (?:#{space}\(([^-]*) # Space, followed by version (?:-(.*))?\))? # Optional platform (!)? # Optional pinned marker - (?:#{space}(.*))? # Optional checksum + (?:#{space}([^ ]+))? # Optional checksum $ # Line end /xo.freeze @@ -183,19 +213,31 @@ module Bundler end def parse_checksum(line) - if line =~ NAME_VERSION - spaces = $1 - return unless spaces.size == 2 - name = $2 - version = $3 - platform = $4 - checksum = $6 + return unless line =~ NAME_VERSION - version = Gem::Version.new(version) - platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY - checksum = Bundler::Checksum.new(name, version, platform, [checksum]) - @checksums[checksum.full_name] = checksum + spaces = $1 + return unless spaces.size == 2 + name = $2 + version = $3 + platform = $4 + checksums = $6 + return unless checksums + + version = Gem::Version.new(version) + platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY + source = "#{@lockfile_path}:#{@pos} in the CHECKSUMS lockfile section" + checksums = checksums.split(",").map do |c| + algo, digest = c.split("-", 2) + Checksum::Single.new(algo, digest, source) end + + full_name = GemHelpers.spec_full_name(name, version, platform) + + # Don't raise exception if there's a checksum for a gem that's not in the lockfile, + # we prefer to heal invalid lockfiles + return unless spec = @specs[full_name] + + spec.source.checksum_store.register_full_name(full_name, checksums) end def parse_spec(line) diff --git a/lib/bundler/plugin/api/source.rb b/lib/bundler/plugin/api/source.rb index 67c45bd204..57d4eb64ad 100644 --- a/lib/bundler/plugin/api/source.rb +++ b/lib/bundler/plugin/api/source.rb @@ -39,7 +39,7 @@ module Bundler # is present to be compatible with `Definition` and is used by # rubygems source. module Source - attr_reader :uri, :options, :name + attr_reader :uri, :options, :name, :checksum_store attr_accessor :dependency_names def initialize(opts) @@ -48,6 +48,7 @@ module Bundler @uri = opts["uri"] @type = opts["type"] @name = opts["name"] || "#{@type} at #{@uri}" + @checksum_store = Checksum::Store.new end # This is used by the default `spec` method to constructs the diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb index e8054dbbd5..f626a3218e 100644 --- a/lib/bundler/remote_specification.rb +++ b/lib/bundler/remote_specification.rb @@ -93,56 +93,12 @@ module Bundler " #{source.revision[0..6]}" end - # we don't get the checksum from a server like we could with EndpointSpecs - # calculating the checksum from the file on disk still provides some measure of security - # if it changes from install to install, that is cause for concern - def to_checksum - @checksum ||= begin - gem_path = fetch_gem - require "rubygems/package" - package = Gem::Package.new(gem_path) - digest = Bundler::Checksum.digest_from_file_source(package.gem) - digest.hexdigest! - end - - digest = "sha256-#{@checksum}" if @checksum - Bundler::Checksum.new(name, version, platform, [digest]) - end - private def to_ary nil end - def fetch_gem - fetch_platform - - cache_path = download_cache_path || default_cache_path_for_rubygems_dir - gem_path = "#{cache_path}/#{file_name}" - return gem_path if File.exist?(gem_path) - - SharedHelpers.filesystem_access(cache_path) do |p| - FileUtils.mkdir_p(p) - end - - Bundler.rubygems.download_gem(self, remote.uri, cache_path) - - gem_path - end - - def download_cache_path - return unless Bundler.feature_flag.global_gem_cache? - return unless remote - return unless remote.cache_slug - - Bundler.user_cache.join("gems", remote.cache_slug) - end - - def default_cache_path_for_rubygems_dir - "#{Bundler.bundle_path}/cache" - end - def _remote_specification @_remote_specification ||= @spec_fetcher.fetch_spec([@name, @version, @original_platform]) @_remote_specification || raise(GemspecError, "Gemspec data for #{full_name} was" \ diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index 22e3185b7f..0a289c416f 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -61,7 +61,7 @@ module Bundler end def pre_install_checks - super && validate_bundler_checksum(options[:bundler_expected_checksum]) + super && validate_bundler_checksum(options[:bundler_checksum_store]) end def build_extensions @@ -115,55 +115,56 @@ module Bundler raise DirectoryRemovalError.new(e, "Could not delete previous installation of `#{dir}`") end - def validate_bundler_checksum(checksum) + def validate_bundler_checksum(checksum_store) return true if Bundler.settings[:disable_checksum_validation] - return true unless checksum + return true unless source = @package.instance_variable_get(:@gem) return true unless source.respond_to?(:with_read_io) - digest = Bundler::Checksum.digest_from_file_source(source) - calculated_checksum = send(checksum_type(checksum), digest) + digests = Bundler::Checksum.digests_from_file_source(source).transform_values(&:hexdigest!) + + checksum = checksum_store[spec] + unless checksum.match_digests?(digests) + expected = checksum_store.send(:store)[spec.full_name] - unless calculated_checksum == checksum - raise SecurityError, <<-MESSAGE + raise SecurityError, <<~MESSAGE Bundler cannot continue installing #{spec.name} (#{spec.version}). The checksum for the downloaded `#{spec.full_name}.gem` does not match \ - the checksum given by the server. This means the contents of the downloaded \ - gem is different from what was uploaded to the server, and could be a potential security issue. + the known checksum for the gem. + This means the contents of the downloaded \ + gem is different from what was uploaded to the server \ + or first used by your teammates, and could be a potential security issue. To resolve this issue: - 1. delete the downloaded gem located at: `#{spec.gem_dir}/#{spec.full_name}.gem` + 1. delete the downloaded gem located at: `#{source.path}` 2. run `bundle install` + If you are sure that the new checksum is correct, you can \ + remove the `#{GemHelpers.lock_name spec.name, spec.version, spec.platform}` entry under the lockfile `CHECKSUMS` \ + section and rerun `bundle install`. + If you wish to continue installing the downloaded gem, and are certain it does not pose a \ security issue despite the mismatching checksum, do the following: 1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification 2. run `bundle install` - (More info: The expected SHA256 checksum was #{checksum.inspect}, but the \ - checksum for the downloaded gem was #{calculated_checksum.inspect}.) + #{expected.map do |algo, multi| + next unless actual = digests[algo] + next if actual == multi + + "(More info: The expected #{algo.upcase} checksum was #{multi.digest.inspect}, but the " \ + "checksum for the downloaded gem was #{actual.inspect}. The expected checksum came from: #{multi.sources.join(", ")})" + end.compact.join("\n")} MESSAGE end + register_digests(digests, checksum_store, source) true end - def checksum_type(checksum) - case checksum.length - when 64 then :hexdigest! - when 44 then :base64digest! - else raise InstallError, "The given checksum for #{spec.full_name} (#{checksum.inspect}) is not a valid SHA256 hexdigest nor base64digest" - end - end - - def hexdigest!(digest) - digest.hexdigest! - end - - def base64digest!(digest) - if digest.respond_to?(:base64digest!) - digest.base64digest! - else - [digest.digest!].pack("m0") - end + def register_digests(digests, checksum_store, source) + checksum_store.register( + spec, + digests.map {|algo, digest| Checksum::Single.new(algo, digest, "downloaded gem @ `#{source.path}`") } + ) end end end diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index f7f5ea7865..115dbd1378 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -11,6 +11,8 @@ module Bundler attr_accessor :dependency_names + attr_reader :checksum_store + def unmet_deps specs.unmet_dependency_names end diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index bdfcf8274a..cdf871250a 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -14,6 +14,7 @@ module Bundler DEFAULT_GLOB = "{,*,*/*}.gemspec" def initialize(options) + @checksum_store = Checksum::Store.new @options = options.dup @glob = options["glob"] || DEFAULT_GLOB diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 3d4d2eeec1..9ea8367006 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -19,6 +19,7 @@ module Bundler @allow_remote = false @allow_cached = false @allow_local = options["allow_local"] || false + @checksum_store = Checksum::Store.new Array(options["remotes"]).reverse_each {|r| add_remote(r) } end @@ -177,7 +178,7 @@ module Bundler :wrappers => true, :env_shebang => true, :build_args => options[:build_args], - :bundler_expected_checksum => spec.respond_to?(:checksum) && spec.checksum, + :bundler_checksum_store => spec.source.checksum_store, :bundler_extension_cache_path => extension_cache_path(spec) ) diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb index 0ce68b964c..da830cf8d4 100644 --- a/lib/bundler/stub_specification.rb +++ b/lib/bundler/stub_specification.rb @@ -93,16 +93,6 @@ module Bundler stub.raw_require_paths end - def add_checksum(checksum) - @checksum ||= checksum - end - - def to_checksum - return Bundler::Checksum.new(name, version, platform, ["sha256-#{checksum}"]) if checksum - - _remote_specification&.to_checksum - end - private def _remote_specification diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 8af62cced7..6f69ee22ce 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -761,8 +761,6 @@ class Gem::Specification < Gem::BasicSpecification attr_accessor :specification_version - attr_reader :checksum - def self._all # :nodoc: @@all ||= Gem.loaded_specs.values | stubs.map(&:to_spec) end @@ -2740,22 +2738,4 @@ class Gem::Specification < Gem::BasicSpecification def raw_require_paths # :nodoc: @require_paths end - - def add_checksum(checksum) - @checksum ||= checksum - end - - # if we don't get the checksum from the server - # calculating the checksum from the file on disk still provides some measure of security - # if it changes from install to install, that is cause for concern - def to_checksum - return Bundler::Checksum.new(name, version, platform, ["sha256-#{checksum}"]) if checksum - return Bundler::Checksum.new(name, version, platform) unless File.exist?(cache_file) - - require "rubygems/package" - package = Gem::Package.new(cache_file) - digest = Bundler::Checksum.digest_from_file_source(package.gem) - calculated_checksum = digest.hexdigest! - Bundler::Checksum.new(name, version, platform, ["sha256-#{calculated_checksum}"]) if calculated_checksum - end end diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index 3676ed21c8..ba6f9668ad 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -168,7 +168,7 @@ RSpec.describe Bundler::Definition do only_java CHECKSUMS - #{checksum_for_repo_gem gem_repo1, "only_java", "1.1", "java"} + only_java (1.1-java) BUNDLED WITH #{Bundler::VERSION} diff --git a/spec/bundler/cache/gems_spec.rb b/spec/bundler/cache/gems_spec.rb index 63c00eba01..6053c4c761 100644 --- a/spec/bundler/cache/gems_spec.rb +++ b/spec/bundler/cache/gems_spec.rb @@ -283,6 +283,7 @@ RSpec.describe "bundle cache" do :rubygems_version => "1.3.2" simulate_new_machine + pending "Causes checksum mismatch exception" bundle :install expect(cached_gem("rack-1.0.0")).to exist end diff --git a/spec/bundler/commands/check_spec.rb b/spec/bundler/commands/check_spec.rb index 7832a9d877..dacbd6c45f 100644 --- a/spec/bundler/commands/check_spec.rb +++ b/spec/bundler/commands/check_spec.rb @@ -426,8 +426,8 @@ RSpec.describe "bundle check" do depends_on_rack! CHECKSUMS - #{checksum_for_repo_gem gem_repo4, "depends_on_rack", "1.0"} - #{checksum_for_repo_gem gem_repo4, "rack", "1.0"} + depends_on_rack (1.0) + rack (1.0) BUNDLED WITH #{Bundler::VERSION} diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 471cd6c354..62add30252 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -905,7 +905,7 @@ RSpec.describe "bundle clean" do bundle :lock bundle "config set without development" bundle "config set path vendor/bundle" - bundle "install" + bundle "install", :verbose => true bundle :clean very_simple_binary_extensions_dir = diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 4426c484fb..90138087f6 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -65,7 +65,9 @@ RSpec.describe "bundle lock" do it "prints a lockfile when there is no existing lockfile with --print" do bundle "lock --print" - expect(out).to eq(@lockfile.strip) + # No checksums because no way to get them from a file uri source + # + no existing lockfile that has them + expect(out).to eq(@lockfile.strip.gsub(/ sha256-[a-f0-9]+$/, "")) end it "prints a lockfile when there is an existing lockfile with --print" do @@ -79,7 +81,9 @@ RSpec.describe "bundle lock" do it "writes a lockfile when there is no existing lockfile" do bundle "lock" - expect(read_lockfile).to eq(@lockfile) + # No checksums because no way to get them from a file uri source + # + no existing lockfile that has them + expect(read_lockfile).to eq(@lockfile.gsub(/ sha256-[a-f0-9]+$/, "")) end it "writes a lockfile when there is an outdated lockfile using --update" do @@ -93,7 +97,8 @@ RSpec.describe "bundle lock" do bundle "lock --update", :env => { "BUNDLE_FROZEN" => "true" } - expect(read_lockfile).to eq(@lockfile) + # No checksums for the updated gems + expect(read_lockfile).to eq(@lockfile.gsub(/( \(2\.3\.2\)) sha256-[a-f0-9]+$/, "\\1")) end it "does not fetch remote specs when using the --local option" do @@ -120,7 +125,7 @@ RSpec.describe "bundle lock" do foo CHECKSUMS - #{checksum_for_repo_gem repo, "foo", "1.0"} + #{checksum_for_repo_gem repo, "foo", "1.0", :empty => true} BUNDLED WITH #{Bundler::VERSION} @@ -136,7 +141,7 @@ RSpec.describe "bundle lock" do bundle "lock --lockfile=lock" expect(out).to match(/Writing lockfile to.+lock/) - expect(read_lockfile("lock")).to eq(@lockfile) + expect(read_lockfile("lock")).to eq(@lockfile.gsub(/ sha256-[a-f0-9]+$/, "")) expect { read_lockfile }.to raise_error(Errno::ENOENT) end @@ -156,7 +161,7 @@ RSpec.describe "bundle lock" do c.repo_gem repo, "weakling", "0.0.3" end - lockfile = strip_lockfile(<<-L) + lockfile = <<~L GEM remote: #{file_uri_for(repo)}/ specs: @@ -203,7 +208,17 @@ RSpec.describe "bundle lock" do bundle "lock --update rails rake" - expect(read_lockfile).to eq(@lockfile) + expect(read_lockfile).to eq(@lockfile.gsub(/( \((?:2\.3\.2|13\.0\.1)\)) sha256-[a-f0-9]+$/, "\\1")) + end + + it "preserves unknown checksum algorithms" do + lockfile @lockfile.gsub(/(sha256-[a-f0-9]+)$/, "constant-true,\\1,xyz-123") + + previous_lockfile = read_lockfile + + bundle "lock" + + expect(read_lockfile).to eq(previous_lockfile) end it "does not unlock git sources when only uri shape changes" do @@ -280,7 +295,7 @@ RSpec.describe "bundle lock" do G bundle "config set without test" bundle "config set path vendor/bundle" - bundle "lock" + bundle "lock", :verbose => true expect(bundled_app("vendor/bundle")).not_to exist end @@ -611,10 +626,10 @@ RSpec.describe "bundle lock" do mixlib-shellout CHECKSUMS - #{checksum_for_repo_gem gem_repo4, "ffi", "1.9.14", "x86-mingw32"} - #{checksum_for_repo_gem gem_repo4, "gssapi", "1.2.0"} - #{checksum_for_repo_gem gem_repo4, "mixlib-shellout", "2.2.6", "universal-mingw32"} - #{checksum_for_repo_gem gem_repo4, "win32-process", "0.8.3"} + #{checksum_for_repo_gem gem_repo4, "ffi", "1.9.14", "x86-mingw32", :empty => true} + #{checksum_for_repo_gem gem_repo4, "gssapi", "1.2.0", :empty => true} + #{checksum_for_repo_gem gem_repo4, "mixlib-shellout", "2.2.6", "universal-mingw32", :empty => true} + #{checksum_for_repo_gem gem_repo4, "win32-process", "0.8.3", :empty => true} BUNDLED WITH #{Bundler::VERSION} @@ -646,12 +661,12 @@ RSpec.describe "bundle lock" do mixlib-shellout CHECKSUMS - #{checksum_for_repo_gem gem_repo4, "ffi", "1.9.14"} - #{checksum_for_repo_gem gem_repo4, "ffi", "1.9.14", "x86-mingw32"} - #{checksum_for_repo_gem gem_repo4, "gssapi", "1.2.0"} - #{checksum_for_repo_gem gem_repo4, "mixlib-shellout", "2.2.6"} - #{checksum_for_repo_gem gem_repo4, "mixlib-shellout", "2.2.6", "universal-mingw32"} - #{checksum_for_repo_gem gem_repo4, "win32-process", "0.8.3"} + #{checksum_for_repo_gem gem_repo4, "ffi", "1.9.14", :empty => true} + #{checksum_for_repo_gem gem_repo4, "ffi", "1.9.14", "x86-mingw32", :empty => true} + #{checksum_for_repo_gem gem_repo4, "gssapi", "1.2.0", :empty => true} + #{checksum_for_repo_gem gem_repo4, "mixlib-shellout", "2.2.6", :empty => true} + #{checksum_for_repo_gem gem_repo4, "mixlib-shellout", "2.2.6", "universal-mingw32", :empty => true} + #{checksum_for_repo_gem gem_repo4, "win32-process", "0.8.3", :empty => true} BUNDLED WITH #{Bundler::VERSION} @@ -732,8 +747,8 @@ RSpec.describe "bundle lock" do libv8 CHECKSUMS - #{checksum_for_repo_gem gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-19"} - #{checksum_for_repo_gem gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-20"} + #{checksum_for_repo_gem gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-19", :empty => true} + #{checksum_for_repo_gem gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-20", :empty => true} BUNDLED WITH #{Bundler::VERSION} @@ -928,13 +943,15 @@ RSpec.describe "bundle lock" do end context "when an update is available" do - let(:repo) { gem_repo2 } - - before do - lockfile(@lockfile) + let(:repo) do build_repo2 do build_gem "foo", "2.0" end + gem_repo2 + end + + before do + lockfile(@lockfile) end it "does not implicitly update" do @@ -952,7 +969,7 @@ RSpec.describe "bundle lock" do c.repo_gem repo, "weakling", "0.0.3" end - expected_lockfile = strip_lockfile(<<-L) + expected_lockfile = <<~L GEM remote: #{file_uri_for(repo)}/ specs: @@ -1003,13 +1020,15 @@ RSpec.describe "bundle lock" do c.repo_gem repo, "activerecord", "2.3.2" c.repo_gem repo, "activeresource", "2.3.2" c.repo_gem repo, "activesupport", "2.3.2" - c.repo_gem repo, "foo", "2.0" + # We don't have a checksum for foo 2, + # since it is not downloaded by bundle lock, therefore we don't include it + # c.repo_gem repo, "foo", "2.0" c.repo_gem repo, "rails", "2.3.2" c.repo_gem repo, "rake", "13.0.1" c.repo_gem repo, "weakling", "0.0.3" end - expected_lockfile = strip_lockfile(<<-L) + expected_lockfile = <<~L GEM remote: #{file_uri_for(repo)}/ specs: @@ -1041,7 +1060,7 @@ RSpec.describe "bundle lock" do weakling CHECKSUMS - #{expected_checksums} + #{expected_checksums.prepend(" ").lines(:chomp => true).append(" foo (2.0)").sort.join("\n")} BUNDLED WITH #{Bundler::VERSION} @@ -1118,8 +1137,8 @@ RSpec.describe "bundle lock" do debug CHECKSUMS - #{checksum_for_repo_gem gem_repo4, "debug", "1.6.3"} - #{checksum_for_repo_gem gem_repo4, "irb", "1.5.0"} + #{checksum_for_repo_gem gem_repo4, "debug", "1.6.3", :empty => true} + #{checksum_for_repo_gem gem_repo4, "irb", "1.5.0", :empty => true} BUNDLED WITH #{Bundler::VERSION} @@ -1424,6 +1443,10 @@ RSpec.describe "bundle lock" do DEPENDENCIES foo! + CHECKSUMS + #{checksum_for_repo_gem(gem_repo4, "foo", "1.0", :empty => true)} + #{checksum_for_repo_gem(gem_repo4, "nokogiri", "1.14.2", :empty => true)} + BUNDLED WITH #{Bundler::VERSION} L @@ -1507,6 +1530,12 @@ RSpec.describe "bundle lock" do activesupport (= 7.0.4.3) govuk_app_config + CHECKSUMS + #{checksum_for_repo_gem gem_repo4, "actionpack", "7.0.4.3", :empty => true} + #{checksum_for_repo_gem gem_repo4, "activesupport", "7.0.4.3", :empty => true} + #{checksum_for_repo_gem gem_repo4, "govuk_app_config", "4.13.0", :empty => true} + #{checksum_for_repo_gem gem_repo4, "railties", "7.0.4.3", :empty => true} + BUNDLED WITH #{Bundler::VERSION} L diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index cf6a8d5be1..99ae3e8d07 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -300,7 +300,7 @@ RSpec.describe "bundle update" do previous_lockfile = lockfile - bundle "lock --update" + bundle "lock --update", :env => { "DEBUG" => "1" }, :verbose => true expect(lockfile).to eq(previous_lockfile) end @@ -539,6 +539,10 @@ RSpec.describe "bundle update" do expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9") expect(lockfile).to eq(expected_lockfile) + # needed because regressing to versions already present on the system + # won't add a checksum + expected_lockfile = expected_lockfile.gsub(/ sha256-[a-f0-9]+$/, "") + lockfile original_lockfile bundle "update" expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9") @@ -547,26 +551,7 @@ RSpec.describe "bundle update" do lockfile original_lockfile bundle "lock --update" expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9") - expect(lockfile).to eq <<~L - GEM - remote: #{file_uri_for(gem_repo4)}/ - specs: - activesupport (6.0.4.1) - tzinfo (~> 1.1) - tzinfo (1.2.9) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - activesupport (~> 6.0.0) - - CHECKSUMS - #{expected_checksums} - - BUNDLED WITH - #{Bundler::VERSION} - L + expect(lockfile).to eq expected_lockfile end end @@ -1283,11 +1268,26 @@ RSpec.describe "bundle update --bundler" do source "#{file_uri_for(gem_repo4)}" gem "rack" G - lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') - expected_checksum = checksum_for_repo_gem(gem_repo4, "rack", "1.0") + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (1.0) - FileUtils.rm_r gem_repo4 + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + CHECKSUMS + #{expected_checksum} + + BUNDLED WITH + #{Bundler::VERSION} + L + lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') bundle :update, :bundler => true, :artifice => "compact_index", :verbose => true expect(out).to include("Using bundler #{Bundler::VERSION}") @@ -1717,14 +1717,6 @@ RSpec.describe "bundle update conservative" do it "should only change direct dependencies when updating the lockfile with --conservative" do bundle "lock --update --conservative" - expected_checksums = construct_checksum_section do |c| - c.repo_gem gem_repo4, "isolated_dep", "2.0.1" - c.repo_gem gem_repo4, "isolated_owner", "1.0.2" - c.repo_gem gem_repo4, "shared_dep", "5.0.1" - c.repo_gem gem_repo4, "shared_owner_a", "3.0.2" - c.repo_gem gem_repo4, "shared_owner_b", "4.0.2" - end - expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo4)}/ @@ -1747,7 +1739,11 @@ RSpec.describe "bundle update conservative" do shared_owner_b CHECKSUMS - #{expected_checksums} + isolated_dep (2.0.1) + isolated_owner (1.0.2) + shared_dep (5.0.1) + shared_owner_a (3.0.2) + shared_owner_b (4.0.2) BUNDLED WITH #{Bundler::VERSION} diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb index f72726fec1..da8b6a90b1 100644 --- a/spec/bundler/install/gemfile/gemspec_spec.rb +++ b/spec/bundler/install/gemfile/gemspec_spec.rb @@ -721,7 +721,7 @@ RSpec.describe "bundle install from an existing gemspec" do CHECKSUMS activeadmin (2.9.0) - #{checksum_for_repo_gem gem_repo4, "jruby-openssl", "0.10.7", "java"} + jruby-openssl (0.10.7-java) #{checksum_for_repo_gem gem_repo4, "railties", "6.1.4"} BUNDLED WITH diff --git a/spec/bundler/install/gemfile/install_if_spec.rb b/spec/bundler/install/gemfile/install_if_spec.rb index 96b7f07d16..ced6f42d79 100644 --- a/spec/bundler/install/gemfile/install_if_spec.rb +++ b/spec/bundler/install/gemfile/install_if_spec.rb @@ -39,9 +39,9 @@ RSpec.describe "bundle install with install_if conditionals" do CHECKSUMS #{checksum_for_repo_gem gem_repo1, "activesupport", "2.3.5"} - #{checksum_for_repo_gem gem_repo1, "foo", "1.0"} + #{checksum_for_repo_gem gem_repo1, "foo", "1.0", :empty => true} #{checksum_for_repo_gem gem_repo1, "rack", "1.0.0"} - #{checksum_for_repo_gem gem_repo1, "thin", "1.0"} + #{checksum_for_repo_gem gem_repo1, "thin", "1.0", :empty => true} BUNDLED WITH #{Bundler::VERSION} diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb index 086d6c3ed1..5d0c759f4e 100644 --- a/spec/bundler/install/gemfile/path_spec.rb +++ b/spec/bundler/install/gemfile/path_spec.rb @@ -849,6 +849,10 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES foo! + CHECKSUMS + foo (1.0) + rack (0.9.1) + BUNDLED WITH #{Bundler::VERSION} G diff --git a/spec/bundler/install/gemfile/platform_spec.rb b/spec/bundler/install/gemfile/platform_spec.rb index de474d968e..bb62558deb 100644 --- a/spec/bundler/install/gemfile/platform_spec.rb +++ b/spec/bundler/install/gemfile/platform_spec.rb @@ -226,6 +226,12 @@ RSpec.describe "bundle install across platforms" do pry CHECKSUMS + #{checksum_for_repo_gem gem_repo4, "coderay", "1.1.2"} + #{checksum_for_repo_gem gem_repo4, "empyrean", "0.1.0"} + #{checksum_for_repo_gem gem_repo4, "ffi", "1.9.23", "java"} + #{checksum_for_repo_gem gem_repo4, "method_source", "0.9.0"} + #{checksum_for_repo_gem gem_repo4, "pry", "0.11.3", "java"} + #{checksum_for_repo_gem gem_repo4, "spoon", "0.0.6"} BUNDLED WITH #{Bundler::VERSION} @@ -260,6 +266,13 @@ RSpec.describe "bundle install across platforms" do pry CHECKSUMS + #{checksum_for_repo_gem gem_repo4, "coderay", "1.1.2"} + #{checksum_for_repo_gem gem_repo4, "empyrean", "0.1.0"} + #{checksum_for_repo_gem gem_repo4, "ffi", "1.9.23", "java"} + #{checksum_for_repo_gem gem_repo4, "method_source", "0.9.0"} + pry (0.11.3) + #{checksum_for_repo_gem gem_repo4, "pry", "0.11.3", "java"} + #{checksum_for_repo_gem gem_repo4, "spoon", "0.0.6"} BUNDLED WITH #{Bundler::VERSION} @@ -295,6 +308,12 @@ RSpec.describe "bundle install across platforms" do pry CHECKSUMS + #{checksum_for_repo_gem gem_repo4, "coderay", "1.1.2"} + #{checksum_for_repo_gem gem_repo4, "empyrean", "0.1.0"} + #{checksum_for_repo_gem gem_repo4, "ffi", "1.9.23", "java"} + #{checksum_for_repo_gem gem_repo4, "method_source", "0.9.0"} + #{checksum_for_repo_gem gem_repo4, "pry", "0.11.3", "java"} + #{checksum_for_repo_gem gem_repo4, "spoon", "0.0.6"} BUNDLED WITH 1.16.1 @@ -407,7 +426,7 @@ RSpec.describe "bundle install across platforms" do CHECKSUMS #{checksum_for_repo_gem(gem_repo1, "platform_specific", "1.0")} - #{checksum_for_repo_gem(gem_repo1, "platform_specific", "1.0", "java")} + #{checksum_for_repo_gem(gem_repo1, "platform_specific", "1.0", "java", :empty => true)} BUNDLED WITH #{Bundler::VERSION} diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index 4718d0dec1..6ec236b0c8 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -79,6 +79,9 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES google-protobuf + CHECKSUMS + google-protobuf (3.0.0.alpha.4.0) + BUNDLED WITH 2.1.4 L @@ -102,6 +105,7 @@ RSpec.describe "bundle install with specific platforms" do google-protobuf CHECKSUMS + google-protobuf (3.0.0.alpha.5.0.5.1) BUNDLED WITH #{Bundler::VERSION} @@ -622,8 +626,8 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static CHECKSUMS - #{checksum_for_repo_gem gem_repo4, "nokogiri", "1.13.0", "x86_64-darwin"} - #{checksum_for_repo_gem gem_repo4, "sorbet-static", "0.5.10601", "x86_64-darwin"} + #{checksum_for_repo_gem gem_repo4, "nokogiri", "1.13.0", "x86_64-darwin", :empty => true} + #{checksum_for_repo_gem gem_repo4, "sorbet-static", "0.5.10601", "x86_64-darwin", :empty => true} BUNDLED WITH #{Bundler::VERSION} @@ -807,6 +811,10 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES sorbet-static (= 0.5.10549) + CHECKSUMS + #{checksum_for_repo_gem gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-20"} + #{checksum_for_repo_gem gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-21"} + BUNDLED WITH #{Bundler::VERSION} L @@ -828,7 +836,7 @@ RSpec.describe "bundle install with specific platforms" do CHECKSUMS #{checksum_for_repo_gem gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-20"} - #{checksum_for_repo_gem gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-21"} + #{checksum_for_repo_gem gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-21", :empty => true} BUNDLED WITH #{Bundler::VERSION} @@ -884,15 +892,15 @@ RSpec.describe "bundle install with specific platforms" do nokogiri (1.13.8-#{Gem::Platform.local}) PLATFORMS - #{lockfile_platforms_for([specific_local_platform, "ruby"])} + #{lockfile_platforms("ruby")} DEPENDENCIES nokogiri tzinfo (~> 1.2) CHECKSUMS - #{checksum_for_repo_gem gem_repo4, "nokogiri", "1.13.8"} - #{checksum_for_repo_gem gem_repo4, "nokogiri", "1.13.8", "arm64-darwin-22"} + #{checksum_for_repo_gem gem_repo4, "nokogiri", "1.13.8", :empty => true} + #{checksum_for_repo_gem gem_repo4, "nokogiri", "1.13.8", Gem::Platform.local, :empty => true} BUNDLED WITH #{Bundler::VERSION} @@ -946,6 +954,10 @@ RSpec.describe "bundle install with specific platforms" do concurrent-ruby rack + CHECKSUMS + #{checksum_for_repo_gem gem_repo4, "concurrent-ruby", "1.2.2", :empty => true} + #{checksum_for_repo_gem gem_repo4, "rack", "3.0.7", :empty => true} + BUNDLED WITH #{Bundler::VERSION} L diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index 20e3d93175..f723c0da73 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -882,18 +882,33 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G + api_checksum = Spec::Checksums::ChecksumsBuilder.new.repo_gem(gem_repo1, "rack", "1.0.0").first.checksums.fetch("sha256") + + gem_path = if Bundler.feature_flag.global_gem_cache? + default_cache_path.dirname.join("cache", "gems", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "rack-1.0.0.gem") + else + default_cache_path.dirname.join("rack-1.0.0.gem") + end + expect(exitstatus).to eq(19) expect(err). - to include("Bundler cannot continue installing rack (1.0.0)."). - and include("The checksum for the downloaded `rack-1.0.0.gem` does not match the checksum given by the server."). - and include("This means the contents of the downloaded gem is different from what was uploaded to the server, and could be a potential security issue."). - and include("To resolve this issue:"). - and include("1. delete the downloaded gem located at: `#{default_bundle_path}/gems/rack-1.0.0/rack-1.0.0.gem`"). - and include("2. run `bundle install`"). - and include("If you wish to continue installing the downloaded gem, and are certain it does not pose a security issue despite the mismatching checksum, do the following:"). - and include("1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification"). - and include("2. run `bundle install`"). - and match(/\(More info: The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/) + to eq <<~E.strip + Bundler cannot continue installing rack (1.0.0). + The checksum for the downloaded `rack-1.0.0.gem` does not match the known checksum for the gem. + This means the contents of the downloaded gem is different from what was uploaded to the server or first used by your teammates, and could be a potential security issue. + + To resolve this issue: + 1. delete the downloaded gem located at: `#{gem_path}` + 2. run `bundle install` + + If you are sure that the new checksum is correct, you can remove the `rack (1.0.0)` entry under the lockfile `CHECKSUMS` section and rerun `bundle install`. + + If you wish to continue installing the downloaded gem, and are certain it does not pose a security issue despite the mismatching checksum, do the following: + 1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification + 2. run `bundle install` + + (More info: The expected SHA256 checksum was "69b69b69b69b69b69b69b69b69b69b69b69b69b69b69b69b69b69b69b69b69b69b", but the checksum for the downloaded gem was "#{api_checksum}". The expected checksum came from: API response from http://localgemserver.test/) + E end it "raises when the checksum is the wrong length" do @@ -901,8 +916,8 @@ The checksum of /versions does not match the checksum provided by the server! So source "#{source_uri}" gem "rack" G - expect(exitstatus).to eq(5) - expect(err).to include("The given checksum for rack-1.0.0 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest") + expect(exitstatus).to eq(14) + expect(err).to include("The given checksum for rack-0.9.1 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest") end it "does not raise when disable_checksum_validation is set" do diff --git a/spec/bundler/install/yanked_spec.rb b/spec/bundler/install/yanked_spec.rb index bc84e25417..a84772fa78 100644 --- a/spec/bundler/install/yanked_spec.rb +++ b/spec/bundler/install/yanked_spec.rb @@ -161,7 +161,8 @@ RSpec.context "when resolving a bundle that includes yanked gems, but unlocking foo CHECKSUMS - #{checksum_for_repo_gem(gem_repo4, "bar", "2.0.0")} + #{checksum_for_repo_gem(gem_repo4, "bar", "2.0.0", :empty => true)} + #{checksum_for_repo_gem(gem_repo4, "foo", "9.0.0", :empty => true)} BUNDLED WITH #{Bundler::VERSION} diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb index 0f0169062e..04355792ef 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -146,6 +146,9 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack + CHECKSUMS + #{checksum_for_repo_gem(gem_repo2, "rack", "1.0.0")} + BUNDLED WITH #{version} L @@ -171,6 +174,9 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack + CHECKSUMS + #{checksum_for_repo_gem(gem_repo2, "rack", "1.0.0")} + BUNDLED WITH #{version} G @@ -677,6 +683,10 @@ RSpec.describe "the lockfile format" do DEPENDENCIES ckeditor! + CHECKSUMS + #{checksum_for_repo_gem(gem_repo4, "ckeditor", "4.0.8", :empty => true)} + #{checksum_for_repo_gem(gem_repo4, "orm_adapter", "0.4.1", :empty => true)} + BUNDLED WITH #{Bundler::VERSION} L @@ -1516,6 +1526,10 @@ RSpec.describe "the lockfile format" do DEPENDENCIES direct_dependency + CHECKSUMS + #{checksum_for_repo_gem(gem_repo4, "direct_dependency", "4.5.6")} + #{checksum_for_repo_gem(gem_repo4, "indirect_dependency", "1.2.3")} + BUNDLED WITH #{Bundler::VERSION} G @@ -1570,6 +1584,10 @@ RSpec.describe "the lockfile format" do DEPENDENCIES minitest-bisect + CHECKSUMS + #{checksum_for_repo_gem(gem_repo4, "minitest-bisect", "1.6.0")} + #{checksum_for_repo_gem(gem_repo4, "path_expander", "1.1.1")} + BUNDLED WITH #{Bundler::VERSION} L diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb index 3001dd279a..afbf053636 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -48,6 +48,9 @@ RSpec.configure do |config| config.silence_filter_announcements = !ENV["TEST_ENV_NUMBER"].nil? + config.backtrace_exclusion_patterns << + %r{./spec/(spec_helper\.rb|support/.+)} + config.disable_monkey_patching! # Since failures cause us to keep a bunch of long strings in memory, stop diff --git a/spec/bundler/support/checksums.rb b/spec/bundler/support/checksums.rb index 93e27402c7..ba7770fda8 100644 --- a/spec/bundler/support/checksums.rb +++ b/spec/bundler/support/checksums.rb @@ -7,19 +7,19 @@ module Spec @checksums = [] end - def repo_gem(gem_repo, gem_name, gem_version, platform = nil) + def repo_gem(gem_repo, gem_name, gem_version, platform = nil, empty: false) gem_file = if platform "#{gem_repo}/gems/#{gem_name}-#{gem_version}-#{platform}.gem" else "#{gem_repo}/gems/#{gem_name}-#{gem_version}.gem" end - checksum = sha256_checksum(gem_file) - @checksums << Bundler::Checksum.new(gem_name, gem_version, platform, [checksum]) + checksum = { "sha256" => sha256_checksum(gem_file) } unless empty + @checksums << Bundler::Checksum.new(gem_name, gem_version, platform, checksum) end def to_lock - @checksums.map(&:to_lock).join.strip + @checksums.map(&:to_lock).sort.join.strip end private @@ -29,7 +29,7 @@ module Spec digest = Bundler::SharedHelpers.digest(:SHA256).new digest << f.read(16_384) until f.eof? - "sha256-#{digest.hexdigest!}" + digest.hexdigest! end end end @@ -42,9 +42,9 @@ module Spec checksums.to_lock end - def checksum_for_repo_gem(gem_repo, gem_name, gem_version, platform = nil) + def checksum_for_repo_gem(*args, **kwargs) construct_checksum_section do |c| - c.repo_gem(gem_repo, gem_name, gem_version, platform) + c.repo_gem(*args, **kwargs) end end end |