diff options
Diffstat (limited to 'vendor/gems/bundler-checksum/lib/bundler/checksum.rb')
-rw-r--r-- | vendor/gems/bundler-checksum/lib/bundler/checksum.rb | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/vendor/gems/bundler-checksum/lib/bundler/checksum.rb b/vendor/gems/bundler-checksum/lib/bundler/checksum.rb new file mode 100644 index 00000000000..c8d78eba111 --- /dev/null +++ b/vendor/gems/bundler-checksum/lib/bundler/checksum.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require 'bundler' +require 'bundler/checksum/version' +require 'json' + +module Bundler + module Patches + # This module monkey-patches Bundler to check Gemfile.checksum + # when installing gems that are from RubyGems + module RubyGemsInstallerPatch + def pre_install_checks + super && validate_local_package_checksum + end + + private + + def validate_local_package_checksum + cached_checksum = fetch_checksum_from_file(spec) + + if cached_checksum.nil? + raise SecurityError, "Cached checksum for #{spec.full_name} not found. Please (re-)generate Gemfile.checksum" + end + + validate_file_checksum(cached_checksum) + end + + def fetch_checksum_from_file(spec) + ::Bundler::Checksum.checksum_for(spec.name, spec.version.to_s, spec.platform.to_s) + end + + # Modified from + # https://github.com/rubygems/rubygems/blob/243173279e79a38f03e318eea8825d1c8824e119/bundler/lib/bundler/rubygems_gem_installer.rb#L116 + def validate_file_checksum(checksum) + return true if Bundler.settings[:disable_checksum_validation] + + source = @package.instance_variable_get(:@gem) + + # Contary to upstream, we raise instead of silently returning + raise "#{@package.inspect} does not have :@gem" unless source + raise "#{source.inspect} does not respond to :with_read_io" unless source.respond_to?(:with_read_io) + + digest = source.with_read_io do |io| + digest = SharedHelpers.digest(:SHA256).new + digest << io.read(16_384) until io.eof? + io.rewind + send(checksum_type(checksum), digest) + end + unless digest == checksum + 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 from the checksum file. This means the contents of the downloaded \ + gem is different from what was recorded in the checksum file, and could be potential security issue. + gem is different from what was uploaded to the server, 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` + 2. run `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 #{digest.inspect}.) + MESSAGE + end + true + end + end + end +end + +module Bundler + module Checksum + class << self + def checksum_file + @checksum_file ||= File.join(File.dirname(Bundler.default_gemfile), 'Gemfile.checksum') + end + + def checksums_from_file + @checksums_from_file ||= JSON.parse(File.open(checksum_file).read, symbolize_names: true) + rescue JSON::ParserError => e + raise "Invalid checksum file: #{e.message}" + end + + def checksum_for(gem_name, gem_version, gem_platform) + item = checksums_from_file.detect do |item| + item[:name] == gem_name && + item[:platform] == gem_platform && + item[:version] == gem_version + end + + item&.fetch(:checksum) + end + + def patch! + return if defined?(@patched) && @patched + @patched = true + + Bundler.ui.info "Patching bundler with bundler-checksum..." + require 'bundler/rubygems_gem_installer' + ::Bundler::RubyGemsGemInstaller.prepend(Bundler::Patches::RubyGemsInstallerPatch) + end + end + end +end |