jwt 2.8.2 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5fc1b13d678680a3d1b23e06cad091deb0f3ffff77783a99cd1d1f7231938563
4
- data.tar.gz: f7d3f5294cb3b6ba57c2772a58c50ee2d55cf6412db6b707a38c74b381662777
3
+ metadata.gz: 0f3d3ea752a6b4dee8f1b020a3e0af95e003f74a8c40f730faab73b616fc9e98
4
+ data.tar.gz: 8dcdb470771f56931485824c32739bfcec1ce310b7096d6cd28c656d0f8c21fd
5
5
  SHA512:
6
- metadata.gz: 5f4602ed313d982db0a2e469c2fc3b58aa0de226f85e332501628d9f7b59ed8a84e78691b57f14ddf153c4be028e9b3caa13d9b3231996c798e9e63f69f4d10a
7
- data.tar.gz: a3ed20caccfe2c2979426b41a6e0bbeeabe67157643c4571836642f48ccf76ba8f58bb3270501eeefcbd53e1d027de1b558310a083360ca644157934ce3c06bf
6
+ metadata.gz: c68cccb66154a824751df6e7cd4d0b9d31372c0bada498097770feb8992ac3c7a462cd11eb759171d096e8d957b2a67b088fb35dc79fe7b4dd6eac6b25140d63
7
+ data.tar.gz: 03fe234584cce6458fb1a0f64cb399f81bc3c7aee40e25c011f0c27484ede3c6c6a6d950a9c5f3f13e4d66db5a76de98f2801942762df514097b2f663bf79247
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.9.0](https://github.com/jwt/ruby-jwt/tree/v2.9.0) (NEXT)
4
+
5
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.2...v2.9.0)
6
+
7
+ **Features:**
8
+
9
+ - Build and push gem using a GH action [#612](https://github.com/jwt/ruby-jwt/pull/612) ([@anakinj](https://github.com/anakinj))
10
+
11
+ **Fixes and enhancements:**
12
+
13
+ - Refactor claim validators into their own classes [#605](https://github.com/jwt/ruby-jwt/pull/605) ([@anakinj](https://github.com/anakinj), [@MatteoPierro](https://github.com/MatteoPierro))
14
+ - Allow extending available algorithms [#607](https://github.com/jwt/ruby-jwt/pull/607) ([@anakinj](https://github.com/anakinj))
15
+ - Do not include the EdDSA algorithm if rbnacl not available [#613](https://github.com/jwt/ruby-jwt/pull/613) ([@anakinj](https://github.com/anakinj))
16
+
3
17
  ## [v2.8.2](https://github.com/jwt/ruby-jwt/tree/v2.8.2) (2024-06-18)
4
18
 
5
19
  [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.1...v2.8.2)
data/README.md CHANGED
@@ -223,18 +223,22 @@ puts decoded_token
223
223
 
224
224
  ### **Custom algorithms**
225
225
 
226
- An object implementing custom signing or verification behaviour can be passed in the `algorithm` option when encoding and decoding. The given object needs to implement the method `valid_alg?` and `verify` and/or `alg` and `sign`, depending if object is used for encoding or decoding.
226
+ When encoding or decoding a token, you can pass in a custom object through the `algorithm` option to handle signing or verification. This custom object must include or extend the `JWT::JWA::SigningAlgorithm` module and implement certain methods:
227
+
228
+ - For decoding/verifying: The object must implement the methods `alg` and `verify`.
229
+ - For encoding/signing: The object must implement the methods `alg` and `sign`.
230
+
231
+ For customization options check the details from `JWT::JWA::SigningAlgorithm`.
232
+
227
233
 
228
234
  ```ruby
229
235
  module CustomHS512Algorithm
236
+ extend JWT::JWA::SigningAlgorithm
237
+
230
238
  def self.alg
231
239
  'HS512'
232
240
  end
233
241
 
234
- def self.valid_alg?(alg_to_validate)
235
- alg_to_validate == alg
236
- end
237
-
238
242
  def self.sign(data:, signing_key:)
239
243
  OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), data, signing_key)
240
244
  end
@@ -690,21 +694,25 @@ jwk_hash = jwk.export
690
694
  thumbprint_as_the_kid = jwk_hash[:kid]
691
695
  ```
692
696
 
693
- # Development and Tests
697
+ # Development and testing
694
698
 
695
- We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec and performing releases to rubygems.org, which can be done with
699
+ The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
696
700
 
697
701
  ```bash
698
- rake release
702
+ bundle install
703
+ bundle exec appraisal rake test
699
704
  ```
700
705
 
701
- The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
706
+ # Releasing
707
+
708
+ To cut a new release adjust the [version.rb](lib/jwt/version.rb) and [CHANGELOG](CHANGELOG.md) with desired version numbers and dates and commit the changes. Tag the release with the version number using the following command:
702
709
 
703
710
  ```bash
704
- bundle install
705
- bundle exec appraisal rake test
711
+ rake release:source_control_push
706
712
  ```
707
713
 
714
+ This will tag a new version an trigger a [GitHub action](.github/workflows/push_gem.yml) that eventually will push the gem to rubygems.org.
715
+
708
716
  ## How to contribute
709
717
  See [CONTRIBUTING](CONTRIBUTING.md).
710
718
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Claims
5
+ class Audience
6
+ def initialize(expected_audience:)
7
+ @expected_audience = expected_audience
8
+ end
9
+
10
+ def verify!(context:, **_args)
11
+ aud = context.payload['aud']
12
+ raise JWT::InvalidAudError, "Invalid audience. Expected #{expected_audience}, received #{aud || '<none>'}" if ([*aud] & [*expected_audience]).empty?
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :expected_audience
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Claims
5
+ class Expiration
6
+ def initialize(leeway:)
7
+ @leeway = leeway || 0
8
+ end
9
+
10
+ def verify!(context:, **_args)
11
+ return unless context.payload.is_a?(Hash)
12
+ return unless context.payload.key?('exp')
13
+
14
+ raise JWT::ExpiredSignature, 'Signature has expired' if context.payload['exp'].to_i <= (Time.now.to_i - leeway)
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :leeway
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Claims
5
+ class IssuedAt
6
+ def verify!(context:, **_args)
7
+ return unless context.payload.is_a?(Hash)
8
+ return unless context.payload.key?('iat')
9
+
10
+ iat = context.payload['iat']
11
+ raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(::Numeric) || iat.to_f > Time.now.to_f
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Claims
5
+ class Issuer
6
+ def initialize(issuers:)
7
+ @issuers = Array(issuers).map { |item| item.is_a?(Symbol) ? item.to_s : item }
8
+ end
9
+
10
+ def verify!(context:, **_args)
11
+ case (iss = context.payload['iss'])
12
+ when *issuers
13
+ nil
14
+ else
15
+ raise JWT::InvalidIssuerError, "Invalid issuer. Expected #{issuers}, received #{iss || '<none>'}"
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :issuers
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Claims
5
+ class JwtId
6
+ def initialize(validator:)
7
+ @validator = validator
8
+ end
9
+
10
+ def verify!(context:, **_args)
11
+ jti = context.payload['jti']
12
+ if validator.respond_to?(:call)
13
+ verified = validator.arity == 2 ? validator.call(jti, context.payload) : validator.call(jti)
14
+ raise(JWT::InvalidJtiError, 'Invalid jti') unless verified
15
+ elsif jti.to_s.strip.empty?
16
+ raise(JWT::InvalidJtiError, 'Missing jti')
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :validator
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Claims
5
+ class NotBefore
6
+ def initialize(leeway:)
7
+ @leeway = leeway || 0
8
+ end
9
+
10
+ def verify!(context:, **_args)
11
+ return unless context.payload.is_a?(Hash)
12
+ return unless context.payload.key?('nbf')
13
+
14
+ raise JWT::ImmatureSignature, 'Signature nbf has not been reached' if context.payload['nbf'].to_i > (Time.now.to_i + leeway)
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :leeway
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Claims
5
+ class Numeric
6
+ def self.verify!(payload:, **_args)
7
+ return unless payload.is_a?(Hash)
8
+
9
+ new(payload).verify!
10
+ end
11
+
12
+ NUMERIC_CLAIMS = %i[
13
+ exp
14
+ iat
15
+ nbf
16
+ ].freeze
17
+
18
+ def initialize(payload)
19
+ @payload = payload.transform_keys(&:to_sym)
20
+ end
21
+
22
+ def verify!
23
+ validate_numeric_claims
24
+
25
+ true
26
+ end
27
+
28
+ private
29
+
30
+ def validate_numeric_claims
31
+ NUMERIC_CLAIMS.each do |claim|
32
+ validate_is_numeric(claim) if @payload.key?(claim)
33
+ end
34
+ end
35
+
36
+ def validate_is_numeric(claim)
37
+ return if @payload[claim].is_a?(::Numeric)
38
+
39
+ raise InvalidPayload, "#{claim} claim must be a Numeric value but it is a #{@payload[claim].class}"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Claims
5
+ class Required
6
+ def initialize(required_claims:)
7
+ @required_claims = required_claims
8
+ end
9
+
10
+ def verify!(context:, **_args)
11
+ required_claims.each do |required_claim|
12
+ next if context.payload.is_a?(Hash) && context.payload.key?(required_claim)
13
+
14
+ raise JWT::MissingRequiredClaim, "Missing required claim #{required_claim}"
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :required_claims
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Claims
5
+ class Subject
6
+ def initialize(expected_subject:)
7
+ @expected_subject = expected_subject.to_s
8
+ end
9
+
10
+ def verify!(context:, **_args)
11
+ sub = context.payload['sub']
12
+ raise(JWT::InvalidSubError, "Invalid subject. Expected #{expected_subject}, received #{sub || '<none>'}") unless sub.to_s == expected_subject
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :expected_subject
18
+ end
19
+ end
20
+ end
data/lib/jwt/claims.rb ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'claims/audience'
4
+ require_relative 'claims/expiration'
5
+ require_relative 'claims/issued_at'
6
+ require_relative 'claims/issuer'
7
+ require_relative 'claims/jwt_id'
8
+ require_relative 'claims/not_before'
9
+ require_relative 'claims/numeric'
10
+ require_relative 'claims/required'
11
+ require_relative 'claims/subject'
12
+
13
+ module JWT
14
+ module Claims
15
+ VerificationContext = Struct.new(:payload, keyword_init: true)
16
+
17
+ VERIFIERS = {
18
+ verify_expiration: ->(options) { Claims::Expiration.new(leeway: options[:exp_leeway] || options[:leeway]) },
19
+ verify_not_before: ->(options) { Claims::NotBefore.new(leeway: options[:nbf_leeway] || options[:leeway]) },
20
+ verify_iss: ->(options) { Claims::Issuer.new(issuers: options[:iss]) },
21
+ verify_iat: ->(*) { Claims::IssuedAt.new },
22
+ verify_jti: ->(options) { Claims::JwtId.new(validator: options[:verify_jti]) },
23
+ verify_aud: ->(options) { Claims::Audience.new(expected_audience: options[:aud]) },
24
+ verify_sub: ->(options) { options[:sub] && Claims::Subject.new(expected_subject: options[:sub]) },
25
+ required_claims: ->(options) { Claims::Required.new(required_claims: options[:required_claims]) }
26
+ }.freeze
27
+
28
+ class << self
29
+ def verify!(payload, options)
30
+ VERIFIERS.each do |key, verifier_builder|
31
+ next unless options[key]
32
+
33
+ verifier_builder&.call(options)&.verify!(context: VerificationContext.new(payload: payload))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/jwt/decode.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
-
5
- require 'jwt/verify'
6
4
  require 'jwt/x5c_key_finder'
7
5
 
8
6
  # JWT::Decode module
@@ -92,7 +90,7 @@ module JWT
92
90
  end
93
91
 
94
92
  def resolve_allowed_algorithms
95
- algs = given_algorithms.map { |alg| JWA.create(alg) }
93
+ algs = given_algorithms.map { |alg| JWA.resolve(alg) }
96
94
 
97
95
  sort_by_alg_header(algs)
98
96
  end
@@ -113,8 +111,7 @@ module JWT
113
111
  end
114
112
 
115
113
  def verify_claims
116
- Verify.verify_claims(payload, @options)
117
- Verify.verify_required_claims(payload, @options)
114
+ Claims.verify!(payload, @options)
118
115
  end
119
116
 
120
117
  def validate_segment_count!
data/lib/jwt/encode.rb CHANGED
@@ -1,20 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'jwa'
4
- require_relative 'claims_validator'
5
4
 
6
5
  # JWT::Encode module
7
6
  module JWT
8
7
  # Encoding logic for JWT
9
8
  class Encode
10
- ALG_KEY = 'alg'
11
-
12
9
  def initialize(options)
13
10
  @payload = options[:payload]
14
11
  @key = options[:key]
15
- @algorithm = JWA.create(options[:algorithm])
12
+ @algorithm = JWA.resolve(options[:algorithm])
16
13
  @headers = options[:headers].transform_keys(&:to_s)
17
- @headers[ALG_KEY] = @algorithm.alg
18
14
  end
19
15
 
20
16
  def segments
@@ -41,7 +37,7 @@ module JWT
41
37
  end
42
38
 
43
39
  def encode_header
44
- encode_data(@headers)
40
+ encode_data(@headers.merge(@algorithm.header(signing_key: @key)))
45
41
  end
46
42
 
47
43
  def encode_payload
@@ -55,7 +51,7 @@ module JWT
55
51
  def validate_claims!
56
52
  return unless @payload.is_a?(Hash)
57
53
 
58
- ClaimsValidator.new(@payload).validate!
54
+ Claims::Numeric.new(@payload).verify!
59
55
  end
60
56
 
61
57
  def encode_signature
data/lib/jwt/jwa/ecdsa.rb CHANGED
@@ -2,8 +2,35 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
- module Ecdsa
6
- module_function
5
+ class Ecdsa
6
+ include JWT::JWA::SigningAlgorithm
7
+
8
+ def initialize(alg, digest)
9
+ @alg = alg
10
+ @digest = OpenSSL::Digest.new(digest)
11
+ end
12
+
13
+ def sign(data:, signing_key:)
14
+ curve_definition = curve_by_name(signing_key.group.curve_name)
15
+ key_algorithm = curve_definition[:algorithm]
16
+ if alg != key_algorithm
17
+ raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided"
18
+ end
19
+
20
+ asn1_to_raw(signing_key.dsa_sign_asn1(digest.digest(data)), signing_key)
21
+ end
22
+
23
+ def verify(data:, signature:, verification_key:)
24
+ curve_definition = curve_by_name(verification_key.group.curve_name)
25
+ key_algorithm = curve_definition[:algorithm]
26
+ if alg != key_algorithm
27
+ raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided"
28
+ end
29
+
30
+ verification_key.dsa_verify_asn1(digest.digest(data), raw_to_asn1(signature, verification_key))
31
+ rescue OpenSSL::PKey::PKeyError
32
+ raise JWT::VerificationError, 'Signature verification raised'
33
+ end
7
34
 
8
35
  NAMED_CURVES = {
9
36
  'prime256v1' => {
@@ -28,36 +55,22 @@ module JWT
28
55
  }
29
56
  }.freeze
30
57
 
31
- SUPPORTED = NAMED_CURVES.map { |_, c| c[:algorithm] }.uniq.freeze
58
+ NAMED_CURVES.each_value do |v|
59
+ register_algorithm(new(v[:algorithm], v[:digest]))
60
+ end
32
61
 
33
- def sign(algorithm, msg, key)
34
- curve_definition = curve_by_name(key.group.curve_name)
35
- key_algorithm = curve_definition[:algorithm]
36
- if algorithm != key_algorithm
37
- raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
62
+ def self.curve_by_name(name)
63
+ NAMED_CURVES.fetch(name) do
64
+ raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
38
65
  end
39
-
40
- digest = OpenSSL::Digest.new(curve_definition[:digest])
41
- asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
42
66
  end
43
67
 
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]
47
- if algorithm != key_algorithm
48
- raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
49
- end
68
+ private
50
69
 
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
70
+ attr_reader :digest
56
71
 
57
72
  def curve_by_name(name)
58
- NAMED_CURVES.fetch(name) do
59
- raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
60
- end
73
+ self.class.curve_by_name(name)
61
74
  end
62
75
 
63
76
  def raw_to_asn1(signature, private_key)
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