diff --git a/lib/jwt.rb b/lib/jwt.rb index e8c1a30a..32fb3fde 100644 --- a/lib/jwt.rb +++ b/lib/jwt.rb @@ -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 @@ -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 diff --git a/spec/jwt_spec.rb b/spec/jwt_spec.rb index 54774b9d..8dd86146 100644 --- a/spec/jwt_spec.rb +++ b/spec/jwt_spec.rb @@ -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-----