jwt 2.2.1 → 2.7.1

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +79 -44
  3. data/CHANGELOG.md +271 -20
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +99 -0
  6. data/README.md +253 -35
  7. data/lib/jwt/algos/algo_wrapper.rb +26 -0
  8. data/lib/jwt/algos/ecdsa.rb +55 -14
  9. data/lib/jwt/algos/eddsa.rb +18 -8
  10. data/lib/jwt/algos/hmac.rb +57 -17
  11. data/lib/jwt/algos/hmac_rbnacl.rb +53 -0
  12. data/lib/jwt/algos/hmac_rbnacl_fixed.rb +52 -0
  13. data/lib/jwt/algos/none.rb +19 -0
  14. data/lib/jwt/algos/ps.rb +10 -12
  15. data/lib/jwt/algos/rsa.rb +9 -5
  16. data/lib/jwt/algos/unsupported.rb +7 -4
  17. data/lib/jwt/algos.rb +66 -0
  18. data/lib/jwt/claims_validator.rb +12 -8
  19. data/lib/jwt/configuration/container.rb +21 -0
  20. data/lib/jwt/configuration/decode_configuration.rb +46 -0
  21. data/lib/jwt/configuration/jwk_configuration.rb +27 -0
  22. data/lib/jwt/configuration.rb +15 -0
  23. data/lib/jwt/decode.rb +85 -17
  24. data/lib/jwt/encode.rb +30 -19
  25. data/lib/jwt/error.rb +16 -14
  26. data/lib/jwt/jwk/ec.rb +236 -0
  27. data/lib/jwt/jwk/hmac.rb +103 -0
  28. data/lib/jwt/jwk/key_base.rb +55 -0
  29. data/lib/jwt/jwk/key_finder.rb +19 -30
  30. data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
  31. data/lib/jwt/jwk/okp_rbnacl.rb +110 -0
  32. data/lib/jwt/jwk/rsa.rb +181 -25
  33. data/lib/jwt/jwk/set.rb +80 -0
  34. data/lib/jwt/jwk/thumbprint.rb +26 -0
  35. data/lib/jwt/jwk.rb +39 -15
  36. data/lib/jwt/verify.rb +18 -3
  37. data/lib/jwt/version.rb +23 -3
  38. data/lib/jwt/x5c_key_finder.rb +55 -0
  39. data/lib/jwt.rb +5 -4
  40. data/ruby-jwt.gemspec +15 -10
  41. metadata +30 -90
  42. data/.codeclimate.yml +0 -20
  43. data/.ebert.yml +0 -18
  44. data/.gitignore +0 -11
  45. data/.rspec +0 -1
  46. data/.rubocop.yml +0 -98
  47. data/.travis.yml +0 -20
  48. data/Appraisals +0 -14
  49. data/Gemfile +0 -3
  50. data/Rakefile +0 -11
  51. data/lib/jwt/default_options.rb +0 -15
  52. data/lib/jwt/security_utils.rb +0 -57
  53. data/lib/jwt/signature.rb +0 -52
@@ -1,34 +1,75 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Ecdsa
4
6
  module_function
5
7
 
6
- SUPPORTED = %w[ES256 ES384 ES512].freeze
7
8
  NAMED_CURVES = {
8
- 'prime256v1' => 'ES256',
9
- 'secp384r1' => 'ES384',
10
- 'secp521r1' => 'ES512'
9
+ 'prime256v1' => {
10
+ algorithm: 'ES256',
11
+ digest: 'sha256'
12
+ },
13
+ 'secp256r1' => { # alias for prime256v1
14
+ algorithm: 'ES256',
15
+ digest: 'sha256'
16
+ },
17
+ 'secp384r1' => {
18
+ algorithm: 'ES384',
19
+ digest: 'sha384'
20
+ },
21
+ 'secp521r1' => {
22
+ algorithm: 'ES512',
23
+ digest: 'sha512'
24
+ },
25
+ 'secp256k1' => {
26
+ algorithm: 'ES256K',
27
+ digest: 'sha256'
28
+ }
11
29
  }.freeze
12
30
 
