jwt 2.8.2 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,49 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
5
- module HmacRbNaCl
6
- MAPPING = { 'HS512256' => ::RbNaCl::HMAC::SHA512256 }.freeze
7
- SUPPORTED = MAPPING.keys
8
- class << self
9
- def sign(algorithm, msg, key)
10
- Deprecations.warning("The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
11
- if (hmac = resolve_algorithm(algorithm))
12
- hmac.auth(key_for_rbnacl(hmac, key).encode('binary'), msg.encode('binary'))
13
- else
14
- Hmac.sign(algorithm, msg, key)
15
- end
16
- end
17
-
18
- def verify(algorithm, key, signing_input, signature)
19
- Deprecations.warning("The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
20
- if (hmac = resolve_algorithm(algorithm))
21
- hmac.verify(key_for_rbnacl(hmac, key).encode('binary'), signature.encode('binary'), signing_input.encode('binary'))
22
- else
23
- Hmac.verify(algorithm, key, signing_input, signature)
24
- end
25
- rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
26
- false
27
- end
28
-
29
- private
30
-
31
- def key_for_rbnacl(hmac, key)
32
- key ||= ''
33
- raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
34
-
35
- return padded_empty_key(hmac.key_bytes) if key == ''
36
-
37
- key
38
- end
39
-
40
- def resolve_algorithm(algorithm)
41
- MAPPING.fetch(algorithm)
42
- end
43
-
44
- def padded_empty_key(length)
45
- Array.new(length, 0x0).pack('C*').encode('binary')
46
- end
4
+ module JWA
5
+ class HmacRbNaCl
6
+ include JWT::JWA::SigningAlgorithm
7
+
8
+ def initialize(alg, hmac)
9
+ @alg = alg
10
+ @hmac = hmac
11
+ end
12
+
13
+ def sign(data:, signing_key:)
14
+ Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
15
+ hmac.auth(key_for_rbnacl(hmac, signing_key).encode('binary'), data.encode('binary'))
16
+ end
17
+
18
+ def verify(data:, signature:, verification_key:)
19
+ Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
20
+ hmac.verify(key_for_rbnacl(hmac, verification_key).encode('binary'), signature.encode('binary'), data.encode('binary'))
21
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
22
+ false
23
+ end
24
+
25
+ register_algorithm(new('HS512256', ::RbNaCl::HMAC::SHA512256))
26
+
27
+ private
28
+
29
+ attr_reader :hmac
30
+
31
+ def key_for_rbnacl(hmac, key)
32
+ key ||= ''
33
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
34
+
35
+ return padded_empty_key(hmac.key_bytes) if key == ''
36
+
37
+ key
38
+ end
39
+
40
+ def padded_empty_key(length)
41
+ Array.new(length, 0x0).pack('C*').encode('binary')
47
42
  end
48
43
  end
49
44
  end
@@ -1,45 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
5
- module HmacRbNaClFixed
6
- MAPPING = { 'HS512256' => ::RbNaCl::HMAC::SHA512256 }.freeze
7
- SUPPORTED = MAPPING.keys
8
-
9
- class << self
10
- def sign(algorithm, msg, key)
11
- key ||= ''
12
- Deprecations.warning("The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
13
- raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
14
-
15
- if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes
16
- hmac.auth(padded_key_bytes(key, hmac.key_bytes), msg.encode('binary'))
17
- else
18
- Hmac.sign(algorithm, msg, key)
19
- end
20
- end
21
-
22
- def verify(algorithm, key, signing_input, signature)
23
- key ||= ''
24
- Deprecations.warning("The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
25
- raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
26
-
27
- if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes
28
- hmac.verify(padded_key_bytes(key, hmac.key_bytes), signature.encode('binary'), signing_input.encode('binary'))
29
- else
30
- Hmac.verify(algorithm, key, signing_input, signature)
31
- end
32
- rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
33
- false
34
- end
35
-
36
- def resolve_algorithm(algorithm)
37
- MAPPING.fetch(algorithm)
38
- end
39
-
40
- def padded_key_bytes(key, bytesize)
41
- key.bytes.fill(0, key.bytesize...bytesize).pack('C*')
42
- end
4
+ module JWA
5
+ class HmacRbNaClFixed
6
+ include JWT::JWA::SigningAlgorithm
7
+
8
+ def initialize(alg, hmac)
9
+ @alg = alg
10
+ @hmac = hmac
11
+ end
12
+
13
+ def sign(data:, signing_key:)
14
+ signing_key ||= ''
15
+ Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
16
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless signing_key.is_a?(String)
17
+
18
+ hmac.auth(padded_key_bytes(signing_key, hmac.key_bytes), data.encode('binary'))
19
+ end
20
+
21
+ def verify(data:, signature:, verification_key:)
22
+ verification_key ||= ''
23
+ Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
24
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless verification_key.is_a?(String)
25
+
26
+ hmac.verify(padded_key_bytes(verification_key, hmac.key_bytes), signature.encode('binary'), data.encode('binary'))
27
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
28
+ false
29
+ end
30
+
31
+ register_algorithm(new('HS512256', ::RbNaCl::HMAC::SHA512256))
32
+
33
+ private
34
+
35
+ attr_reader :hmac
36
+
37
+ def padded_key_bytes(key, bytesize)
38
+ key.bytes.fill(0, key.bytesize...bytesize).pack('C*')
43
39
  end
44
40
  end
45
41
  end
data/lib/jwt/jwa/none.rb CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
- module None
6
- module_function
5
+ class None
6
+ include JWT::JWA::SigningAlgorithm
7
7
 
8
- SUPPORTED = %w[none].freeze
8
+ def initialize
9
+ @alg = 'none'
10
+ end
9
11
 
10
12
  def sign(*)
11
13
  ''
@@ -14,6 +16,8 @@ module JWT
14
16
  def verify(*)
15
17
  true
16
18
  end
19
+
20
+ register_algorithm(new)
17
21
  end
18
22
  end
19
23
  end
data/lib/jwt/jwa/ps.rb CHANGED
@@ -2,29 +2,35 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
- module Ps
6
- # RSASSA-PSS signing algorithms
5
+ class Ps
6
+ include JWT::JWA::SigningAlgorithm
7
7
 
8
- module_function
9
-
10
- SUPPORTED = %w[PS256 PS384 PS512].freeze
8
+ def initialize(alg)
9
+ @alg = alg
10
+ @digest_algorithm = alg.sub('PS', 'sha')
11
+ end
11
12
 
12
- def sign(algorithm, msg, key)
13
- unless key.is_a?(::OpenSSL::PKey::RSA)
14
- raise EncodeError, "The given key is a #{key_class}. It has to be an OpenSSL::PKey::RSA instance."
13
+ def sign(data:, signing_key:)
14
+ unless signing_key.is_a?(::OpenSSL::PKey::RSA)
15
+ raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance.")
15
16
  end
16
17
 
17
- translated_algorithm = algorithm.sub('PS', 'sha')
18
-
19
- key.sign_pss(translated_algorithm, msg, salt_length: :digest, mgf1_hash: translated_algorithm)
18
+ signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm)
20
19
  end
21
20
 
22
- def verify(algorithm, public_key, signing_input, signature)
23
- translated_algorithm = algorithm.sub('PS', 'sha')
24
- public_key.verify_pss(translated_algorithm, signature, signing_input, salt_length: :auto, mgf1_hash: translated_algorithm)
21
+ def verify(data:, signature:, verification_key:)
22
+ verification_key.verify_pss(digest_algorithm, signature, data, salt_length: :auto, mgf1_hash: digest_algorithm)
25
23
  rescue OpenSSL::PKey::PKeyError
26
24
  raise JWT::VerificationError, 'Signature verification raised'
27
25
  end
26
+
27
+ register_algorithm(new('PS256'))
28
+ register_algorithm(new('PS384'))
29
+ register_algorithm(new('PS512'))
30
+
31
+ private
32
+
33
+ attr_reader :digest_algorithm
28
34
  end
29
35
  end
30
36
  end
data/lib/jwt/jwa/rsa.rb CHANGED
@@ -2,24 +2,35 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
- module Rsa
6
- module_function
5
+ class Rsa
6
+ include JWT::JWA::SigningAlgorithm
7
7
 
8
- SUPPORTED = %w[RS256 RS384 RS512].freeze
8
+ def initialize(alg)
9
+ @alg = alg
10
+ @digest = OpenSSL::Digest.new(alg.sub('RS', 'SHA'))
11
+ end
9
12
 
10
- def sign(algorithm, msg, key)
11
- unless key.is_a?(OpenSSL::PKey::RSA)
12
- raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance"
13
+ def sign(data:, signing_key:)
14
+ unless signing_key.is_a?(OpenSSL::PKey::RSA)
15
+ raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance")
13
16
  end
14
17
 
15
- key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
18
+ signing_key.sign(digest, data)
16
19
  end
17
20
 
18
- def verify(algorithm, public_key, signing_input, signature)
19
- public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
21
+ def verify(data:, signature:, verification_key:)
22
+ verification_key.verify(digest, signature, data)
20
23
  rescue OpenSSL::PKey::PKeyError
21
24
  raise JWT::VerificationError, 'Signature verification raised'
22
25
  end
26
+
27
+ register_algorithm(new('RS256'))
28
+ register_algorithm(new('RS384'))
29
+ register_algorithm(new('RS512'))
30
+
31
+ private
32
+
33
+ attr_reader :digest
23
34
  end
24
35
  end
25
36
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ module SigningAlgorithm
6
+ module ClassMethods
7
+ def register_algorithm(algo)
8
+ ::JWT::JWA.register_algorithm(algo)
9
+ end
10
+ end
11
+
12
+ def self.included(klass)
13
+ klass.extend(ClassMethods)
14
+ end
15
+
16
+ attr_reader :alg
17
+
18
+ def valid_alg?(alg_to_check)
19
+ alg&.casecmp(alg_to_check)&.zero? == true
20
+ end
21
+
22
+ def header(*)
23
+ { 'alg' => alg }
24
+ end
25
+
26
+ def sign(*)
27
+ raise_sign_error!('Algorithm implementation is missing the sign method')
28
+ end
29
+
30
+ def verify(*)
31
+ raise_verify_error!('Algorithm implementation is missing the verify method')
32
+ end
33
+
34
+ def raise_verify_error!(message)
35
+ raise(DecodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
36
+ end
37
+
38
+ def raise_sign_error!(message)
39
+ raise(EncodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
40
+ end
41
+ end
42
+
43
+ class << self
44
+ def register_algorithm(algo)
45
+ algorithms[algo.alg.to_s.downcase] = algo
46
+ end
47
+
48
+ def find(algo)
49
+ algorithms.fetch(algo.to_s.downcase, Unsupported)
50
+ end
51
+
52
+ private
53
+
54
+ def algorithms
55
+ @algorithms ||= {}
56
+ end
57
+ end
58
+ end
59
+ end
@@ -3,16 +3,16 @@
3
3
  module JWT
4
4
  module JWA
5
5
  module Unsupported
6
- module_function
6
+ class << self
7
+ include JWT::JWA::SigningAlgorithm
7
8
 
8
- SUPPORTED = [].freeze
9
+ def sign(*)
10
+ raise_sign_error!('Unsupported signing method')
11
+ end
9
12
 
10
- def sign(*)
11
- raise NotImplementedError, 'Unsupported signing method'
12
- end
13
-
14
- def verify(*)
15
- raise JWT::VerificationError, 'Algorithm not supported'
13
+ def verify(*)
14
+ raise JWT::VerificationError, 'Algorithm not supported'
15
+ end
16
16
  end
17
17
  end
18
18
  end
@@ -3,23 +3,40 @@
3
3
  module JWT
4
4
  module JWA
5
5
  class Wrapper
6
- attr_reader :alg, :cls
6
+ include SigningAlgorithm
7
7
 
8
- def initialize(alg, cls)
9
- @alg = alg
10
- @cls = cls
8
+ def initialize(algorithm)
9
+ @algorithm = algorithm
10
+ end
11
+
12
+ def alg
13
+ return @algorithm.alg if @algorithm.respond_to?(:alg)
14
+
15
+ super
11
16
  end
12
17
 
13
18
  def valid_alg?(alg_to_check)
14
- alg&.casecmp(alg_to_check)&.zero? == true
19
+ return @algorithm.valid_alg?(alg_to_check) if @algorithm.respond_to?(:valid_alg?)
20
+
21
+ super
15
22
  end
16
23
 
17
- def sign(data:, signing_key:)
18
- cls.sign(alg, data, signing_key)
24
+ def header(*args, **kwargs)
25
+ return @algorithm.header(*args, **kwargs) if @algorithm.respond_to?(:header)
26
+
27
+ super
19
28
  end
20
29
 
21
- def verify(data:, signature:, verification_key:)
22
- cls.verify(alg, verification_key, data, signature)
30
+ def sign(*args, **kwargs)
31
+ return @algorithm.sign(*args, **kwargs) if @algorithm.respond_to?(:sign)
32
+
33
+ super
34
+ end
35
+
36
+ def verify(*args, **kwargs)
37
+ return @algorithm.verify(*args, **kwargs) if @algorithm.respond_to?(:verify)
38
+
39
+ super
23
40
  end
24
41
  end
25
42
  end
data/lib/jwt/jwa.rb CHANGED
@@ -8,54 +8,37 @@ rescue LoadError
8
8
  raise if defined?(RbNaCl)
9
9
  end
10
10
 
11
- require_relative 'jwa/hmac'
12
- require_relative 'jwa/eddsa'
11
+ require_relative 'jwa/signing_algorithm'
13
12
  require_relative 'jwa/ecdsa'
14
- require_relative 'jwa/rsa'
15
- require_relative 'jwa/ps'
13
+ require_relative 'jwa/hmac'
16
14
  require_relative 'jwa/none'
15
+ require_relative 'jwa/ps'
16
+ require_relative 'jwa/rsa'
17
17
  require_relative 'jwa/unsupported'
18
18
  require_relative 'jwa/wrapper'
19
19
 
20
+ if JWT.rbnacl?
21
+ require_relative 'jwa/eddsa'
22
+ end
23
+
24
+ if JWT.rbnacl_6_or_greater?
25
+ require_relative 'jwa/hmac_rbnacl'
26
+ elsif JWT.rbnacl?
27
+ require_relative 'jwa/hmac_rbnacl_fixed'
28
+ end
29
+
20
30
  module JWT
21
31
  module JWA
22
- ALGOS = [Hmac, Ecdsa, Rsa, Eddsa, Ps, None, Unsupported].tap do |l|
23
- if ::JWT.rbnacl_6_or_greater?
24
- require_relative 'jwa/hmac_rbnacl'
25
- l << Algos::HmacRbNaCl
26
- elsif ::JWT.rbnacl?
27
- require_relative 'jwa/hmac_rbnacl_fixed'
28
- l << Algos::HmacRbNaClFixed
29
- end
30
- end.freeze
31
-
32
32
  class << self
33
- def find(algorithm)
34
- indexed[algorithm&.downcase]
35
- end
36
-
37
- def create(algorithm)
38
- return algorithm if JWA.implementation?(algorithm)
39
-
40
- Wrapper.new(*find(algorithm))
41
- end
42
-
43
- def implementation?(algorithm)
44
- (algorithm.respond_to?(:valid_alg?) && algorithm.respond_to?(:verify)) ||
45
- (algorithm.respond_to?(:alg) && algorithm.respond_to?(:sign))
46
- end
33
+ def resolve(algorithm)
34
+ return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol)
47
35
 
48
- private
49
-
50
- def indexed
51
- @indexed ||= begin
52
- fallback = [nil, Unsupported]
53
- ALGOS.each_with_object(Hash.new(fallback)) do |cls, hash|
54
- cls.const_get(:SUPPORTED).each do |alg|
55
- hash[alg.downcase] = [alg, cls]
56
- end
57
- end
36
+ unless algorithm.is_a?(SigningAlgorithm)
37
+ Deprecations.warning('Custom algorithms are required to include JWT::JWA::SigningAlgorithm')
38
+ return Wrapper.new(algorithm)
58
39
  end
40
+
41
+ algorithm
59
42
  end
60
43
  end
61
44
  end
data/lib/jwt/version.rb CHANGED
@@ -11,9 +11,9 @@ module JWT
11
11
  # major version
12
12
  MAJOR = 2
13
13
  # minor version
14
- MINOR = 8
14
+ MINOR = 9
15
15
  # tiny version
16
- TINY = 2
16
+ TINY = 0
17
17
  # alpha, beta, etc. tag
18
18
  PRE = nil
19
19
 
data/lib/jwt.rb CHANGED
@@ -9,6 +9,7 @@ require 'jwt/deprecations'
9
9
  require 'jwt/encode'
10
10
  require 'jwt/error'
11
11
  require 'jwt/jwk'
12
+ require 'jwt/claims'
12
13
 
13
14
  # JSON Web Token implementation
14
15
  #
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.8.2
4
+ version: 2.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Rudat
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-18 00:00:00.000000000 Z
11
+ date: 2024-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -123,7 +123,16 @@ files:
123
123
  - README.md
124
124
  - lib/jwt.rb
125
125
  - lib/jwt/base64.rb
126
- - lib/jwt/claims_validator.rb
126
+ - lib/jwt/claims.rb
127
+ - lib/jwt/claims/audience.rb
128
+ - lib/jwt/claims/expiration.rb
129
+ - lib/jwt/claims/issued_at.rb
130
+ - lib/jwt/claims/issuer.rb
131
+ - lib/jwt/claims/jwt_id.rb
132
+ - lib/jwt/claims/not_before.rb
133
+ - lib/jwt/claims/numeric.rb
134
+ - lib/jwt/claims/required.rb
135
+ - lib/jwt/claims/subject.rb
127
136
  - lib/jwt/configuration.rb
128
137
  - lib/jwt/configuration/container.rb
129
138
  - lib/jwt/configuration/decode_configuration.rb
@@ -142,6 +151,7 @@ files:
142
151
  - lib/jwt/jwa/none.rb
143
152
  - lib/jwt/jwa/ps.rb
144
153
  - lib/jwt/jwa/rsa.rb
154
+ - lib/jwt/jwa/signing_algorithm.rb
145
155
  - lib/jwt/jwa/unsupported.rb
146
156
  - lib/jwt/jwa/wrapper.rb
147
157
  - lib/jwt/jwk.rb
@@ -154,7 +164,6 @@ files:
154
164
  - lib/jwt/jwk/rsa.rb
155
165
  - lib/jwt/jwk/set.rb
156
166
  - lib/jwt/jwk/thumbprint.rb
157
- - lib/jwt/verify.rb
158
167
  - lib/jwt/version.rb
159
168
  - lib/jwt/x5c_key_finder.rb
160
169
  - ruby-jwt.gemspec
@@ -163,9 +172,9 @@ licenses:
163
172
  - MIT
164
173
  metadata:
165
174
  bug_tracker_uri: https://github.com/jwt/ruby-jwt/issues
166
- changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.8.2/CHANGELOG.md
175
+ changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.9.0/CHANGELOG.md
167
176
  rubygems_mfa_required: 'true'
168
- post_install_message:
177
+ post_install_message:
169
178
  rdoc_options: []
170
179
  require_paths:
171
180
  - lib
@@ -180,8 +189,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
189
  - !ruby/object:Gem::Version
181
190
  version: '0'
182
191
  requirements: []
183
- rubygems_version: 3.5.3
184
- signing_key:
192
+ rubygems_version: 3.5.16
193
+ signing_key:
185
194
  specification_version: 4
186
195
  summary: JSON Web Token implementation in Ruby
187
196
  test_files: []
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'error'
4
-
5
- module JWT
6
- class ClaimsValidator
7
- NUMERIC_CLAIMS = %i[
8
- exp
9
- iat
10
- nbf
11
- ].freeze
12
-
13
- def initialize(payload)
14
- @payload = payload.transform_keys(&:to_sym)
15
- end
16
-
17
- def validate!
18
- validate_numeric_claims
19
-
20
- true
21
- end
22
-
23
- private
24
-
25
- def validate_numeric_claims
26
- NUMERIC_CLAIMS.each do |claim|
27
- validate_is_numeric(claim) if @payload.key?(claim)
28
- end
29
- end
30
-
31
- def validate_is_numeric(claim)
32
- return if @payload[claim].is_a?(Numeric)
33
-
34
- raise InvalidPayload, "#{claim} claim must be a Numeric value but it is a #{@payload[claim].class}"
35
- end
36
- end
37
- end