jwt 0.1.13 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/Manifest +1 -0
  2. data/Rakefile +1 -2
  3. data/jwt.gemspec +4 -7
  4. data/lib/jwt.rb +66 -36
  5. data/lib/jwt/json.rb +30 -0
  6. data/spec/jwt_spec.rb +46 -26
  7. metadata +4 -18
data/Manifest CHANGED
@@ -1,5 +1,6 @@
1
1
  Rakefile
2
2
  lib/jwt.rb
3
+ lib/jwt/json.rb
3
4
  spec/helper.rb
4
5
  spec/jwt_spec.rb
5
6
  Manifest
data/Rakefile CHANGED
@@ -2,13 +2,12 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'echoe'
4
4
 
5
- Echoe.new('jwt', '0.1.13') do |p|
5
+ Echoe.new('jwt', '1.0.0') do |p|
6
6
  p.description = "JSON Web Token implementation in Ruby"
7
7
  p.url = "http://github.com/progrium/ruby-jwt"
8
8
  p.author = "Jeff Lindsay"
9
9
  p.email = "progrium@gmail.com"
10
10
  p.ignore_pattern = ["tmp/*"]
11
- p.runtime_dependencies = ["multi_json >=1.5"]
12
11
  p.development_dependencies = ["echoe >=4.6.3"]
13
12
  p.licenses = "MIT"
14
13
  end
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "jwt"
5
- s.version = "0.1.13"
5
+ s.version = "1.0.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Jeff Lindsay"]
9
- s.date = "2014-05-09"
9
+ s.date = "2014-05-07"
10
10
  s.description = "JSON Web Token implementation in Ruby"
11
11
  s.email = "progrium@gmail.com"
12
- s.extra_rdoc_files = ["lib/jwt.rb"]
13
- s.files = ["Rakefile", "lib/jwt.rb", "spec/helper.rb", "spec/jwt_spec.rb", "Manifest", "jwt.gemspec"]
12
+ s.extra_rdoc_files = ["lib/jwt.rb", "lib/jwt/json.rb"]
13
+ s.files = ["Rakefile", "lib/jwt.rb", "lib/jwt/json.rb", "spec/helper.rb", "spec/jwt_spec.rb", "Manifest", "jwt.gemspec"]
14
14
  s.homepage = "http://github.com/progrium/ruby-jwt"
15
15
  s.licenses = ["MIT"]
16
16
  s.rdoc_options = ["--line-numbers", "--title", "Jwt", "--main", "README.md"]
@@ -23,14 +23,11 @@ Gem::Specification.new do |s|
23
23
  s.specification_version = 3
24
24
 
25
25
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
- s.add_runtime_dependency(%q<multi_json>, [">= 1.5"])
27
26
  s.add_development_dependency(%q<echoe>, [">= 4.6.3"])
28
27
  else
29
- s.add_dependency(%q<multi_json>, [">= 1.5"])
30
28
  s.add_dependency(%q<echoe>, [">= 4.6.3"])
31
29
  end
32
30
  else
33
- s.add_dependency(%q<multi_json>, [">= 1.5"])
34
31
  s.add_dependency(%q<echoe>, [">= 4.6.3"])
35
32
  end
36
33
  end
data/lib/jwt.rb CHANGED
@@ -6,10 +6,11 @@
6
6
 
7
7
  require "base64"
8
8
  require "openssl"
9
- require "multi_json"
9
+ require "jwt/json"
10
10
 
11
11
  module JWT
12
12
  class DecodeError < StandardError; end
13
+ extend JWT::Json
13
14
 
14
15
  module_function
15
16
 
@@ -44,59 +45,88 @@ module JWT
44
45
  Base64.encode64(str).tr("+/", "-_").gsub(/[\n=]/, "")
45
46
  end
46
47
 
47
- def encode(payload, key, algorithm="HS256", header_fields={})
48
- algorithm ||= "none"
49
- segments = []
48
+ def encoded_header(algorithm="HS256", header_fields={})
50
49
  header = {"typ" => "JWT", "alg" => algorithm}.merge(header_fields)
