jwt 2.8.1 → 2.9.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.
data/lib/jwt/jwa/eddsa.rb CHANGED
@@ -2,41 +2,33 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
- module Eddsa
6
- SUPPORTED = %w[ED25519 EdDSA].freeze
7
- SUPPORTED_DOWNCASED = SUPPORTED.map(&:downcase).freeze
5
+ class Eddsa
6
+ include JWT::JWA::SigningAlgorithm
8
7
 
9
- class << self
10
- def sign(algorithm, msg, key)
11
- unless key.is_a?(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
-
15
- validate_algorithm!(algorithm)
8
+ def initialize(alg)
9
+ @alg = alg
10
+ end
16
11
 
17
- key.sign(msg)
12
+ def sign(data:, signing_key:)
13
+ unless signing_key.is_a?(RbNaCl::Signatures::Ed25519::SigningKey)
14
+ raise_encode_error!("Key given is a #{signing_key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey")
18
15
  end
19
16
 
20
- def verify(algorithm, public_key, signing_input, signature)
21
- unless public_key.is_a?(RbNaCl::Signatures::Ed25519::VerifyKey)
22
- raise DecodeError, "key given is a #{public_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey"
23
- end
24
-
25
- validate_algorithm!(algorithm)
17
+ signing_key.sign(data)
18
+ end
26
19
 
27
- public_key.verify(signature, signing_input)
28
- rescue RbNaCl::CryptoError
29
- false
20
+ def verify(data:, signature:, verification_key:)
21
+ unless verification_key.is_a?(RbNaCl::Signatures::Ed25519::VerifyKey)
22
+ raise_decode_error!("key given is a #{verification_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey")
30
23
  end
31
24
 
32
- private
33
-
34
- def validate_algorithm!(algorithm)
35
- return if SUPPORTED_DOWNCASED.include?(algorithm.downcase)
36
-
37
- raise IncorrectAlgorithm, "Algorithm #{algorithm} not supported. Supported algoritms are #{SUPPORTED.join(', ')}"
38
- end
25
+ verification_key.verify(signature, data)
26
+ rescue RbNaCl::CryptoError
27
+ false
39
28
  end
29
+
30
+ register_algorithm(new('ED25519'))
31
+ register_algorithm(new('EdDSA'))
40
32
  end
41
33
  end
42
34
  end
data/lib/jwt/jwa/hmac.rb CHANGED
@@ -2,35 +2,39 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
- module Hmac
6
- module_function
5
+ class Hmac
6
+ include JWT::JWA::SigningAlgorithm
7
7
 
8
- MAPPING = {
9
- 'HS256' => OpenSSL::Digest::SHA256,
10
- 'HS384' => OpenSSL::Digest::SHA384,
11
- 'HS512' => OpenSSL::Digest::SHA512
12
- }.freeze
13
-
14
- SUPPORTED = MAPPING.keys
15
-
16
- def sign(algorithm, msg, key)
17
- key ||= ''
8
+ def initialize(alg, digest)
9
+ @alg = alg
10
+ @digest = digest
11
+ end
18
12
 
19
- raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
13
+ def sign(data:, signing_key:)
14
+ signing_key ||= ''
15
+ raise_verify_error!('HMAC key expected to be a String') unless signing_key.is_a?(String)
20
16
 
21
- OpenSSL::HMAC.digest(MAPPING[algorithm].new, key, msg)
17
+ OpenSSL::HMAC.digest(digest.new, signing_key, data)
22
18
  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'
19
+ if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure'
20
+ raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret')
25
21
  end
26
22
 
27
23
  raise e
28
24
  end
29
25
 
30
- def verify(algorithm, key, signing_input, signature)
31
- SecurityUtils.secure_compare(signature, sign(algorithm, signing_input, key))
26
+ def verify(data:, signature:, verification_key:)
27
+ SecurityUtils.secure_compare(signature, sign(data: data, signing_key: verification_key))
32
28
  end
33
29
 
30
+ register_algorithm(new('HS256', OpenSSL::Digest::SHA256))
31
+ register_algorithm(new('HS384', OpenSSL::Digest::SHA384))
32
+ register_algorithm(new('HS512', OpenSSL::Digest::SHA512))
33
+
34
+ private
35
+
36
+ attr_reader :digest
37
+
34
38
  # Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb
35
39
  # rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
36
40
  module SecurityUtils
@@ -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