13
- def sign(to_sign)
14
- algorithm, msg, key = to_sign.values
15
- key_algorithm = NAMED_CURVES[key.group.curve_name]
31
+ SUPPORTED = NAMED_CURVES.map { |_, c| c[:algorithm] }.uniq.freeze
32
+
33
+ def sign(algorithm, msg, key)
34
+ curve_definition = curve_by_name(key.group.curve_name)
35
+ key_algorithm = curve_definition[:algorithm]
16
36
  if algorithm != key_algorithm
17
37
  raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
18
38
  end
19
39
 
20
- digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
21
- SecurityUtils.asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
40
+ digest = OpenSSL::Digest.new(curve_definition[:digest])
41
+ asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
22
42
  end
23
43
 
24
- def verify(to_verify)
25
- algorithm, public_key, signing_input, signature = to_verify.values
26
- key_algorithm = NAMED_CURVES[public_key.group.curve_name]
44
+ def verify(algorithm, public_key, signing_input, signature)
45
+ curve_definition = curve_by_name(public_key.group.curve_name)
46
+ key_algorithm = curve_definition[:algorithm]
27
47
  if algorithm != key_algorithm
28
48
  raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
29
49
  end
30
- digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
31
- public_key.dsa_verify_asn1(digest.digest(signing_input), SecurityUtils.raw_to_asn1(signature, public_key))
50
+
51
+ digest = OpenSSL::Digest.new(curve_definition[:digest])
52
+ public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
53
+ rescue OpenSSL::PKey::PKeyError
54
+ raise JWT::VerificationError, 'Signature verification raised'
55
+ end
56
+
57
+ def curve_by_name(name)
58
+ NAMED_CURVES.fetch(name) do
59
+ raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
60
+ end
61
+ end
62
+
63
+ def raw_to_asn1(signature, private_key)
64
+ byte_size = (private_key.group.degree + 7) / 8
65
+ sig_bytes = signature[0..(byte_size - 1)]
66
+ sig_char = signature[byte_size..-1] || ''
67
+ OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
68
+ end
69
+
70
+ def asn1_to_raw(signature, public_key)
71
+ byte_size = (public_key.group.degree + 7) / 8
72
+ OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
32
73
  end
33
74
  end
34
75
  end
@@ -1,22 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Eddsa
4
6
  module_function
5
7
 
6
- SUPPORTED = %w[ED25519].freeze
8
+ SUPPORTED = %w[ED25519 EdDSA].freeze
9
+
10
+ def sign(algorithm, msg, key)
11
+ if key.class != RbNaCl::Signatures::Ed25519::SigningKey
12
+ raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey"
13
+ end
14
+ unless SUPPORTED.map(&:downcase).map(&:to_sym).include?(algorithm.downcase.to_sym)
15
+ raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided"
16
+ end
7
17
 
8
- def sign(to_sign)
9
- algorithm, msg, key = to_sign.values
10
- raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey" if key.class != RbNaCl::Signatures::Ed25519::SigningKey
11
- raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided" if algorithm.downcase.to_sym != key.primitive
12
18
  key.sign(msg)
13
19
  end
14
20
 
15
- def verify(to_verify)
16
- algorithm, public_key, signing_input, signature = to_verify.values
17
- raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{public_key.primitive} verification key was provided" if algorithm.downcase.to_sym != public_key.primitive
21
+ def verify(algorithm, public_key, signing_input, signature)
22
+ unless SUPPORTED.map(&:downcase).map(&:to_sym).include?(algorithm.downcase.to_sym)
23
+ raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided"
24
+ end
18
25
  raise DecodeError, "key given is a #{public_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey" if public_key.class != RbNaCl::Signatures::Ed25519::VerifyKey
26
+
19
27
  public_key.verify(signature, signing_input)
28
+ rescue RbNaCl::CryptoError
29
+ false
20
30
  end
21
31
  end
22
32
  end
@@ -1,33 +1,73 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Hmac
4
6
  module_function
5
7
 
6
- SUPPORTED = %w[HS256 HS512256 HS384 HS512].freeze
8
+ MAPPING = {
9
+ 'HS256' => OpenSSL::Digest::SHA256,
10
+ 'HS384' => OpenSSL::Digest::SHA384,
11
+ 'HS512' => OpenSSL::Digest::SHA512
12
+ }.freeze
7
13
 