51
- segments << base64url_encode(MultiJson.encode(header))
52
- segments << base64url_encode(MultiJson.encode(payload))
53
- signing_input = segments.join(".")
50
+ base64url_encode(encode_json(header))
51
+ end
52
+
53
+ def encoded_payload(payload)
54
+ base64url_encode(encode_json(payload))
55
+ end
56
+
57
+ def encoded_signature(signing_input, key, algorithm)
54
58
  if algorithm == "none"
55
- segments << ""
59
+ ""
56
60
  else
57
61
  signature = sign(algorithm, signing_input, key)
58
- segments << base64url_encode(signature)
62
+ base64url_encode(signature)
59
63
  end
64
+ end
65
+
66
+ def encode(payload, key, algorithm="HS256", header_fields={})
67
+ algorithm ||= "none"
68
+ segments = []
69
+ segments << encoded_header(algorithm, header_fields)
70
+ segments << encoded_payload(payload)
71
+ segments << encoded_signature(segments.join("."), key, algorithm)
60
72
  segments.join(".")
61
73
  end
62
74
 
63
- def decode(jwt, key=nil, verify=true, &keyfinder)
75
+ def raw_segments(jwt, verify=true)
64
76
  segments = jwt.split(".")
65
- raise JWT::DecodeError.new("Not enough or too many segments") unless [2,3].include? segments.length
66
- header_segment, payload_segment, crypto_segment = segments
77
+ required_num_segments = verify ? [3] : [2,3]
78
+ raise JWT::DecodeError.new("Not enough or too many segments") unless required_num_segments.include? segments.length
79
+ segments
80
+ end
81
+
82
+ def decode_header_and_payload(header_segment, payload_segment)
83
+ header = decode_json(base64url_decode(header_segment))
84
+ payload = decode_json(base64url_decode(payload_segment))
85
+ [header, payload]
86
+ end
87
+
88
+ def decoded_segments(jwt, verify=true)
89
+ header_segment, payload_segment, crypto_segment = raw_segments(jwt, verify)
90
+ header, payload = decode_header_and_payload(header_segment, payload_segment)
91
+ signature = base64url_decode(crypto_segment.to_s) if verify
67
92
  signing_input = [header_segment, payload_segment].join(".")
68
- begin
69
- header = MultiJson.decode(base64url_decode(header_segment))
70
- payload = MultiJson.decode(base64url_decode(payload_segment))
71
- signature = base64url_decode(crypto_segment.to_s) if verify
72
- rescue MultiJson::LoadError
73
- raise JWT::DecodeError.new("Invalid segment encoding")
74
- end
93
+ [header, payload, signature, signing_input]
94
+ end
95
+
96
+ def decode(jwt, key=nil, verify=true, &keyfinder)
97
+ raise JWT::DecodeError.new("Nil JSON web token") unless jwt
75
98
 
99
+ header, payload, signature, signing_input = decoded_segments(jwt, verify)
76
100
  raise JWT::DecodeError.new("Not enough or too many segments") unless header && payload
77
101
 
78
102
  if verify
79
- algo = header["alg"]
103
+ algo, key = signature_algorithm_and_key(header, key, &keyfinder)
104
+ verify_signature(algo, key, signing_input, signature)
105
+ end
106
+ return payload,header
107
+ end
80
108
 
81
- if keyfinder
82
- key = keyfinder.call(header)
83
- end
109
+ def signature_algorithm_and_key(header, key, &keyfinder)
110
+ if keyfinder
111
+ key = keyfinder.call(header)
112
+ end
113
+ [header['alg'], key]
114
+ end
84
115
 
