aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Emde <martin.emde@gmail.com>2023-10-20 20:16:24 -0700
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2023-10-23 13:59:01 +0900
commit6dcd4e90d8d2a2db06a140cf10c5d9519360fc69 (patch)
tree6898fd5e6ddfdf91b1a03cda886a7c9ed101a751
parentc667de72ff9de195e1cab4b1937973e841ff89ae (diff)
downloadruby-6dcd4e90d8d2a2db06a140cf10c5d9519360fc69.tar.gz
[rubygems/rubygems] Handle base64 encoded checksums in lockfile for future compatibility.
Save checksums using = as separator. https://github.com/rubygems/rubygems/commit/a36ad7d160
-rw-r--r--lib/bundler/checksum.rb33
-rw-r--r--spec/bundler/bundler/lockfile_parser_spec.rb33
-rw-r--r--spec/bundler/commands/lock_spec.rb2
-rw-r--r--spec/bundler/install/gemfile/sources_spec.rb14
-rw-r--r--spec/bundler/install/gems/compact_index_spec.rb20
-rw-r--r--spec/bundler/support/checksums.rb2
6 files changed, 73 insertions, 31 deletions
diff --git a/lib/bundler/checksum.rb b/lib/bundler/checksum.rb
index 7539522908..f8fd386569 100644
--- a/lib/bundler/checksum.rb
+++ b/lib/bundler/checksum.rb
@@ -2,6 +2,7 @@
module Bundler
class Checksum
+ ALGO_SEPARATOR = "="
DEFAULT_ALGORITHM = "sha256"
private_constant :DEFAULT_ALGORITHM
DEFAULT_BLOCK_SIZE = 16_384
@@ -15,20 +16,24 @@ module Bundler
Checksum.new(algo, digest.hexdigest!, Source.new(:gem, pathname))
end
- def from_api(digest, source_uri)
- # transform the bytes from base64 to hex, switch to unpack1 when we drop older rubies
- hexdigest = digest.length == 44 ? digest.unpack("m0").first.unpack("H*").first : digest
-
- if hexdigest.length != 64
- raise ArgumentError, "#{digest.inspect} is not a valid SHA256 hexdigest nor base64digest"
- end
-
- Checksum.new(DEFAULT_ALGORITHM, hexdigest, Source.new(:api, source_uri))
+ def from_api(digest, source_uri, algo = DEFAULT_ALGORITHM)
+ Checksum.new(algo, to_hexdigest(digest, algo), Source.new(:api, source_uri))
end
def from_lock(lock_checksum, lockfile_location)
- algo, digest = lock_checksum.strip.split("-", 2)
- Checksum.new(algo, digest, Source.new(:lock, lockfile_location))
+ algo, digest = lock_checksum.strip.split(ALGO_SEPARATOR, 2)
+ Checksum.new(algo, to_hexdigest(digest, algo), Source.new(:lock, lockfile_location))
+ end
+
+ def to_hexdigest(digest, algo = DEFAULT_ALGORITHM)
+ return digest unless algo == DEFAULT_ALGORITHM
+ return digest if digest.match?(/\A[0-9a-f]{64}\z/i)
+ if digest.match?(%r{\A[-0-9a-z_+/]{43}={0,2}\z}i)
+ digest = digest.tr("-_", "+/") # fix urlsafe base64
+ # transform to hex. Use unpack1 when we drop older rubies
+ return digest.unpack("m0").first.unpack("H*").first
+ end
+ raise ArgumentError, "#{digest.inspect} is not a valid SHA256 hex or base64 digest"
end
end
@@ -59,7 +64,7 @@ module Bundler
end
def to_lock
- "#{algo}-#{digest}"
+ "#{algo}#{ALGO_SEPARATOR}#{digest}"
end
def merge!(other)
@@ -87,7 +92,7 @@ module Bundler
end
def inspect
- abbr = "#{algo}-#{digest[0, 8]}"
+ abbr = "#{algo}#{ALGO_SEPARATOR}#{digest[0, 8]}"
from = "from #{sources.join(" and ")}"
"#<#{self.class}:#{object_id} #{abbr} #{from}>"
end
@@ -109,7 +114,7 @@ module Bundler
end
# phrased so that the usual string format is grammatically correct
- # rake (10.3.2) sha256-abc123 from #{to_s}
+ # rake (10.3.2) sha256=abc123 from #{to_s}
def to_s
case type
when :lock
diff --git a/spec/bundler/bundler/lockfile_parser_spec.rb b/spec/bundler/bundler/lockfile_parser_spec.rb
index a42bdad6e4..6d306da916 100644
--- a/spec/bundler/bundler/lockfile_parser_spec.rb
+++ b/spec/bundler/bundler/lockfile_parser_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe Bundler::LockfileParser do
rake
CHECKSUMS
- rake (10.3.2) sha256-814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8
+ rake (10.3.2) sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8
RUBY VERSION
ruby 2.1.3p242
@@ -121,8 +121,8 @@ RSpec.describe Bundler::LockfileParser do
let(:lockfile_path) { Bundler.default_lockfile.relative_path_from(Dir.pwd) }
let(:rake_checksum) do
Bundler::Checksum.from_lock(
- "sha256-814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8",
- "#{lockfile_path}:??:1"
+ "sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8",
+ "#{lockfile_path}:20:17"
)
end
@@ -163,8 +163,33 @@ RSpec.describe Bundler::LockfileParser do
include_examples "parsing"
end
+ context "when the checksum is urlsafe base64 encoded" do
+ let(:lockfile_contents) do
+ super().sub(
+ "sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8",
+ "sha256=gUgow08TFdfnt-gpUYRXfMTpabrWFWrAadAtY_WNgug="
+ )
+ end
+ include_examples "parsing"
+ end
+
+ context "when the checksum is of an unknown algorithm" do
+ let(:lockfile_contents) do
+ super().sub(
+ "sha256=",
+ "sha512=pVDn9GLmcFkz8vj1ueiVxj5uGKkAyaqYjEX8zG6L5O4BeVg3wANaKbQdpj/B82Nd/MHVszy6polHcyotUdwilQ==,sha256="
+ )
+ end
+ include_examples "parsing"
+
+ it "preserves the checksum as is" do
+ checksum = subject.sources.last.checksum_store.fetch(specs.last, "sha512")
+ expect(checksum.algo).to eq("sha512")
+ end
+ end
+
context "when CHECKSUMS has duplicate checksums in the lockfile that don't match" do
- let(:bad_checksum) { "sha256-c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11" }
+ let(:bad_checksum) { "sha256=c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11" }
let(:lockfile_contents) { super().split(/(?<=CHECKSUMS\n)/m).insert(1, " rake (10.3.2) #{bad_checksum}\n").join }
it "raises a security error" do
diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb
index 1cfc702a80..fa89b98c8d 100644
--- a/spec/bundler/commands/lock_spec.rb
+++ b/spec/bundler/commands/lock_spec.rb
@@ -214,7 +214,7 @@ RSpec.describe "bundle lock" do
end
it "preserves unknown checksum algorithms" do
- lockfile @lockfile.gsub(/(sha256-[a-f0-9]+)$/, "constant-true,\\1,xyz-123")
+ lockfile @lockfile.gsub(/(sha256=[a-f0-9]+)$/, "constant=true,\\1,xyz=123")
previous_lockfile = read_lockfile
diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb
index 16b9d82104..64eed1a2f4 100644
--- a/spec/bundler/install/gemfile/sources_spec.rb
+++ b/spec/bundler/install/gemfile/sources_spec.rb
@@ -129,7 +129,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
end
it "works in standalone mode", :bundler => "< 3" do
- gem_checksum = checksum_for_repo_gem(gem_repo4, "foo", "1.0").split("-").last
+ gem_checksum = checksum_for_repo_gem(gem_repo4, "foo", "1.0").split(Bundler::Checksum::ALGO_SEPARATOR).last
bundle "install --standalone", :artifice => "compact_index", :env => { "BUNDLER_SPEC_FOO_CHECKSUM" => gem_checksum }
end
end
@@ -337,7 +337,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(err).to eq(<<~E.strip)
[DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source.
Bundler found mismatched checksums. This is a potential security risk.
- rack (1.0.0) sha256-#{rack_checksum}
+ rack (1.0.0) sha256=#{rack_checksum}
from the API at https://gem.repo2/
and the API at https://gem.repo1/
#{checksum_for_repo_gem(gem_repo2, "rack", "1.0.0")}
@@ -354,7 +354,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
end
it "installs from the other source and warns about ambiguous gems when the sources have the same checksum", :bundler => "< 3" do
- gem_checksum = checksum_for_repo_gem(gem_repo2, "rack", "1.0.0").split("-").last
+ gem_checksum = checksum_for_repo_gem(gem_repo2, "rack", "1.0.0").split(Bundler::Checksum::ALGO_SEPARATOR).last
bundle :install, :artifice => "compact_index", :env => { "BUNDLER_SPEC_RACK_CHECKSUM" => gem_checksum, "DEBUG" => "1" }
expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
@@ -1302,16 +1302,16 @@ RSpec.describe "bundle install with gems on multiple sources" do
bundle "install", :artifice => "compact_index", :raise_on_error => false
- api_checksum1 = checksum_for_repo_gem(gem_repo1, "rack", "0.9.1").split("sha256-").last
- api_checksum3 = checksum_for_repo_gem(gem_repo3, "rack", "0.9.1").split("sha256-").last
+ api_checksum1 = checksum_for_repo_gem(gem_repo1, "rack", "0.9.1").split("sha256=").last
+ api_checksum3 = checksum_for_repo_gem(gem_repo3, "rack", "0.9.1").split("sha256=").last
expect(exitstatus).to eq(37)
expect(err).to eq(<<~E.strip)
[DEPRECATED] Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure.
Bundler found mismatched checksums. This is a potential security risk.
- rack (0.9.1) sha256-#{api_checksum3}
+ rack (0.9.1) sha256=#{api_checksum3}
from the API at https://gem.repo3/
- rack (0.9.1) sha256-#{api_checksum1}
+ rack (0.9.1) sha256=#{api_checksum1}
from the API at https://gem.repo1/
Mismatched checksums each have an authoritative source:
diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb
index 03c25d53bf..305d253def 100644
--- a/spec/bundler/install/gems/compact_index_spec.rb
+++ b/spec/bundler/install/gems/compact_index_spec.rb
@@ -876,13 +876,25 @@ The checksum of /versions does not match the checksum provided by the server! So
end
describe "checksum validation" do
+ it "handles checksums from the server in base64" do
+ api_checksum = checksum_for_repo_gem(gem_repo1, "rack", "1.0.0").split("sha256=").last
+ rack_checksum = [[api_checksum].pack("H*")].pack("m0")
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_RACK_CHECKSUM" => rack_checksum }
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+
it "raises when the checksum does not match" do
install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :raise_on_error => false
source "#{source_uri}"
gem "rack"
G
- api_checksum = checksum_for_repo_gem(gem_repo1, "rack", "1.0.0").split("sha256-").last
+ api_checksum = checksum_for_repo_gem(gem_repo1, "rack", "1.0.0").split("sha256=").last
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")
@@ -893,9 +905,9 @@ The checksum of /versions does not match the checksum provided by the server! So
expect(exitstatus).to eq(37)
expect(err).to eq <<~E.strip
Bundler found mismatched checksums. This is a potential security risk.
- rack (1.0.0) sha256-2222222222222222222222222222222222222222222222222222222222222222
+ rack (1.0.0) sha256=2222222222222222222222222222222222222222222222222222222222222222
from the API at http://localgemserver.test/
- rack (1.0.0) sha256-#{api_checksum}
+ rack (1.0.0) sha256=#{api_checksum}
from the gem at #{gem_path}
If you trust the API at http://localgemserver.test/, to resolve this issue you can:
@@ -913,7 +925,7 @@ The checksum of /versions does not match the checksum provided by the server! So
gem "rack"
G
expect(exitstatus).to eq(14)
- expect(err).to include("Invalid checksum for rack-0.9.1: \"checksum!\" is not a valid SHA256 hexdigest nor base64digest")
+ expect(err).to include('Invalid checksum for rack-0.9.1: "checksum!" is not a valid SHA256 hex or base64 digest')
end
it "does not raise when disable_checksum_validation is set" do
diff --git a/spec/bundler/support/checksums.rb b/spec/bundler/support/checksums.rb
index b5579d5671..f0cac4219a 100644
--- a/spec/bundler/support/checksums.rb
+++ b/spec/bundler/support/checksums.rb
@@ -61,7 +61,7 @@ module Spec
checksums = checksums.each_line.map do |line|
if prefixes.nil? || line.match?(prefixes)
- line.gsub(/ sha256-[a-f0-9]{64}/i, "")
+ line.gsub(/ sha256=[a-f0-9]{64}/i, "")
else
line
end