8
- def sign(to_sign)
9
- algorithm, msg, key = to_sign.values
10
- authenticator, padded_key = SecurityUtils.rbnacl_fixup(algorithm, key)
11
- if authenticator && padded_key
12
- authenticator.auth(padded_key, msg.encode('binary'))
13
- else
14
- OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
14
+ SUPPORTED = MAPPING.keys
15
+
16
+ def sign(algorithm, msg, key)
17
+ key ||= ''
18
+
19
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
20
+
21
+ OpenSSL::HMAC.digest(MAPPING[algorithm].new, key, msg)
22
+ rescue OpenSSL::HMACError => e
23
+ if key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure'
24
+ raise JWT::DecodeError, 'OpenSSL 3.0 does not support nil or empty hmac_secret'
15
25
  end
26
+
27
+ raise e
28
+ end
29
+
30
+ def verify(algorithm, key, signing_input, signature)
31
+ SecurityUtils.secure_compare(signature, sign(algorithm, signing_input, key))
16
32
  end
17
33
 
18
- def verify(to_verify)
19
- algorithm, public_key, signing_input, signature = to_verify.values
20
- authenticator, padded_key = SecurityUtils.rbnacl_fixup(algorithm, public_key)
21
- if authenticator && padded_key
22
- begin
23
- authenticator.verify(padded_key, signature.encode('binary'), signing_input.encode('binary'))
24
- rescue RbNaCl::BadAuthenticatorError
25
- false
34
+ # Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb
35
+ # rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
36
+ module SecurityUtils
37
+ # Constant time string comparison, for fixed length strings.
38
+ #
39
+ # The values compared should be of fixed length, such as strings
40
+ # that have already been processed by HMAC. Raises in case of length mismatch.
41
+
42
+ if defined?(OpenSSL.fixed_length_secure_compare)
43
+ def fixed_length_secure_compare(a, b)
44
+ OpenSSL.fixed_length_secure_compare(a, b)
26
45
  end
27
46
  else
28
- SecurityUtils.secure_compare(signature, sign(JWT::Signature::ToSign.new(algorithm, signing_input, public_key)))
47
+ def fixed_length_secure_compare(a, b)
48
+ raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
49
+
50
+ l = a.unpack "C#{a.bytesize}"
51
+
52
+ res = 0
53
+ b.each_byte { |byte| res |= byte ^ l.shift }
54
+ res == 0
55
+ end
56
+ end
57
+ module_function :fixed_length_secure_compare
58
+
59
+ # Secure string comparison for strings of variable length.
60
+ #
61
+ # While a timing attack would not be able to discern the content of
62
+ # a secret compared via secure_compare, it is possible to determine
63
+ # the secret length. This should be considered when using secure_compare
64
+ # to compare weak, short secrets to user input.
65
+ def secure_compare(a, b)
66
+ a.bytesize == b.bytesize && fixed_length_secure_compare(a, b)
29
67
  end
68
+ module_function :secure_compare
30
69
  end
70
+ # rubocop:enable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
31
71
  end
32
72
  end
