jwt 2.0.0.beta1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f874da696e03e2c7f0ffe02942540825eb8a6314
4
- data.tar.gz: d57f6842df5cc35d21bea1a1e511026faeb0aaa4
3
+ metadata.gz: c63d3d103ec14f12ea2b51b95ae8f5407dd7ace9
4
+ data.tar.gz: f1de3f1e8ddfb79aa690a1f6d44492c70d037942
5
5
  SHA512:
6
- metadata.gz: 51acfa10b330d62022d463cd4c0d25eff3ceb642b29e2c625d4aab40cd1a4138ca5597e0e19d540232f3f3b2e76fdcf4b403566f5b54aa7e687ab7d8696186e6
7
- data.tar.gz: b8d9b8f1485f4e0c74d189c1e7ba64d71ce89f4813b134dbaf28263c695403dd102f12ab9dddde0ee7990f6cbcfde5ab5941876919bb1540b4088349129d1baf
6
+ metadata.gz: 3b19fcd5018d17e4277a49abc5787cee37ff3991fc8cbcc97dbb00f50c3878fc08062d97e18e07cdaf5f8f2f09cfbf5a8937e11859ae9b44d90a8d5a20caa021
7
+ data.tar.gz: f9df1adefd0f0d4525567372c8283e72639b2ee67320a893ef03d470bbbd6afd09a65725d3eb3153f8c68e7d40f693c7da3a5ed138ab766a23aa6ebbfe5c62f6
@@ -0,0 +1,17 @@
1
+ styleguide: plataformatec/linters
2
+ engines:
3
+ reek:
4
+ enabled: true
5
+ fixme:
6
+ enabled: true
7
+ rubocop:
8
+ enabled: true
9
+ duplication:
10
+ config:
11
+ languages:
12
+ - ruby
13
+ enabled: true
14
+ remark-lint:
15
+ enabled: true
16
+ exclude_paths:
17
+ - spec
@@ -0,0 +1,40 @@
1
+ ---
2
+ TooManyStatements:
3
+ max_statements: 10
4
+ UncommunicativeMethodName:
5
+ reject:
6
+ - !ruby/regexp /^[a-z]$/
7
+ - !ruby/regexp /[0-9]$/
8
+ UncommunicativeParameterName:
9
+ reject:
10
+ - !ruby/regexp /^.$/
11
+ - !ruby/regexp /[0-9]$/
12
+ - !ruby/regexp /^_/
13
+ UncommunicativeVariableName:
14
+ reject:
15
+ - !ruby/regexp /^.$/
16
+ - !ruby/regexp /[0-9]$/
17
+ UtilityFunction:
18
+ enabled: false
19
+ LongParameterList:
20
+ enabled: false
21
+ DuplicateMethodCall:
22
+ max_calls: 2
23
+ IrresponsibleModule:
24
+ enabled: false
25
+ NestedIterators:
26
+ max_allowed_nesting: 2
27
+ PrimaDonnaMethod:
28
+ enabled: false
29
+ UnusedParameters:
30
+ enabled: false
31
+ FeatureEnvy:
32
+ enabled: false
33
+ ControlParameter:
34
+ enabled: false
35
+ UnusedPrivateMethod:
36
+ enabled: false
37
+ InstanceVariableAssumption:
38
+ exclude:
39
+ - !ruby/regexp /Controller$/
40
+ - !ruby/regexp /Mailer$/s
@@ -1,5 +1,98 @@
1
1
  AllCops:
2
- Excludes:
3
- - spec/**/*
2
+ Exclude:
3
+ - 'bin/**/*'
4
+ - 'db/**/*'
5
+ - 'config/**/*'
6
+ - 'script/**/*'
7
+
8
+ Rails:
9
+ Enabled: true
10
+
11
+ Style/AlignParameters:
12
+ EnforcedStyle: with_fixed_indentation
13
+
14
+ Style/CaseIndentation:
15
+ EnforcedStyle: end
16
+
17
+ Style/AsciiComments:
18
+ Enabled: false
19
+
20
+ Style/IndentHash:
21
+ Enabled: false
22
+
23
+ Style/CollectionMethods:
24
+ Enabled: true
25
+ PreferredMethods:
26
+ inject: 'inject'
27
+
28
+ Style/Documentation:
29
+ Enabled: false
30
+
31
+ Style/BlockDelimiters:
32
+ Exclude:
33
+ - spec/**/*_spec.rb
34
+
35
+ Style/BracesAroundHashParameters:
36
+ Exclude:
37
+ - spec/**/*_spec.rb
38
+
39
+ Style/GuardClause:
40
+ Enabled: false
41
+
42
+ Style/IfUnlessModifier:
43
+ Enabled: false
44
+
45
+ Style/SpaceInsideHashLiteralBraces:
46
+ Enabled: false
47
+
48
+ Style/Lambda:
49
+ Enabled: false
50
+
51
+ Style/RaiseArgs:
52
+ Enabled: false
53
+
54
+ Style/SignalException:
55
+ Enabled: false
56
+
57
+ Metrics/AbcSize:
58
+ Max: 20
59
+
60
+ Metrics/ClassLength:
61
+ Max: 100
62
+
63
+ Metrics/ModuleLength:
64
+ Max: 100
65
+
4
66
  Metrics/LineLength:
5
67
  Enabled: false
68
+
69
+ Metrics/MethodLength:
70
+ Max: 15
71
+
72
+ Style/SingleLineBlockParams:
73
+ Enabled: false
74
+
75
+ Lint/EndAlignment:
76
+ EnforcedStyleAlignWith: variable
77
+
78
+ Style/FormatString:
79
+ Enabled: false
80
+
81
+ Style/MultilineMethodCallIndentation:
82
+ EnforcedStyle: indented
83
+
84
+ Style/MultilineOperationIndentation:
85
+ EnforcedStyle: indented
86
+
87
+ Style/WordArray:
88
+ Enabled: false
89
+
90
+ Style/RedundantSelf:
91
+ Enabled: false
92
+
93
+ Style/AlignHash:
94
+ Enabled: true
95
+ EnforcedLastArgumentHashStyle: always_ignore
96
+
97
+ Style/TrivialAccessors:
98
+ AllowPredicates: true
@@ -1,5 +1,6 @@
1
1
  sudo: required
2
2
  cache: bundler
3
+ dist: trusty
3
4
  language: ruby
4
5
  rvm:
5
6
  - 2.2.0
@@ -1,7 +1,39 @@
1
1
  # Change Log
2
2
 
3
- ## [v2.0.0](https://github.com/jwt/ruby-jwt/tree/v2.0.0) (2017-02-27)
4
- [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.6...v2.0.0)
3
+ ## [v2.0.0](https://github.com/jwt/ruby-jwt/tree/v2.0.0) (2017-09-03)
4
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.0.0.beta1...v2.0.0)
5
+
6
+ **Fixed bugs:**
7
+
8
+ - Support versions outside 2.1 [\#209](https://github.com/jwt/ruby-jwt/issues/209)
9
+ - Verifying expiration without leeway throws exception [\#206](https://github.com/jwt/ruby-jwt/issues/206)
10
+ - Ruby interpreter warning [\#200](https://github.com/jwt/ruby-jwt/issues/200)
11
+ - TypeError: no implicit conversion of String into Integer [\#188](https://github.com/jwt/ruby-jwt/issues/188)
12
+ - Fix JWT.encode\(nil\) [\#203](https://github.com/jwt/ruby-jwt/pull/203) ([tmm1](https://github.com/tmm1))
13
+
14
+ **Closed issues:**
15
+
16
+ - Possibility to disable claim verifications [\#222](https://github.com/jwt/ruby-jwt/issues/222)
17
+ - Proper way to verify Firebase id tokens [\#216](https://github.com/jwt/ruby-jwt/issues/216)
18
+
19
+ **Merged pull requests:**
20
+
21
+ - Skip 'exp' claim validation for array payloads [\#224](https://github.com/jwt/ruby-jwt/pull/224) ([excpt](https://github.com/excpt))
22
+ - Use a default leeway of 0 [\#223](https://github.com/jwt/ruby-jwt/pull/223) ([travisofthenorth](https://github.com/travisofthenorth))
23
+ - Fix reported codesmells [\#221](https://github.com/jwt/ruby-jwt/pull/221) ([excpt](https://github.com/excpt))
24
+ - Add fancy gem version badge [\#220](https://github.com/jwt/ruby-jwt/pull/220) ([excpt](https://github.com/excpt))
25
+ - Add missing dist option to .travis.yml [\#219](https://github.com/jwt/ruby-jwt/pull/219) ([excpt](https://github.com/excpt))
26
+ - Fix ruby version requirements in gemspec file [\#218](https://github.com/jwt/ruby-jwt/pull/218) ([excpt](https://github.com/excpt))
27
+ - Fix a little typo in the readme [\#214](https://github.com/jwt/ruby-jwt/pull/214) ([RyanBrushett](https://github.com/RyanBrushett))
28
+ - Update README.md [\#212](https://github.com/jwt/ruby-jwt/pull/212) ([zuzannast](https://github.com/zuzannast))
29
+ - Fix typo in HS512256 algorithm description [\#211](https://github.com/jwt/ruby-jwt/pull/211) ([ojab](https://github.com/ojab))
30
+ - Allow configuration of multiple acceptable issuers [\#210](https://github.com/jwt/ruby-jwt/pull/210) ([ojab](https://github.com/ojab))
31
+ - Enforce `exp` to be an `Integer` [\#205](https://github.com/jwt/ruby-jwt/pull/205) ([lucasmazza](https://github.com/lucasmazza))
32
+ - ruby 1.9.3 support message upd [\#204](https://github.com/jwt/ruby-jwt/pull/204) ([maokomioko](https://github.com/maokomioko))
33
+ - Guard against partially loaded RbNaCl when failing to load libsodium [\#202](https://github.com/jwt/ruby-jwt/pull/202) ([Dorian](https://github.com/Dorian))
34
+
35
+ ## [v2.0.0.beta1](https://github.com/jwt/ruby-jwt/tree/v2.0.0.beta1) (2017-02-27)
36
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.6...v2.0.0.beta1)
5
37
 
6
38
  **Implemented enhancements:**
7
39
 
@@ -36,6 +68,8 @@
36
68
 
37
69
  **Merged pull requests:**
38
70
 
71
+ - Version bump 2.0.0.beta1 [\#199](https://github.com/jwt/ruby-jwt/pull/199) ([excpt](https://github.com/excpt))
72
+ - Update CHANGELOG.md and minor fixes [\#198](https://github.com/jwt/ruby-jwt/pull/198) ([excpt](https://github.com/excpt))
39
73
  - Add Codacy coverage reporter [\#194](https://github.com/jwt/ruby-jwt/pull/194) ([excpt](https://github.com/excpt))
40
74
  - Add minimum required ruby version to gemspec [\#193](https://github.com/jwt/ruby-jwt/pull/193) ([excpt](https://github.com/excpt))
41
75
  - Code smell fixes [\#192](https://github.com/jwt/ruby-jwt/pull/192) ([excpt](https://github.com/excpt))
@@ -301,7 +335,6 @@
301
335
 
302
336
  **Closed issues:**
303
337
 
304
- - yanking of version 0.1.12 causes issues [\#39](https://github.com/jwt/ruby-jwt/issues/39)
305
338
  - Semantic versioning [\#37](https://github.com/jwt/ruby-jwt/issues/37)
306
339
  - Update gem to get latest changes [\#36](https://github.com/jwt/ruby-jwt/issues/36)
307
340
 
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  source 'https://rubygems.org'
3
2
 
4
3
  gemspec
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # JWT
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/jwt.svg)](https://badge.fury.io/rb/jwt)
3
4
  [![Build Status](https://travis-ci.org/jwt/ruby-jwt.svg)](https://travis-ci.org/jwt/ruby-jwt)
4
5
  [![Code Climate](https://codeclimate.com/github/jwt/ruby-jwt/badges/gpa.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
5
6
  [![Test Coverage](https://codeclimate.com/github/jwt/ruby-jwt/badges/coverage.svg)](https://codeclimate.com/github/jwt/ruby-jwt/coverage)
@@ -11,7 +12,7 @@ If you have further questions related to development or usage, join us: [ruby-jw
11
12
 
12
13
  ## Announcements
13
14
 
14
- * Ruby 1.9.3 support will be dropped by December 31st, 2016.
15
+ * Ruby 1.9.3 support was dropped at December 31st, 2016.
15
16
  * Version 1.5.3 yanked. See: [#132](https://github.com/jwt/ruby-jwt/issues/132) and [#133](https://github.com/jwt/ruby-jwt/issues/133)
16
17
 
17
18
  ## Installing
@@ -63,7 +64,7 @@ puts decoded_token
63
64
  **HMAC** (default: HS256)
64
65
 
65
66
  * HS256 - HMAC using SHA-256 hash algorithm (default)
66
- * HS512256 - HMAC using SHA-512/256 hash algorithm (only available with RbNaCl; see note below)
67
+ * HS512256 - HMAC using SHA-512-256 hash algorithm (only available with RbNaCl; see note below)
67
68
  * HS384 - HMAC using SHA-384 hash algorithm
68
69
  * HS512 - HMAC using SHA-512 hash algorithm
69
70
 
@@ -85,7 +86,11 @@ decoded_token = JWT.decode token, hmac_secret, true, { :algorithm => 'HS256' }
85
86
  puts decoded_token
86
87
  ```
87
88
 
88
- Note: If [RbNaCl](https://github.com/cryptosphere/rbnacl) is loadable, ruby-jwt will use it for HMAC-SHA256, HMAC-SHA512/256, and HMAC-SHA512. RbNaCl enforces a maximum key size of 32 bytes for these algorithms.
89
+ Note: If [RbNaCl](https://github.com/cryptosphere/rbnacl) is loadable, ruby-jwt will use it for HMAC-SHA256, HMAC-SHA512-256, and HMAC-SHA512. RbNaCl enforces a maximum key size of 32 bytes for these algorithms.
90
+
91
+ [RbNaCl](https://github.com/cryptosphere/rbnacl) requires
92
+ [libsodium](https://github.com/jedisct1/libsodium), it can be installed
93
+ on MacOS with `brew install libsodium`.
89
94
 
90
95
  **RSA**
91
96
 
@@ -273,6 +278,8 @@ From [Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim](https://tools.ietf.org/h
273
278
 
274
279
  > The `iss` (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The `iss` value is a case-sensitive string containing a ***StringOrURI*** value. Use of this claim is OPTIONAL.
275
280
 
281
+ You can pass multiple allowed issuers as an Array, verification will pass if one of them matches the `iss` value in the payload.
282
+
276
283
  ```ruby
277
284
  iss = 'My Awesome Company Inc. or https://my.awesome.website/'
278
285
  iss_payload = { :data => 'data', :iss => iss }
data/lib/jwt.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'base64'
3
4
  require 'jwt/decode'
4
5
  require 'jwt/default_options'
@@ -16,13 +17,6 @@ module JWT
16
17
 
17
18
  module_function
18
19
 
19
- def decoded_segments(jwt, verify = true)
20
- raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
21
-
22
- decoder = Decode.new jwt, verify
23
- decoder.decode_segments
24
- end
25
-
26
20
  def encode(payload, key, algorithm = 'HS256', header_fields = {})
27
21
  encoder = Encode.new payload, key, algorithm, header_fields
28
22
  encoder.segments
@@ -37,7 +31,7 @@ module JWT
37
31
  header, payload, signature, signing_input = decoder.decode_segments
38
32
  decode_verify_signature(key, header, payload, signature, signing_input, merged_options, &keyfinder) if verify
39
33
 
40
- Verify.verify_claims(payload, merged_options)
34
+ Verify.verify_claims(payload, merged_options) if verify
41
35
 
42
36
  raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
43
37
 
@@ -56,10 +50,10 @@ module JWT
56
50
  def signature_algorithm_and_key(header, payload, key, &keyfinder)
57
51
  if keyfinder
58
52
  key = if keyfinder.arity == 2
59
- yield(header, payload)
60
- else
61
- yield(header)
62
- end
53
+ yield(header, payload)
54
+ else
55
+ yield(header)
56
+ end
63
57
  raise JWT::DecodeError, 'No verification key available' unless key
64
58
  end
65
59
  [header['alg'], key]
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'json'
3
4
 
4
5
  # JWT::Decode module
@@ -15,10 +16,13 @@ module JWT
15
16
  def initialize(jwt, verify)
16
17
  @jwt = jwt
17
18
  @verify = verify
19
+ @header = ''
20
+ @payload = ''
21
+ @signature = ''
18
22
  end
19
23
 
20
24
  def decode_segments
21
- header_segment, payload_segment, crypto_segment = raw_segments(@jwt, @verify)
25
+ header_segment, payload_segment, crypto_segment = raw_segments
22
26
  @header, @payload = decode_header_and_payload(header_segment, payload_segment)
23
27
  @signature = Decode.base64url_decode(crypto_segment.to_s) if @verify
24
28
  signing_input = [header_segment, payload_segment].join('.')
@@ -27,9 +31,9 @@ module JWT
27
31
 
28
32
  private
29
33
 
30
- def raw_segments(jwt, verify)
31
- segments = jwt.split('.')
32
- required_num_segments = verify ? [3] : [2, 3]
34
+ def raw_segments
35
+ segments = @jwt.split('.')
36
+ required_num_segments = @verify ? [3] : [2, 3]
33
37
  raise(JWT::DecodeError, 'Not enough or too many segments') unless required_num_segments.include? segments.length
34
38
  segments
35
39
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'json'
3
4
 
4
5
  # JWT::Encode module
@@ -21,31 +22,30 @@ module JWT
21
22
 
22
23
  private
23
24
 
24
- def encoded_header(algorithm, header_fields)
25
- header = { 'alg' => algorithm }.merge(header_fields)
25
+ def encoded_header
26
+ header = { 'alg' => @algorithm }.merge(@header_fields)
26
27
  Encode.base64url_encode(JSON.generate(header))
27
28
  end
28
29
 
29
- def encoded_payload(payload)
30
- raise InvalidPayload, 'exp claim must be an integer' if payload['exp'] && payload['exp'].is_a?(Time)
31
- Encode.base64url_encode(JSON.generate(payload))
30
+ def encoded_payload
31
+ raise InvalidPayload, 'exp claim must be an integer' if @payload && !@payload.is_a?(Array) && @payload.key?('exp') && !@payload['exp'].is_a?(Integer)
32
+ Encode.base64url_encode(JSON.generate(@payload))
32
33
  end
33
34
 
34
- def encoded_signature(signing_input, key, algorithm)
35
- if algorithm == 'none'
35
+ def encoded_signature(signing_input)
36
+ if @algorithm == 'none'
36
37
  ''
37
38
  else
38
- signature = JWT::Signature.sign(algorithm, signing_input, key)
39
+ signature = JWT::Signature.sign(@algorithm, signing_input, @key)
39
40
  Encode.base64url_encode(signature)
40
41
  end
41
42
  end
42
43
 
43
44
  def encode_segments
44
- segments = []
45
- segments << encoded_header(@algorithm, @header_fields)
46
- segments << encoded_payload(@payload)
47
- segments << encoded_signature(segments.join('.'), @key, @algorithm)
48
- segments.join('.')
45
+ header = encoded_header
46
+ payload = encoded_payload
47
+ signature = encoded_signature([header, payload].join('.'))
48
+ [header, payload, signature].join('.')
49
49
  end
50
50
  end
51
51
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module JWT
3
4
  class EncodeError < StandardError; end
4
5
  class DecodeError < StandardError; end
@@ -0,0 +1,52 @@
1
+ module JWT
2
+ # Collection of security methods
3
+ #
4
+ # @see: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/security_utils.rb
5
+ module SecurityUtils
6
+
7
+ module_function
8
+
9
+ def secure_compare(left, right)
10
+ left_bytesize = left.bytesize
11
+
12
+ return false unless left_bytesize == right.bytesize
13
+
14
+ unpacked_left = left.unpack "C#{left_bytesize}"
15
+ result = 0
16
+ right.each_byte { |byte| result |= byte ^ unpacked_left.shift }
17
+ result.zero?
18
+ end
19
+
20
+ def verify_rsa(algorithm, public_key, signing_input, signature)
21
+ public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
22
+ end
23
+
24
+ def asn1_to_raw(signature, public_key)
25
+ byte_size = (public_key.group.degree + 7) / 8
26
+ OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
27
+ end
28
+
29
+ def raw_to_asn1(signature, private_key)
30
+ byte_size = (private_key.group.degree + 7) / 8
31
+ sig_bytes = signature[0..(byte_size - 1)]
32
+ sig_char = signature[byte_size..-1] || ''
33
+ OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
34
+ end
35
+
36
+ def rbnacl_fixup(algorithm, key)
37
+ algorithm = algorithm.sub('HS', 'SHA').to_sym
38
+
39
+ return [] unless defined?(RbNaCl) && RbNaCl::HMAC.constants(false).include?(algorithm)
40
+
41
+ authenticator = RbNaCl::HMAC.const_get(algorithm)
42
+
43
+ # Fall back to OpenSSL for keys larger than 32 bytes.
44
+ return [] if key.bytesize > authenticator.key_bytes
45
+
46
+ [
47
+ authenticator,
48
+ key.bytes.fill(0, key.bytesize...authenticator.key_bytes).pack('C*')
49
+ ]
50
+ end
51
+ end
52
+ end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'jwt/security_utils'
2
4
  require 'openssl'
3
5
  begin
4
6
  require 'rbnacl'
5
- rescue LoadError
7
+ rescue LoadError => e
8
+ abort(e.message) if defined?(RbNaCl)
6
9
  end
7
10
 
8
11
  # JWT::Signature module
@@ -11,9 +14,9 @@ module JWT
11
14
  module Signature
12
15
  extend self
13
16
 
14
- HMAC_ALGORITHMS = %w(HS256 HS512256 HS384 HS512).freeze
15
- RSA_ALGORITHMS = %w(RS256 RS384 RS512).freeze
16
- ECDSA_ALGORITHMS = %w(ES256 ES384 ES512).freeze
17
+ HMAC_ALGORITHMS = %w[HS256 HS512256 HS384 HS512].freeze
18
+ RSA_ALGORITHMS = %w[RS256 RS384 RS512].freeze
19
+ ECDSA_ALGORITHMS = %w[ES256 ES384 ES512].freeze
17
20
 
18
21
  NAMED_CURVES = {
19
22
  'prime256v1' => 'ES256',
@@ -35,14 +38,14 @@ module JWT
35
38
 
36
39
  def verify(algo, key, signing_input, signature)
37
40
  verified = if HMAC_ALGORITHMS.include?(algo)
38
- verify_hmac(algo, key, signing_input, signature)
39
- elsif RSA_ALGORITHMS.include?(algo)
40
- verify_rsa(algo, key, signing_input, signature)
41
- elsif ECDSA_ALGORITHMS.include?(algo)
42
- verify_ecdsa(algo, key, signing_input, signature)
43
- else
44
- raise JWT::VerificationError, 'Algorithm not supported'
45
- end
41
+ verify_hmac(algo, key, signing_input, signature)
42
+ elsif RSA_ALGORITHMS.include?(algo)
43
+ SecurityUtils.verify_rsa(algo, key, signing_input, signature)
44
+ elsif ECDSA_ALGORITHMS.include?(algo)
45
+ verify_ecdsa(algo, key, signing_input, signature)
46
+ else
47
+ raise JWT::VerificationError, 'Algorithm not supported'
48
+ end
46
49
 
47
50
  raise(JWT::VerificationError, 'Signature verification raised') unless verified
48
51
  rescue OpenSSL::PKey::PKeyError
@@ -65,11 +68,11 @@ module JWT
65
68
  end
66
69
 
67
70
  digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
68
- asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
71
+ SecurityUtils.asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
69
72
  end
70
73
 
71
74
  def sign_hmac(algorithm, msg, key)
72
- authenticator, padded_key = rbnacl_fixup(algorithm, key)
75
+ authenticator, padded_key = SecurityUtils.rbnacl_fixup(algorithm, key)
73
76
  if authenticator && padded_key
74
77
  authenticator.auth(padded_key, msg.encode('binary'))
75
78
  else
@@ -77,10 +80,6 @@ module JWT
77
80
  end
78
81
  end
79
82
 
80
- def verify_rsa(algorithm, public_key, signing_input, signature)
81
- public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
82
- end
83
-
84
83
  def verify_ecdsa(algorithm, public_key, signing_input, signature)
85
84
  key_algorithm = NAMED_CURVES[public_key.group.curve_name]
86
85
  if algorithm != key_algorithm
@@ -88,11 +87,11 @@ module JWT
88
87
  end
89
88
 
90
89
  digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
91
- public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
90
+ public_key.dsa_verify_asn1(digest.digest(signing_input), SecurityUtils.raw_to_asn1(signature, public_key))
92
91
  end
93
92
 
94
93
  def verify_hmac(algorithm, public_key, signing_input, signature)
95
- authenticator, padded_key = rbnacl_fixup(algorithm, public_key)
94
+ authenticator, padded_key = SecurityUtils.rbnacl_fixup(algorithm, public_key)
96
95
  if authenticator && padded_key
97
96
  begin
98
97
  authenticator.verify(padded_key, signature.encode('binary'), signing_input.encode('binary'))
@@ -100,46 +99,8 @@ module JWT
100
99
  false
101
100
  end
102
101
  else
103
- secure_compare(signature, sign_hmac(algorithm, signing_input, public_key))
102
+ SecurityUtils.secure_compare(signature, sign_hmac(algorithm, signing_input, public_key))
104
103
  end
105
104
  end
106
-
107
- def asn1_to_raw(signature, public_key)
108
- byte_size = (public_key.group.degree + 7) / 8
109
- OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
110
- end
111
-
112
- def raw_to_asn1(signature, private_key)
113
- byte_size = (private_key.group.degree + 7) / 8
114
- r = signature[0..(byte_size - 1)]
115
- s = signature[byte_size..-1] || ''
116
- OpenSSL::ASN1::Sequence.new([r, s].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
117
- end
118
-
119
- def rbnacl_fixup(algorithm, key)
120
- algorithm = algorithm.sub('HS', 'SHA').to_sym
121
-
122
- return [] unless defined?(RbNaCl) && RbNaCl::HMAC.constants(false).include?(algorithm)
123
-
124
- authenticator = RbNaCl::HMAC.const_get(algorithm)
125
-
126
- # Fall back to OpenSSL for keys larger than 32 bytes.
127
- return [] if key.bytesize > authenticator.key_bytes
128
-
129
- [
130
- authenticator,
131
- key.bytes.fill(0, key.bytesize...authenticator.key_bytes).pack('C*')
132
- ]
133
- end
134
-
135
- # From devise
136
- # constant-time comparison algorithm to prevent timing attacks
137
- def secure_compare(a, b)
138
- return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize
139
- l = a.unpack "C#{a.bytesize}"
140
- res = 0
141
- b.each_byte { |byte| res |= byte ^ l.shift }
142
- res.zero?
143
- end
144
105
  end
145
106
  end
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'jwt/error'
3
4
 
4
5
  module JWT
5
6
  # JWT verify methods
6
7
  class Verify
8
+ DEFAULTS = {
9
+ leeway: 0
10
+ }.freeze
11
+
7
12
  class << self
8
- %w(verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub).each do |method_name|
13
+ %w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub].each do |method_name|
9
14
  define_method method_name do |payload, options|
10
15
  new(payload, options).send(method_name)
11
16
  end
@@ -21,12 +26,14 @@ module JWT
21
26
 
22
27
  def initialize(payload, options)
23
28
  @payload = payload
24
- @options = options
29
+ @options = DEFAULTS.merge(options)
25
30
  end
26
31
 
27
32
  def verify_aud
28
33
  return unless (options_aud = @options[:aud])
29
- raise(JWT::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{@payload['aud'] || '<none>'}") if ([*@payload['aud']] & [*options_aud]).empty?
34
+
35
+ aud = @payload['aud']
36
+ raise(JWT::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{aud || '<none>'}") if ([*aud] & [*options_aud]).empty?
30
37
  end
31
38
 
32
39
  def verify_expiration
@@ -36,19 +43,28 @@ module JWT
36
43
 
37
44
  def verify_iat
38
45
  return unless @payload.include?('iat')
39
- raise(JWT::InvalidIatError, 'Invalid iat') if !@payload['iat'].is_a?(Numeric) || @payload['iat'].to_f > (Time.now.to_f + iat_leeway)
46
+
47
+ iat = @payload['iat']
48
+ raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(Numeric) || iat.to_f > (Time.now.to_f + iat_leeway)
40
49
  end
41
50
 
42
51
  def verify_iss
43
52
  return unless (options_iss = @options[:iss])
44
- raise(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{@payload['iss'] || '<none>'}") if @payload['iss'].to_s != options_iss.to_s
53
+
54
+ iss = @payload['iss']
55
+
56
+ return if Array(options_iss).map(&:to_s).include?(iss.to_s)
57
+
58
+ raise(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{iss || '<none>'}")
45
59
  end
46
60
 
47
61
  def verify_jti
48
62
  options_verify_jti = @options[:verify_jti]
63
+ jti = @payload['jti']
64
+
49
65
  if options_verify_jti.respond_to?(:call)
50
- raise(JWT::InvalidJtiError, 'Invalid jti') unless options_verify_jti.call(@payload['jti'])
51
- elsif @payload['jti'].to_s.strip.empty?
66
+ raise(JWT::InvalidJtiError, 'Invalid jti') unless options_verify_jti.call(jti)
67
+ elsif jti.to_s.strip.empty?
52
68
  raise(JWT::InvalidJtiError, 'Missing jti')
53
69
  end
54
70
  end
@@ -60,7 +76,8 @@ module JWT
60
76
 
61
77
  def verify_sub
62
78
  return unless (options_sub = @options[:sub])
63
- raise(JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{@payload['sub'] || '<none>'}") unless @payload['sub'].to_s == options_sub.to_s
79
+ sub = @payload['sub']
80
+ raise(JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{sub || '<none>'}") unless sub.to_s == options_sub.to_s
64
81
  end
65
82
 
66
83
  private
@@ -16,7 +16,7 @@ module JWT
16
16
  # tiny version
17
17
  TINY = 0
18
18
  # alpha, beta, etc. tag
19
- PRE = 'beta1'.freeze
19
+ PRE = nil
20
20
 
21
21
  # Build version string
22
22
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
@@ -13,12 +13,12 @@ Gem::Specification.new do |spec|
13
13
  spec.description = 'A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.'
14
14
  spec.homepage = 'http://github.com/jwt/ruby-jwt'
15
15
  spec.license = 'MIT'
16
- spec.required_ruby_version = '~> 2.1'
16
+ spec.required_ruby_version = '>= 2.1'
17
17
 
18
18
  spec.files = `git ls-files -z`.split("\x0")
19
19
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
- spec.require_paths = %w(lib)
21
+ spec.require_paths = %w[lib]
22
22
 
23
23
  spec.add_development_dependency 'bundler'
24
24
  spec.add_development_dependency 'rake'
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative '../spec_helper'
3
4
  require 'jwt'
4
5
 
@@ -122,13 +123,13 @@ describe 'README.md code test' do
122
123
 
123
124
  context 'aud' do
124
125
  it 'array' do
125
- aud = %w(Young Old)
126
+ aud = %w[Young Old]
126
127
  aud_payload = { data: 'data', aud: aud }
127
128
 
128
129
  token = JWT.encode aud_payload, hmac_secret, 'HS256'
129
130
 
130
131
  expect do
131
- JWT.decode token, hmac_secret, true, aud: %w(Old Young), verify_aud: true, algorithm: 'HS256'
132
+ JWT.decode token, hmac_secret, true, aud: %w[Old Young], verify_aud: true, algorithm: 'HS256'
132
133
  end.not_to raise_error
133
134
  end
134
135
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'spec_helper'
3
4
  require 'jwt/verify'
4
5
 
@@ -9,7 +10,7 @@ module JWT
9
10
 
10
11
  context '.verify_aud(payload, options)' do
11
12
  let(:scalar_aud) { 'ruby-jwt-aud' }
12
- let(:array_aud) { %w(ruby-jwt-aud test-aud ruby-ruby-ruby) }
13
+ let(:array_aud) { %w[ruby-jwt-aud test-aud ruby-ruby-ruby] }
13
14
  let(:scalar_payload) { base_payload.merge('aud' => scalar_aud) }
14
15
  let(:array_payload) { base_payload.merge('aud' => array_aud) }
15
16
 
@@ -43,7 +44,6 @@ module JWT
43
44
  end
44
45
 
45
46
  context '.verify_expiration(payload, options)' do
46
- let(:leeway) { 10 }
47
47
  let(:payload) { base_payload.merge('exp' => (Time.now.to_i - 5)) }
48
48
 
49
49
  it 'must raise JWT::ExpiredSignature when the token has expired' do
@@ -67,6 +67,16 @@ module JWT
67
67
  Verify.verify_expiration(payload, options)
68
68
  end.to raise_error JWT::ExpiredSignature
69
69
  end
70
+
71
+ context 'when leeway is not specified' do
72
+ let(:options) { {} }
73
+
74
+ it 'used a default leeway of 0' do
75
+ expect do
76
+ Verify.verify_expiration(payload, options)
77
+ end.to raise_error JWT::ExpiredSignature
78
+ end
79
+ end
70
80
  end
71
81
 
72
82
  context '.verify_iat(payload, options)' do
@@ -108,20 +118,39 @@ module JWT
108
118
 
109
119
  let(:invalid_token) { JWT.encode base_payload, payload[:secret] }
110
120
 
111
- it 'must raise JWT::InvalidIssuerError when the configured issuer does not match the payload issuer' do
112
- expect do
113
- Verify.verify_iss(payload, options.merge(iss: 'mismatched-issuer'))
114
- end.to raise_error JWT::InvalidIssuerError
115
- end
116
-
117
- it 'must raise JWT::InvalidIssuerError when the payload does not include an issuer' do
118
- expect do
119
- Verify.verify_iss(base_payload, options.merge(iss: iss))
120
- end.to raise_error(JWT::InvalidIssuerError, /received <none>/)
121
- end
122
-
123
- it 'must allow a matching issuer to pass' do
124
- Verify.verify_iss(payload, options.merge(iss: iss))
121
+ context 'when iss is a String' do
122
+ it 'must raise JWT::InvalidIssuerError when the configured issuer does not match the payload issuer' do
123
+ expect do
124
+ Verify.verify_iss(payload, options.merge(iss: 'mismatched-issuer'))
125
+ end.to raise_error JWT::InvalidIssuerError
126
+ end
127
+
128
+ it 'must raise JWT::InvalidIssuerError when the payload does not include an issuer' do
129
+ expect do
130
+ Verify.verify_iss(base_payload, options.merge(iss: iss))
131
+ end.to raise_error(JWT::InvalidIssuerError, /received <none>/)
132
+ end
133
+
134
+ it 'must allow a matching issuer to pass' do
135
+ Verify.verify_iss(payload, options.merge(iss: iss))
136
+ end
137
+ end
138
+ context 'when iss is an Array' do
139
+ it 'must raise JWT::InvalidIssuerError when no matching issuers in array' do
140
+ expect do
141
+ Verify.verify_iss(payload, options.merge(iss: %w[first second]))
142
+ end.to raise_error JWT::InvalidIssuerError
143
+ end
144
+
145
+ it 'must raise JWT::InvalidIssuerError when the payload does not include an issuer' do
146
+ expect do
147
+ Verify.verify_iss(base_payload, options.merge(iss: %w[first second]))
148
+ end.to raise_error(JWT::InvalidIssuerError, /received <none>/)
149
+ end
150
+
151
+ it 'must allow an array with matching issuer to pass' do
152
+ Verify.verify_iss(payload, options.merge(iss: ['first', iss, 'third']))
153
+ end
125
154
  end
126
155
  end
127
156
 
@@ -60,9 +60,17 @@ describe JWT do
60
60
  JWT.encode payload, nil, alg
61
61
  end.to raise_error JWT::InvalidPayload
62
62
  end
63
+
64
+ it 'should display a better error message if payload exp is not an Integer' do
65
+ payload['exp'] = Time.now.to_i.to_s
66
+
67
+ expect do
68
+ JWT.encode payload, nil, alg
69
+ end.to raise_error JWT::InvalidPayload
70
+ end
63
71
  end
64
72
 
65
- %w(HS256 HS512256 HS384 HS512).each do |alg|
73
+ %w[HS256 HS512256 HS384 HS512].each do |alg|
66
74
  context "alg: #{alg}" do
67
75
  it 'should generate a valid token' do
68
76
  token = JWT.encode payload, data[:secret], alg
@@ -91,7 +99,7 @@ describe JWT do
91
99
  end
92
100
  end
93
101
 
94
- %w(RS256 RS384 RS512).each do |alg|
102
+ %w[RS256 RS384 RS512].each do |alg|
95
103
  context "alg: #{alg}" do
96
104
  it 'should generate a valid token' do
97
105
  token = JWT.encode payload, data[:rsa_private], alg
@@ -124,7 +132,7 @@ describe JWT do
124
132
  end
125
133
  end
126
134
 
127
- %w(ES256 ES384 ES512).each do |alg|
135
+ %w[ES256 ES384 ES512].each do |alg|
128
136
  context "alg: #{alg}" do
129
137
  before(:each) do
130
138
  data[alg] = JWT.encode payload, data["#{alg}_private"], alg
@@ -230,4 +238,20 @@ describe JWT do
230
238
  expect(JWT::Encode.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
231
239
  end
232
240
  end
241
+
242
+ it 'should not verify token even if the payload has claims' do
243
+ head = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'
244
+ load = 'eyJ1c2VyX2lkIjo1NCwiZXhwIjoxNTA0MzkwODA0fQ'
245
+ sign = 'Skpi6FfYMbZ-DwW9ocyRIosNMdPMAIWRLYxRO68GTQk'
246
+
247
+ expect do
248
+ JWT.decode([head, load, sign].join('.'), '', false)
249
+ end.not_to raise_error
250
+ end
251
+
252
+ it 'should not raise InvalidPayload exception if payload is an array' do
253
+ expect do
254
+ JWT.encode(['my', 'payload'], 'secret')
255
+ end.not_to raise_error
256
+ end
233
257
  end
@@ -18,7 +18,7 @@ CERT_PATH = File.join(File.dirname(__FILE__), 'fixtures', 'certs')
18
18
 
19
19
  RSpec.configure do |config|
20
20
  config.expect_with :rspec do |c|
21
- c.syntax = [:should, :expect]
21
+ c.syntax = %i[should expect]
22
22
  end
23
23
 
24
24
  config.run_all_when_everything_filtered = true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Rudat
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-27 00:00:00.000000000 Z
11
+ date: 2017-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -130,7 +130,9 @@ extensions: []
130
130
  extra_rdoc_files: []
131
131
  files:
132
132
  - ".codeclimate.yml"
133
+ - ".ebert.yml"
133
134
  - ".gitignore"
135
+ - ".reek.yml"
134
136
  - ".rspec"
135
137
  - ".rubocop.yml"
136
138
  - ".travis.yml"
@@ -145,6 +147,7 @@ files:
145
147
  - lib/jwt/default_options.rb
146
148
  - lib/jwt/encode.rb
147
149
  - lib/jwt/error.rb
150
+ - lib/jwt/security_utils.rb
148
151
  - lib/jwt/signature.rb
149
152
  - lib/jwt/verify.rb
150
153
  - lib/jwt/version.rb
@@ -183,17 +186,17 @@ require_paths:
183
186
  - lib
184
187
  required_ruby_version: !ruby/object:Gem::Requirement
185
188
  requirements:
186
- - - "~>"
189
+ - - ">="
187
190
  - !ruby/object:Gem::Version
188
191
  version: '2.1'
189
192
  required_rubygems_version: !ruby/object:Gem::Requirement
190
193
  requirements:
191
- - - ">"
194
+ - - ">="
192
195
  - !ruby/object:Gem::Version
193
- version: 1.3.1
196
+ version: '0'
194
197
  requirements: []
195
198
  rubyforge_project:
196
- rubygems_version: 2.6.8
199
+ rubygems_version: 2.6.13
197
200
  signing_key:
198
201
  specification_version: 4
199
202
  summary: JSON Web Token implementation in Ruby