diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1325988 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,62 @@ +name: Publish gem to rubygems.org + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + push: + if: github.repository == 'bcrypt-ruby/bcrypt-ruby' + runs-on: ubuntu-latest + + environment: + name: rubygems.org + url: https://rubygems.org/gems/bcrypt-ruby + + permissions: + contents: write + id-token: write + + strategy: + matrix: + ruby: ["ruby", "jruby"] + + steps: + - name: Harden Runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + # https://github.com/rubygems/rubygems/issues/5882 + - name: Install dependencies and build for JRuby + run: | + sudo apt install default-jdk maven + gem update --system + gem install ruby-maven rake-compiler --no-document + rake compile + if: matrix.ruby == 'jruby' + + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + + - name: Publish to RubyGems + uses: rubygems/release-gem@v1 + + - name: Create GitHub release + run: | + tag_name="$(git describe --tags --abbrev=0)" + gh release create "${tag_name}" --verify-tag --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: matrix.ruby != 'jruby' diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 82c49b3..c5fb94b 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -3,79 +3,50 @@ name: Test Suite # Run against all commits and pull requests. on: [ push, pull_request ] +env: + JAVA_OPTS: '-Xms60M -Xmx1G' + jobs: - test_matrix: + ruby-versions: + uses: ruby/actions/.github/workflows/ruby_versions.yml@master + with: + engine: cruby + min_version: 3.1 + + test: + needs: ruby-versions + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: - - ubuntu - - macos - - windows - ruby: - - 2.1 - - 2.2 - - 2.3 - - 2.4 - - 2.5 - - 2.6 - - 2.7 - - '3.0' - - 3.1 - - 3.2 - - head - - jruby - - jruby-head - - truffleruby - - truffleruby-head - - mingw - exclude: - - { os: ubuntu, ruby: 2.1 } - - { os: ubuntu, ruby: 2.2 } - - { os: ubuntu, ruby: mingw } - - { os: macos, ruby: mingw } - - { os: windows, ruby: truffleruby } - - { os: windows, ruby: truffleruby-head } - - runs-on: ${{ matrix.os }}-latest + ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} + os: [ ubuntu-latest, macos-latest, windows-latest ] + include: + - { os: windows-latest, ruby: jruby-head } + - { os: macos-latest, ruby: jruby-head } + - { os: ubuntu-latest, ruby: jruby-head } + - { os: windows-latest, ruby: ucrt } + - { os: windows-latest, ruby: mingw } steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - name: Set up Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby-pkgs@v1 with: ruby-version: ${{ matrix.ruby }} - bundler-cache: true - env: - JAVA_OPTS: -Djdk.io.File.enableADS=true - - name: Run tests - run: bundle exec rake default - env: - JAVA_OPTS: -Djdk.io.File.enableADS=true + apt-get: "haveged libyaml-dev" + brew: libyaml + vcpkg: libyaml - test_matrix_old_rubies: - strategy: - fail-fast: false - matrix: - ruby: - - 2.1 - - 2.2 + - name: Set JRuby ENV vars + run: | + echo 'JAVA_OPTS=-Xmx1g' >> $GITHUB_ENV + if: ${{ ! startsWith(matrix.ruby, 'jruby') }} - runs-on: ubuntu-20.04 + - name: Install dependencies + run: bundle install --jobs 1 - steps: - - uses: actions/checkout@v2 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true - name: Run tests - run: bundle exec rake default - - finish: - runs-on: ubuntu-latest - needs: [ test_matrix ] - steps: - - name: Wait for status checks - run: echo "All Green!" + run: bundle exec rake + continue-on-error: ${{ matrix.ruby == 'jruby-head' }} diff --git a/CHANGELOG b/CHANGELOG index 92a8114..1682923 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +3.1.21 Dec 31 2025 + - Use constant time comparisons + - Mark as Ractor safe + 3.1.20 Nov 17 2023 - Limit packaged files -- decrease gem filesize by ~28% [GH #272 by @pusewicz] diff --git a/README.md b/README.md index dd55971..88598ad 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ re-hash those passwords. This vulnerability only affected the JRuby gem. The bcrypt gem is available on the following Ruby platforms: * JRuby -* RubyInstaller 2.0 – 3.0 builds on Windows with the DevKit -* Any 2.0 – 3.0 Ruby on a BSD/OS X/Linux system with a compiler +* RubyInstaller builds on Windows with the DevKit +* Any modern Ruby on a BSD/OS X/Linux system with a compiler ## How to use `bcrypt()` in your Rails application diff --git a/Rakefile b/Rakefile index 58b51fe..95e22a5 100644 --- a/Rakefile +++ b/Rakefile @@ -5,6 +5,8 @@ require 'rake/javaextensiontask' require 'rake/clean' require 'rdoc/task' require 'benchmark' +require "bundler" +Bundler::GemHelper.install_tasks CLEAN.include( "tmp", diff --git a/bcrypt.gemspec b/bcrypt.gemspec index 68a31ea..e35a402 100644 --- a/bcrypt.gemspec +++ b/bcrypt.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = 'bcrypt' - s.version = '3.1.20' + s.version = '3.1.21' s.summary = "OpenBSD's bcrypt() password hashing algorithm." s.description = <<-EOF @@ -14,6 +14,8 @@ Gem::Specification.new do |s| s.add_development_dependency 'rake-compiler', '~> 1.2.0' s.add_development_dependency 'rspec', '>= 3' + s.add_development_dependency 'rdoc', '>= 7.0.3' + s.add_development_dependency 'benchmark', '>= 0.5.0' s.rdoc_options += ['--title', 'bcrypt-ruby', '--line-numbers', '--inline-source', '--main', 'README.md'] s.extra_rdoc_files += ['README.md', 'COPYING', 'CHANGELOG', *Dir['lib/**/*.rb']] @@ -24,4 +26,6 @@ Gem::Specification.new do |s| s.email = "coda.hale@gmail.com" s.homepage = "https://github.com/bcrypt-ruby/bcrypt-ruby" s.license = "MIT" + + s.metadata["changelog_uri"] = s.homepage + "/blob/master/CHANGELOG" end diff --git a/ext/mri/bcrypt_ext.c b/ext/mri/bcrypt_ext.c index 757b068..3e5ac1c 100644 --- a/ext/mri/bcrypt_ext.c +++ b/ext/mri/bcrypt_ext.c @@ -111,6 +111,10 @@ static VALUE bc_crypt(VALUE self, VALUE key, VALUE setting) { /* Create the BCrypt and BCrypt::Engine modules, and populate them with methods. */ void Init_bcrypt_ext(){ +#ifdef HAVE_RB_EXT_RACTOR_SAFE + rb_ext_ractor_safe(true); +#endif + mBCrypt = rb_define_module("BCrypt"); cBCryptEngine = rb_define_class_under(mBCrypt, "Engine", rb_cObject); diff --git a/lib/bcrypt/password.rb b/lib/bcrypt/password.rb index 4a2c140..0c432d6 100644 --- a/lib/bcrypt/password.rb +++ b/lib/bcrypt/password.rb @@ -73,8 +73,17 @@ def initialize(raw_hash) # @password == @password.to_s # => False # @password.to_s == @password # => True # @password.to_s == @password.to_s # => True + # + # secret == @password # => probably False, because the secret is not a BCrypt::Password instance. def ==(secret) - super(BCrypt::Engine.hash_secret(secret, @salt)) + hash = BCrypt::Engine.hash_secret(secret, @salt) + + return false if hash.strip.empty? || strip.empty? || hash.bytesize != bytesize + + # Constant time comparison so they can't tell the length. + res = 0 + bytesize.times { |i| res |= getbyte(i) ^ hash.getbyte(i) } + res == 0 end alias_method :is_password?, :== diff --git a/spec/bcrypt/engine_spec.rb b/spec/bcrypt/engine_spec.rb index b527a6b..8abbe9f 100644 --- a/spec/bcrypt/engine_spec.rb +++ b/spec/bcrypt/engine_spec.rb @@ -17,7 +17,7 @@ BCrypt::Password.create("testing testing", :cost => BCrypt::Engine::MIN_COST + 1) min_time_ms = (Time.now - start_time) * 1000 first = BCrypt::Engine.calibrate(min_time_ms) - second = BCrypt::Engine.calibrate(min_time_ms * 4) + second = BCrypt::Engine.calibrate(min_time_ms * 5) expect(second).to be > first end end