33
73
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Algos
5
+ module HmacRbNaCl
6
+ module_function
7
+
8
+ MAPPING = {
9
+ 'HS256' => ::RbNaCl::HMAC::SHA256,
10
+ 'HS512256' => ::RbNaCl::HMAC::SHA512256,
11
+ 'HS384' => nil,
12
+ 'HS512' => ::RbNaCl::HMAC::SHA512
13
+ }.freeze
14
+
15
+ SUPPORTED = MAPPING.keys
16
+
17
+ def sign(algorithm, msg, key)
18
+ if (hmac = resolve_algorithm(algorithm))
19
+ hmac.auth(key_for_rbnacl(hmac, key).encode('binary'), msg.encode('binary'))
20
+ else
21
+ Hmac.sign(algorithm, msg, key)
22
+ end
23
+ end
24
+
25
+ def verify(algorithm, key, signing_input, signature)
26
+ if (hmac = resolve_algorithm(algorithm))
27
+ hmac.verify(key_for_rbnacl(hmac, key).encode('binary'), signature.encode('binary'), signing_input.encode('binary'))
28
+ else
29
+ Hmac.verify(algorithm, key, signing_input, signature)
30
+ end
31
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
32
+ false
33
+ end
34
+
35
+ def key_for_rbnacl(hmac, key)
36
+ key ||= ''
37
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
38
+
39
+ return padded_empty_key(hmac.key_bytes) if key == ''
40
+
41
+ key
42
+ end
43
+
44
+ def resolve_algorithm(algorithm)
45
+ MAPPING.fetch(algorithm)
46
+ end
47
+
48
+ def padded_empty_key(length)
49
+ Array.new(length, 0x0).pack('C*').encode('binary')
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Algos
5
+ module HmacRbNaClFixed
6
+ module_function
7
+
8
+ MAPPING = {
9
+ 'HS256' => ::RbNaCl::HMAC::SHA256,
10
+ 'HS512256' => ::RbNaCl::HMAC::SHA512256,
11
+ 'HS384' => nil,
12
+ 'HS512' => ::RbNaCl::HMAC::SHA512
13
+ }.freeze
14
+
15
+ SUPPORTED = MAPPING.keys
16
+
17
+ def sign(algorithm, msg, key)
18
+ key ||= ''
19
+
20
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
21
+
22
+ if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes
23
+ hmac.auth(padded_key_bytes(key, hmac.key_bytes), msg.encode('binary'))
24
+ else
25
+ Hmac.sign(algorithm, msg, key)
26
+ end
27
+ end
28
+
29
+ def verify(algorithm, key, signing_input, signature)
30
+ key ||= ''
31
+
32
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
33
+
34
+ if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes
35
+ hmac.verify(padded_key_bytes(key, hmac.key_bytes), signature.encode('binary'), signing_input.encode('binary'))
36
+ else
37
+ Hmac.verify(algorithm, key, signing_input, signature)
38
+ end
39
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
40
+ false
41
+ end
42
+
43
+ def resolve_algorithm(algorithm)
44
+ MAPPING.fetch(algorithm)
45
+ end
46
+
47
+ def padded_key_bytes(key, bytesize)
48
+ key.bytes.fill(0, key.bytesize...bytesize).pack('C*')
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Algos
5
+ module None
6
+ module_function
7
+
8
+ SUPPORTED = %w[none].freeze
9
+
10
+ def sign(*)
11
+ ''
12
+ end
13
+
14
+ def verify(*)
15
+ true
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/jwt/algos/ps.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Ps
@@ -7,31 +9,27 @@ module JWT
7
9
 
8
10
  SUPPORTED = %w[PS256 PS384 PS512].freeze
9
11
 
10
- def sign(to_sign)
12
+ def sign(algorithm, msg, key)
11
13
  require_openssl!
12
14
 
13
- algorithm, msg, key = to_sign.values
14
-
15
- key_class = key.class
16
-
17
- raise EncodeError, "The given key is a #{key_class}. It has to be an OpenSSL::PKey::RSA instance." if key_class == String
15
+ raise EncodeError, "The given key is a #{key_class}. It has to be an OpenSSL::PKey::RSA instance." if key.is_a?(String)
18
16
 
19
17
  translated_algorithm = algorithm.sub('PS', 'sha')
20
18
 
21
19
  key.sign_pss(translated_algorithm, msg, salt_length: :digest, mgf1_hash: translated_algorithm)
22
20
  end
23
21
 
24
- def verify(to_verify)
22
+ def verify(algorithm, public_key, signing_input, signature)
25
23
  require_openssl!
26
-
27
- SecurityUtils.verify_ps(to_verify.algorithm, to_verify.public_key, to_verify.signing_input, to_verify.signature)
24
+ translated_algorithm = algorithm.sub('PS', 'sha')
25
+ public_key.verify_pss(translated_algorithm, signature, signing_input, salt_length: :auto, mgf1_hash: translated_algorithm)
26
+ rescue OpenSSL::PKey::PKeyError
27
+ raise JWT::VerificationError, 'Signature verification raised'
28
28
  end
29
29
 