85
- begin
86
- if ["HS256", "HS384", "HS512"].include?(algo)
87
- raise JWT::DecodeError.new("Signature verification failed") unless secure_compare(signature, sign_hmac(algo, signing_input, key))
88
- elsif ["RS256", "RS384", "RS512"].include?(algo)
89
- raise JWT::DecodeError.new("Signature verification failed") unless verify_rsa(algo, key, signing_input, signature)
90
- else
91
- raise JWT::DecodeError.new("Algorithm not supported")
92
- end
93
- rescue OpenSSL::PKey::PKeyError
94
- raise JWT::DecodeError.new("Signature verification failed")
95
- ensure
96
- OpenSSL.errors.clear
116
+ def verify_signature(algo, key, signing_input, signature)
117
+ begin
118
+ if ["HS256", "HS384", "HS512"].include?(algo)
119
+ raise JWT::DecodeError.new("Signature verification failed") unless secure_compare(signature, sign_hmac(algo, signing_input, key))
120
+ elsif ["RS256", "RS384", "RS512"].include?(algo)
121
+ raise JWT::DecodeError.new("Signature verification failed") unless verify_rsa(algo, key, signing_input, signature)
122
+ else
123
+ raise JWT::DecodeError.new("Algorithm not supported")
97
124
  end
125
+ rescue OpenSSL::PKey::PKeyError
126
+ raise JWT::DecodeError.new("Signature verification failed")
127
+ ensure
128
+ OpenSSL.errors.clear
98
129
  end
99
- payload
100
130
  end
101
131
 
102
132
  # From devise
@@ -0,0 +1,30 @@
1
+ module JWT
2
+ module Json
3
+ if RUBY_VERSION >= "1.9" && !defined?(MultiJson)
4
+ require 'json'
5
+
6
+ def decode_json(encoded)
7
+ JSON.parse(encoded)
8
+ rescue JSON::ParserError
9
+ raise JWT::DecodeError.new("Invalid segment encoding")
10
+ end
11
+
12
+ def encode_json(raw)
13
+ JSON.generate(raw)
14
+ end
15
+
16
+ else
17
+ require "multi_json"
18
+
19
+ def decode_json(encoded)
20
+ MultiJson.decode(encoded)
21
+ rescue MultiJson::LoadError
22
+ raise JWT::DecodeError.new("Invalid segment encoding")
23
+ end
24
+
25
+ def encode_json(raw)
26
+ MultiJson.encode(raw)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -9,24 +9,24 @@ describe JWT do
9
9
  secret = "secret"
10
10
  jwt = JWT.encode(@payload, secret)
11
11
  decoded_payload = JWT.decode(jwt, secret)
12
- decoded_payload.should == @payload
12
+ expect(decoded_payload).to include(@payload)
13
13
  end
14
14
 
15
15
  it "encodes and decodes JWTs for RSA signatures" do
16
16
  private_key = OpenSSL::PKey::RSA.generate(512)
17
17
  jwt = JWT.encode(@payload, private_key, "RS256")
18
18
  decoded_payload = JWT.decode(jwt, private_key.public_key)
19
- decoded_payload.should == @payload
19
+ expect(decoded_payload).to include(@payload)
20
20
  end
21
21
 
22
22
  it "encodes and decodes JWTs with custom header fields" do
23
23
  private_key = OpenSSL::PKey::RSA.generate(512)
24
24
  jwt = JWT.encode(@payload, private_key, "RS256", {"kid" => 'default'})
25
25
  decoded_payload = JWT.decode(jwt) do |header|
26
- header["kid"].should == 'default'
26
+ expect(header["kid"]).to eq('default')
27
27
  private_key.public_key
28
28
  end
29
- decoded_payload.should == @payload
29
+ expect(decoded_payload).to include(@payload)
30
30
  end
31
31
 
32
32
  it "decodes valid JWTs" do
@@ -34,43 +34,46 @@ describe JWT do
34
34
  example_secret = 'secret'
35
35
  example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
36
36
  decoded_payload = JWT.decode(example_jwt, example_secret)
37
- decoded_payload.should == example_payload
37
+ expect(decoded_payload).to include(example_payload)
38
38
  end
39
39
 
40
40
  it "raises exception when the token is invalid" do
41
41
  example_secret = 'secret'
42
42
  # Same as above exmaple with some random bytes replaced
43
43
  example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHiMomlwIjogIkJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
