Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion lib/jwt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def self.decode(jwt, key=nil, verify=true, &keyfinder)

begin
if ["HS256", "HS384", "HS512"].include?(algo)
raise JWT::DecodeError.new("Signature verification failed") unless signature == sign_hmac(algo, signing_input, key)
raise JWT::DecodeError.new("Signature verification failed") unless secure_compare(signature, sign_hmac(algo, signing_input, key))
elsif ["RS256", "RS384", "RS512"].include?(algo)
raise JWT::DecodeError.new("Signature verification failed") unless verify_rsa(algo, key, signing_input, signature)
else
Expand All @@ -92,4 +92,15 @@ def self.decode(jwt, key=nil, verify=true, &keyfinder)
payload
end

# From devise
# constant-time comparison algorithm to prevent timing attacks
def self.secure_compare(a, b)
return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize
l = a.unpack "C#{a.bytesize}"

res = 0
b.each_byte { |byte| res |= byte ^ l.shift }
res == 0
end

end
30 changes: 30 additions & 0 deletions spec/jwt_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,36 @@
decoded_payload.should == @payload
end

it "does not use == to compare digests" do
secret = "secret"
jwt = JWT.encode(@payload, secret)
crypto_segment = jwt.split(".").last

signature = JWT.base64url_decode(crypto_segment)
signature.should_not_receive('==')
JWT.should_receive(:base64url_decode).with(crypto_segment).once.and_return(signature)
JWT.should_receive(:base64url_decode).any_number_of_times.and_call_original

JWT.decode(jwt, secret)
end

describe "secure comparison" do
it "returns true if strings are equal" do
expect(JWT.secure_compare("Foo", "Foo")).to be_true
end

it "returns false if either input is nil or empty" do
[nil, ""].each do |bad|
expect(JWT.secure_compare(bad, "Foo")).to be_false
expect(JWT.secure_compare("Foo", bad)).to be_false
end
end

it "retuns falise of the strings are different" do
expect(JWT.secure_compare("Foo", "Bar")).to be_false
end
end

it "raise exception on invalid signature" do
pubkey = OpenSSL::PKey::RSA.new(<<-PUBKEY)
-----BEGIN PUBLIC KEY-----
Expand Down