30
30
  def require_openssl!
31
31
  if Object.const_defined?('OpenSSL')
32
- major, minor = OpenSSL::VERSION.split('.').first(2)
33
-
34
- unless major.to_i >= 2 && minor.to_i >= 1
32
+ if ::Gem::Version.new(OpenSSL::VERSION) < ::Gem::Version.new('2.1')
35
33
  raise JWT::RequiredDependencyError, "You currently have OpenSSL #{OpenSSL::VERSION}. PS support requires >= 2.1"
36
34
  end
37
35
  else
data/lib/jwt/algos/rsa.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Rsa
@@ -5,14 +7,16 @@ module JWT
5
7
 
6
8
  SUPPORTED = %w[RS256 RS384 RS512].freeze
7
9
 
8
- def sign(to_sign)
9
- algorithm, msg, key = to_sign.values
10
- raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance." if key.class == String
10
+ def sign(algorithm, msg, key)
11
+ raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance." if key.instance_of?(String)
12
+
11
13
  key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
12
14
  end
13
15
 
14
- def verify(to_verify)
15
- SecurityUtils.verify_rsa(to_verify.algorithm, to_verify.public_key, to_verify.signing_input, to_verify.signature)
16
+ def verify(algorithm, public_key, signing_input, signature)
17
+ public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
18
+ rescue OpenSSL::PKey::PKeyError
19
+ raise JWT::VerificationError, 'Signature verification raised'
16
20
  end
17
21
  end
18
22
  end
@@ -1,16 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Unsupported
4
6
  module_function
5
7
 
6
- SUPPORTED = Object.new.tap { |object| object.define_singleton_method(:include?) { |*| true } }
7
- def verify(*)
8
- raise JWT::VerificationError, 'Algorithm not supported'
9
- end
8
+ SUPPORTED = [].freeze
10
9
 
11
10
  def sign(*)
12
11
  raise NotImplementedError, 'Unsupported signing method'
13
12
  end
13
+
14
+ def verify(*)
15
+ raise JWT::VerificationError, 'Algorithm not supported'
16
+ end
14
17
  end
15
18
  end
16
19
  end
data/lib/jwt/algos.rb ADDED
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'rbnacl'
5
+ rescue LoadError
6
+ raise if defined?(RbNaCl)
7
+ end
8
+ require 'openssl'
9
+
10
+ require 'jwt/algos/hmac'
11
+ require 'jwt/algos/eddsa'
12
+ require 'jwt/algos/ecdsa'
13
+ require 'jwt/algos/rsa'
14
+ require 'jwt/algos/ps'
15
+ require 'jwt/algos/none'
16
+ require 'jwt/algos/unsupported'
17
+ require 'jwt/algos/algo_wrapper'
18
+
19
+ module JWT
20
+ module Algos
21
+ extend self
22
+
23
+ ALGOS = [Algos::Ecdsa,
24
+ Algos::Rsa,
25
+ Algos::Eddsa,
26
+ Algos::Ps,
27
+ Algos::None,
28
+ Algos::Unsupported].tap do |l|
29
+ if ::JWT.rbnacl_6_or_greater?
30
+ require_relative 'algos/hmac_rbnacl'
31
+ l.unshift(Algos::HmacRbNaCl)
32
+ elsif ::JWT.rbnacl?
33
+ require_relative 'algos/hmac_rbnacl_fixed'
34
+ l.unshift(Algos::HmacRbNaClFixed)
35
+ else
36
+ l.unshift(Algos::Hmac)
37
+ end
38
+ end.freeze
39
+
40
+ def find(algorithm)
41
+ indexed[algorithm && algorithm.downcase]
42
+ end
43
+
44
+ def create(algorithm)
45
+ Algos::AlgoWrapper.new(*find(algorithm))
46
+ end
47
+
48
+ def implementation?(algorithm)
49
+ (algorithm.respond_to?(:valid_alg?) && algorithm.respond_to?(:verify)) ||
50
+ (algorithm.respond_to?(:alg) && algorithm.respond_to?(:sign))
51
+ end
52
+
53
+ private
54
+
55
+ def indexed
56
+ @indexed ||= begin
57
+ fallback = [nil, Algos::Unsupported]
58
+ ALGOS.each_with_object(Hash.new(fallback)) do |cls, hash|
59
+ cls.const_get(:SUPPORTED).each do |alg|
60
+ hash[alg.downcase] = [alg, cls]
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,33 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative './error'
2
4
 
