summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/bundler/checksum.rb218
-rw-r--r--lib/bundler/definition.rb8
-rw-r--r--lib/bundler/endpoint_specification.rb12
-rw-r--r--lib/bundler/fetcher.rb13
-rw-r--r--lib/bundler/lazy_specification.rb15
-rw-r--r--lib/bundler/lockfile_generator.rb13
-rw-r--r--lib/bundler/lockfile_parser.rb80
-rw-r--r--lib/bundler/plugin/api/source.rb3
-rw-r--r--lib/bundler/remote_specification.rb44
-rw-r--r--lib/bundler/rubygems_gem_installer.rb61
-rw-r--r--lib/bundler/source.rb2
-rw-r--r--lib/bundler/source/path.rb1
-rw-r--r--lib/bundler/source/rubygems.rb3
-rw-r--r--lib/bundler/stub_specification.rb10
-rw-r--r--lib/rubygems/specification.rb20
-rw-r--r--spec/bundler/bundler/definition_spec.rb2
-rw-r--r--spec/bundler/cache/gems_spec.rb1
-rw-r--r--spec/bundler/commands/check_spec.rb4
-rw-r--r--spec/bundler/commands/clean_spec.rb2
-rw-r--r--spec/bundler/commands/lock_spec.rb89
-rw-r--r--spec/bundler/commands/update_spec.rb62
-rw-r--r--spec/bundler/install/gemfile/gemspec_spec.rb2
-rw-r--r--spec/bundler/install/gemfile/install_if_spec.rb4
-rw-r--r--spec/bundler/install/gemfile/path_spec.rb4
-rw-r--r--spec/bundler/install/gemfile/platform_spec.rb21
-rw-r--r--spec/bundler/install/gemfile/specific_platform_spec.rb24
-rw-r--r--spec/bundler/install/gems/compact_index_spec.rb39
-rw-r--r--spec/bundler/install/yanked_spec.rb3
-rw-r--r--spec/bundler/lock/lockfile_spec.rb18
-rw-r--r--spec/bundler/spec_helper.rb3
-rw-r--r--spec/bundler/support/checksums.rb14
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