jwt 2.0.0.beta1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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