3
5
  module JWT
4
6
  class ClaimsValidator
5
- INTEGER_CLAIMS = %i[
7
+ NUMERIC_CLAIMS = %i[
6
8
  exp
7
9
  iat
8
10
  nbf
9
11
  ].freeze
10
12
 
11
13
  def initialize(payload)
12
- @payload = payload.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
14
+ @payload = payload.transform_keys(&:to_sym)
13
15
  end
14
16
 
15
17
  def validate!
16
- validate_int_claims
18
+ validate_numeric_claims
17
19
 
18
20
  true
19
21
  end
20
22
 
21
23
  private
22
24
 
23
- def validate_int_claims
24
- INTEGER_CLAIMS.each do |claim|
25
- validate_is_int(claim) if @payload.key?(claim)
25
+ def validate_numeric_claims
26
+ NUMERIC_CLAIMS.each do |claim|
27
+ validate_is_numeric(claim) if @payload.key?(claim)
26
28
  end
27
29
  end
28
30
 
29
- def validate_is_int(claim)
30
- raise InvalidPayload, "#{claim} claim must be an Integer but it is a #{@payload[claim].class}" unless @payload[claim].is_a?(Integer)
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}"
31
35
  end
32
36
  end
33
37
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'decode_configuration'
4
+ require_relative 'jwk_configuration'
5
+
6
+ module JWT
7
+ module Configuration
8
+ class Container
9
+ attr_accessor :decode, :jwk
10
+
11
+ def initialize
12
+ reset!
13
+ end
14
+
15
+ def reset!
16
+ @decode = DecodeConfiguration.new
17
+ @jwk = JwkConfiguration.new
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Configuration
5
+ class DecodeConfiguration
6
+ attr_accessor :verify_expiration,
7
+ :verify_not_before,
8
+ :verify_iss,
9
+ :verify_iat,
10
+ :verify_jti,
11
+ :verify_aud,
12
+ :verify_sub,
13
+ :leeway,
14
+ :algorithms,
15
+ :required_claims
16
+
17
+ def initialize
18
+ @verify_expiration = true
19
+ @verify_not_before = true
20
+ @verify_iss = false
21
+ @verify_iat = false
22
+ @verify_jti = false
23
+ @verify_aud = false
24
+ @verify_sub = false
25
+ @leeway = 0
26
+ @algorithms = ['HS256']
27
+ @required_claims = []
28
+ end
29
+
30
+ def to_h
31
+ {
32
+ verify_expiration: verify_expiration,
33
+ verify_not_before: verify_not_before,
34
+ verify_iss: verify_iss,
35
+ verify_iat: verify_iat,
36
+ verify_jti: verify_jti,
37
+ verify_aud: verify_aud,
38
+ verify_sub: verify_sub,
39
+ leeway: leeway,
40
+ algorithms: algorithms,
41
+ required_claims: required_claims
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../jwk/kid_as_key_digest'
4
+ require_relative '../jwk/thumbprint'
5
+
6
+ module JWT
7
+ module Configuration
8
+ class JwkConfiguration
9
+ def initialize
10
+ self.kid_generator_type = :key_digest
11
+ end
12
+
13
+ def kid_generator_type=(value)
14
+ self.kid_generator = case value
15
+ when :key_digest
16
+ JWT::JWK::KidAsKeyDigest
17
+ when :rfc7638_thumbprint
18
+ JWT::JWK::Thumbprint
19
+ else
20
+ raise ArgumentError, "#{value} is not a valid kid generator type."
21
+ end
22
+ end
23
+
24
+ attr_accessor :kid_generator
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'configuration/container'
4
+
5
+ module JWT
6
+ module Configuration
7
+ def configure
8
+ yield(configuration)
9
+ end
10
+
11
+ def configuration
12
+ @configuration ||= ::JWT::Configuration::Container.new
13
+ end
14
+ end
15
+ end