Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue with signature verification in JRuby #10

Merged
merged 7 commits into from
Jul 20, 2012
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
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
language: ruby
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- jruby
- ree
script: "bundle exec rake test"
14 changes: 10 additions & 4 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
source "https://rubygems.org"
source :rubygems

gem "multi_json", "~> 1.0"
gem 'json', '>= 1.2.4'
gem 'multi_json', '~> 1.0'
gem 'jruby-openssl', :platforms => :jruby

group :development do
gem "echoe", ">=4.6.3"
gem "rspec"
gem 'echoe', '>= 4.6.3'
end

group :test, :development do
gem 'rake'
gem 'rspec'
end
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ The tests are written with rspec. Given you have rake and rspec, you can run tes
* Ilya Zhitomirskiy <[email protected]>
* Daniel Grippi <[email protected]>
* Jeff Lindsay <[email protected]>
* Bob Aman <[email protected]>

## License

Expand Down
6 changes: 3 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ require 'rubygems'
require 'rake'
require 'echoe'

Echoe.new('jwt', '0.1.4') do |p|
Echoe.new('jwt', '0.1.5') do |p|
p.description = "JSON Web Token implementation in Ruby"
p.url = "http://github.com/progrium/ruby-jwt"
p.author = "Jeff Lindsay"
p.email = "[email protected]"
p.ignore_pattern = ["tmp/*"]
p.runtime_dependencies = ["multi_json ~> 1.0"]
p.runtime_dependencies = ["multi_json ~>1.0"]
p.development_dependencies = ["echoe >=4.6.3"]
end

task :test do
sh "rspec spec/jwt.rb"
sh "rspec spec/jwt_spec.rb"
end
32 changes: 18 additions & 14 deletions lib/jwt.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
#
# JSON Web Token implementation
#
#
# Should be up to date with the latest spec:
# http://self-issued.info/docs/draft-jones-json-web-token-06.html

Expand All @@ -10,7 +10,7 @@

module JWT
class DecodeError < Exception; end

def self.sign(algorithm, msg, key)
if ["HS256", "HS384", "HS512"].include?(algorithm)
sign_hmac(algorithm, msg, key)
Expand All @@ -32,16 +32,16 @@ def self.verify_rsa(algorithm, public_key, signing_input, signature)
def self.sign_hmac(algorithm, msg, key)
OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
end

def self.base64url_decode(str)
str += '=' * (4 - str.length.modulo(4))
Base64.decode64(str.gsub("-", "+").gsub("_", "/"))
end

def self.base64url_encode(str)
Base64.encode64(str).gsub("+", "-").gsub("/", "_").gsub("\n", "").gsub('=', '')
end
end

def self.encode(payload, key, algorithm='HS256', header_fields={})
algorithm ||= "none"
segments = []
Expand All @@ -57,7 +57,7 @@ def self.encode(payload, key, algorithm='HS256', header_fields={})
end
segments.join('.')
end

def self.decode(jwt, key=nil, verify=true, &keyfinder)
segments = jwt.split('.')
raise JWT::DecodeError.new("Not enough or too many segments") unless [2,3].include? segments.length
Expand All @@ -77,12 +77,16 @@ def self.decode(jwt, key=nil, verify=true, &keyfinder)
key = keyfinder.call(header)
end

if ["HS256", "HS384", "HS512"].include?(algo)
raise JWT::DecodeError.new("Signature verification failed") unless 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
raise JWT::DecodeError.new("Algorithm not supported")
begin
if ["HS256", "HS384", "HS512"].include?(algo)
raise JWT::DecodeError.new("Signature verification failed") unless 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
raise JWT::DecodeError.new("Algorithm not supported")
end
rescue OpenSSL::PKey::PKeyError
raise JWT::DecodeError.new("Signature verification failed")
end
end
payload
Expand Down
39 changes: 32 additions & 7 deletions spec/jwt_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@
end

it "encodes and decodes JWTs for RSA signatures" do
private_key = OpenSSL::PKey::RSA.generate(512)
private_key = OpenSSL::PKey::RSA.generate(512)
jwt = JWT.encode(@payload, private_key, "RS256")
decoded_payload = JWT.decode(jwt, private_key.public_key)
decoded_payload.should == @payload
end

it "encodes and decodes JWTs with custom header fields" do
private_key = OpenSSL::PKey::RSA.generate(512)
private_key = OpenSSL::PKey::RSA.generate(512)
jwt = JWT.encode(@payload, private_key, "RS256", {"kid" => 'default'})
decoded_payload = JWT.decode(jwt) do |header|
header["kid"].should == 'default'
private_key.public_key
end
decoded_payload.should == @payload
end

it "decodes valid JWTs" do
example_payload = {"hello" => "world"}
example_secret = 'secret'
Expand All @@ -50,23 +50,48 @@
jwt = JWT.encode(@payload, right_private_key, "RS256")
lambda { JWT.decode(jwt, bad_private_key.public_key) }.should raise_error(JWT::DecodeError)
end

it "allows decoding without key" do
right_secret = 'foo'
bad_secret = 'bar'
jwt = JWT.encode(@payload, right_secret)
decoded_payload = JWT.decode(jwt, bad_secret, false)
decoded_payload.should == @payload
end

it "raises exception on unsupported crypto algorithm" do
lambda { JWT.encode(@payload, "secret", 'HS1024') }.should raise_error(NotImplementedError)
end

it "encodes and decodes plaintext JWTs" do
jwt = JWT.encode(@payload, nil, nil)
jwt.split('.').length.should == 2
decoded_payload = JWT.decode(jwt, nil, nil)
decoded_payload.should == @payload
end

it "raise exception on invalid signature" do
pubkey = OpenSSL::PKey::RSA.new(<<-PUBKEY)
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCaY7425h964bjaoLeUm
SlZ8sK7VtVk9zHbGmZh2ygGYwfuUf2bmMye2Ofv99yDE/rd4loVIAcu7RVvDRgHq
3/CZTnIrSvHsiJQsHBNa3d+F1ihPfzURzf1M5k7CFReBj2SBXhDXd57oRfBQj12w
CVhhwP6kGTAWuoppbIIIBfNF2lE/Nvm7lVVYQqL9xOrP/AQ4xRbpQlB8Ll9sO9Or
SvbWhCDa/LMOWxHdmrcJi6XoSg1vnOyCoKbyAoauTt/XqdkHbkDdQ6HFbJieu9il
LDZZNliPhfENuKeC2MCGVXTEu8Cqhy1w6e4axavLlXoYf4laJIZ/e7au8SqDbY0B
xwIDAQAB
-----END PUBLIC KEY-----
PUBKEY
jwt = (
'eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY' +
'XVkIjoiMTA2MDM1Nzg5MTY4OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSI' +
'sImNpZCI6IjEwNjAzNTc4OTE2ODguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb' +
'20iLCJpZCI6IjExNjQ1MjgyNDMwOTg1Njc4MjE2MyIsInRva2VuX2hhc2giOiJ' +
'0Z2hEOUo4bjhWME4ydmN3NmVNaWpnIiwiaWF0IjoxMzIwNjcwOTc4LCJleHAiO' +
'jEzMjA2NzQ4Nzh9.D8x_wirkxDElqKdJBcsIws3Ogesk38okz6MN7zqC7nEAA7' +
'wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl' +
'6hFycH8wj7Su6JqqyEbIVLxE7q7DkAZGaMPkxbTHs1EhSd5_oaKQ6O4xO3ZnnT4'
)
lambda { JWT.decode(jwt, pubkey, true) }.should raise_error(JWT::DecodeError)
end
end