44
- lambda { JWT.decode(example_jwt, example_secret) }.should raise_error(JWT::DecodeError)
44
+ expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
45
45
  end
46
46
 
47
47
  it "raises exception with wrong hmac key" do
48
48
  right_secret = 'foo'
49
49
  bad_secret = 'bar'
50
50
  jwt_message = JWT.encode(@payload, right_secret, "HS256")
51
- lambda { JWT.decode(jwt_message, bad_secret) }.should raise_error(JWT::DecodeError)
51
+ expect { JWT.decode(jwt_message, bad_secret) }.to raise_error(JWT::DecodeError)
52
52
  end
53
53
 
54
54
  it "raises exception with wrong rsa key" do
55
55
  right_private_key = OpenSSL::PKey::RSA.generate(512)
56
56
  bad_private_key = OpenSSL::PKey::RSA.generate(512)
57
57
  jwt = JWT.encode(@payload, right_private_key, "RS256")
58
- lambda { JWT.decode(jwt, bad_private_key.public_key) }.should raise_error(JWT::DecodeError)
58
+ expect { JWT.decode(jwt, bad_private_key.public_key) }.to raise_error(JWT::DecodeError)
59
59
  end
60
60
 
61
61
  it "raises exception with invalid signature" do
62
- example_payload = {"hello" => "world"}
63
62
  example_secret = 'secret'
64
63
  example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.'
65
- lambda { JWT.decode(example_jwt, example_secret) }.should raise_error(JWT::DecodeError)
64
+ expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
66
65
  end
67
66
 
68
67
  it "raises exception with nonexistent header" do
69
- lambda { JWT.decode("..stuff") }.should raise_error(JWT::DecodeError)
68
+ expect { JWT.decode("..stuff") }.to raise_error(JWT::DecodeError)
70
69
  end
71
70
 
72
71
  it "raises exception with nonexistent payload" do
73
- lambda { JWT.decode("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9..stuff") }.should raise_error(JWT::DecodeError)
72
+ expect { JWT.decode("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9..stuff") }.to raise_error(JWT::DecodeError)
73
+ end
74
+
75
+ it "raises exception with nil jwt" do
76
+ expect { JWT.decode(nil) }.to raise_error(JWT::DecodeError)
74
77
  end
75
78
 
76
79
  it "allows decoding without key" do
@@ -78,7 +81,7 @@ describe JWT do
78
81
  bad_secret = 'bar'
79
82
  jwt = JWT.encode(@payload, right_secret)
80
83
  decoded_payload = JWT.decode(jwt, bad_secret, false)
81
- decoded_payload.should == @payload
84
+ expect(decoded_payload).to include(@payload)
82
85
  end
83
86
 
84
87
  it "checks the key when verify is truthy" do
@@ -86,18 +89,24 @@ describe JWT do
86
89
  bad_secret = 'bar'
87
90
  jwt = JWT.encode(@payload, right_secret)
88
91
  verify = "yes" =~ /^y/i
89
- lambda { JWT.decode(jwt, bad_secret, verify) }.should raise_error(JWT::DecodeError)
92
+ expect { JWT.decode(jwt, bad_secret, verify) }.to raise_error(JWT::DecodeError)
90
93
  end
91
94
 
92
95
  it "raises exception on unsupported crypto algorithm" do
93
- lambda { JWT.encode(@payload, "secret", 'HS1024') }.should raise_error(NotImplementedError)
96
+ expect { JWT.encode(@payload, "secret", 'HS1024') }.to raise_error(NotImplementedError)
94
97
  end
95
98
 
96
99
  it "encodes and decodes plaintext JWTs" do
97
100
  jwt = JWT.encode(@payload, nil, nil)
98
- jwt.split('.').length.should == 2
101
+ expect(jwt.split('.').length).to eq(2)
99
102
  decoded_payload = JWT.decode(jwt, nil, nil)
