diff --git a/lib/rack/session/cookie.rb b/lib/rack/session/cookie.rb index ed9ee58..a208bcf 100644 --- a/lib/rack/session/cookie.rb +++ b/lib/rack/session/cookie.rb @@ -108,7 +108,7 @@ module Rack if session_data && digest ok = @secrets.any? do |secret| - secret && digest == generate_hmac(session_data, secret) + secret && Rack::Utils.secure_compare(digest, generate_hmac(session_data, secret)) end end diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb index b706279..6576dd2 100644 --- a/lib/rack/utils.rb +++ b/lib/rack/utils.rb @@ -336,6 +336,18 @@ module Rack end module_function :byte_ranges + # Constant time string comparison. + def secure_compare(a, b) + return false unless bytesize(a) == bytesize(b) + + l = a.unpack("C*") + + r, i = 0, -1 + b.each_byte { |v| r |= v ^ l[i+=1] } + r == 0 + end + module_function :secure_compare + # Context allows the use of a compatible middleware at different points # in a request handling stack. A compatible middleware must define # #context which should take the arguments env and app. The first of which diff --git a/test/spec_utils.rb b/test/spec_utils.rb index 32d0a6f..69e3fbb 100644 --- a/test/spec_utils.rb +++ b/test/spec_utils.rb @@ -322,6 +322,11 @@ describe Rack::Utils do Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6 end + should "should perform constant time string comparison" do + Rack::Utils.secure_compare('a', 'a').should.equal true + Rack::Utils.secure_compare('a', 'b').should.equal false + end + should "return status code for integer" do Rack::Utils.status_code(200).should.equal 200 end