100
- decoded_payload.should == @payload
103
+ expect(decoded_payload).to include(@payload)
104
+ end
105
+
106
+ it "requires a signature segment when verify is truthy" do
107
+ jwt = JWT.encode(@payload, nil, nil)
108
+ expect(jwt.split('.').length).to eq(2)
109
+ expect { JWT.decode(jwt, nil, true) }.to raise_error(JWT::DecodeError)
101
110
  end
102
111
 
103
112
  it "does not use == to compare digests" do
@@ -106,9 +115,9 @@ describe JWT do
106
115
  crypto_segment = jwt.split(".").last
107
116
 
108
117
  signature = JWT.base64url_decode(crypto_segment)
109
- signature.should_not_receive('==')
110
- JWT.should_receive(:base64url_decode).with(crypto_segment).once.and_return(signature)
111
- JWT.should_receive(:base64url_decode).at_least(:once).and_call_original
118
+ expect(signature).not_to receive('==')
119
+ expect(JWT).to receive(:base64url_decode).with(crypto_segment).once.and_return(signature)
120
+ expect(JWT).to receive(:base64url_decode).at_least(:once).and_call_original
112
121
 
113
122
  JWT.decode(jwt, secret)
114
123
  end
@@ -125,14 +134,14 @@ describe JWT do
125
134
  end
126
135
  end
127
136
 
128
- it "retuns falise of the strings are different" do
129
- expect(JWT.secure_compare("Foo", "Bar")).to be_false
137
+ it "retuns false if the strings are different" do
138
+ expect(JWT.secure_compare("Foo", "Bar")).to be_false
130
139
  end
131
140
  end
132
141
 
133
142
  # no method should leave OpenSSL.errors populated
134
143
  after do
135
- OpenSSL.errors.should be_empty
144
+ expect(OpenSSL.errors).to be_empty
136
145
  end
137
146
 
138
147
  it "raise exception on invalid signature" do
@@ -157,13 +166,24 @@ PUBKEY
157
166
  'wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl' +
158
167
  '6hFycH8wj7Su6JqqyEbIVLxE7q7DkAZGaMPkxbTHs1EhSd5_oaKQ6O4xO3ZnnT4'
159
168
  )
160
- lambda { JWT.decode(jwt, pubkey, true) }.should raise_error(JWT::DecodeError)
169
+ expect { JWT.decode(jwt, pubkey, true) }.to raise_error(JWT::DecodeError)
161
170
  end
162
171
 
163
172
  describe "urlsafe base64 encoding" do
164
173
  it "replaces + and / with - and _" do
165
- Base64.stub(:encode64) { "string+with/non+url-safe/characters_" }
166
- JWT.base64url_encode("foo").should == "string-with_non-url-safe_characters_"
174
+ allow(Base64).to receive(:encode64) { "string+with/non+url-safe/characters_" }
175
+ expect(JWT.base64url_encode("foo")).to eq("string-with_non-url-safe_characters_")
176
+ end
177
+ end
178
+
179
+ describe 'decoded_segments' do
180
+ it "allows access to the decoded header and payload" do
181
+ secret = "secret"
182
+ jwt = JWT.encode(@payload, secret)
183
+ decoded_segments = JWT.decoded_segments(jwt)
184
+ expect(decoded_segments.size).to eq(4)
185
+ expect(decoded_segments[0]).to eq({"typ" => "JWT", "alg" => "HS256"})
186
+ expect(decoded_segments[1]).to eq(@payload)
167
187
  end
168
188
  end
169
189
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-09 00:00:00.000000000 Z
12
+ date: 2014-05-07 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: multi_json
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '1.5'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '1.5'
30
14
  - !ruby/object:Gem::Dependency
31
15
  name: echoe
32
16
  requirement: !ruby/object:Gem::Requirement
@@ -49,9 +33,11 @@ executables: []
49
33
  extensions: []
50
34
  extra_rdoc_files:
51
35
  - lib/jwt.rb
36
+ - lib/jwt/json.rb
52
37
  files:
53
38
  - Rakefile
54
39
  - lib/jwt.rb
40
+ - lib/jwt/json.rb
55
41
  - spec/helper.rb
56
42
  - spec/jwt_spec.rb
57
43
  - Manifest