jwt 2.7.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +84 -27
  3. data/README.md +37 -19
  4. data/lib/jwt/base64.rb +16 -2
  5. data/lib/jwt/claims/audience.rb +20 -0
  6. data/lib/jwt/claims/expiration.rb +22 -0
  7. data/lib/jwt/claims/issued_at.rb +15 -0
  8. data/lib/jwt/claims/issuer.rb +24 -0
  9. data/lib/jwt/claims/jwt_id.rb +25 -0
  10. data/lib/jwt/claims/not_before.rb +22 -0
  11. data/lib/jwt/claims/numeric.rb +43 -0
  12. data/lib/jwt/claims/required.rb +23 -0
  13. data/lib/jwt/claims/subject.rb +20 -0
  14. data/lib/jwt/claims.rb +38 -0
  15. data/lib/jwt/configuration/container.rb +14 -3
  16. data/lib/jwt/configuration/jwk_configuration.rb +1 -1
  17. data/lib/jwt/decode.rb +12 -21
  18. data/lib/jwt/deprecations.rb +48 -0
  19. data/lib/jwt/encode.rb +4 -14
  20. data/lib/jwt/error.rb +1 -0
  21. data/lib/jwt/{algos → jwa}/ecdsa.rb +39 -26
  22. data/lib/jwt/jwa/eddsa.rb +34 -0
  23. data/lib/jwt/{algos → jwa}/hmac.rb +25 -19
  24. data/lib/jwt/jwa/hmac_rbnacl.rb +45 -0
  25. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +42 -0
  26. data/lib/jwt/jwa/none.rb +23 -0
  27. data/lib/jwt/jwa/ps.rb +36 -0
  28. data/lib/jwt/jwa/rsa.rb +36 -0
  29. data/lib/jwt/jwa/signing_algorithm.rb +59 -0
  30. data/lib/jwt/jwa/unsupported.rb +19 -0
  31. data/lib/jwt/jwa/wrapper.rb +43 -0
  32. data/lib/jwt/jwa.rb +45 -0
  33. data/lib/jwt/jwk/ec.rb +42 -27
  34. data/lib/jwt/jwk/key_base.rb +3 -1
  35. data/lib/jwt/jwk/key_finder.rb +4 -4
  36. data/lib/jwt/jwk/set.rb +1 -1
  37. data/lib/jwt/jwk.rb +1 -1
  38. data/lib/jwt/version.rb +4 -3
  39. data/lib/jwt/x5c_key_finder.rb +2 -5
  40. data/lib/jwt.rb +5 -1
  41. data/ruby-jwt.gemspec +3 -0
  42. metadata +58 -20
  43. data/lib/jwt/algos/algo_wrapper.rb +0 -26
  44. data/lib/jwt/algos/eddsa.rb +0 -33
  45. data/lib/jwt/algos/hmac_rbnacl.rb +0 -53
  46. data/lib/jwt/algos/hmac_rbnacl_fixed.rb +0 -52
  47. data/lib/jwt/algos/none.rb +0 -19
  48. data/lib/jwt/algos/ps.rb +0 -41
  49. data/lib/jwt/algos/rsa.rb +0 -23
  50. data/lib/jwt/algos/unsupported.rb +0 -19
  51. data/lib/jwt/algos.rb +0 -66
  52. data/lib/jwt/claims_validator.rb +0 -37
  53. data/lib/jwt/verify.rb +0 -113
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
@@ -10,7 +8,7 @@ module JWT
10
8
  # Decoding logic for JWT
11
9
  class Decode
12
10
  def initialize(jwt, key, verify, options, &keyfinder)
13
- raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
11
+ raise JWT::DecodeError, 'Nil JSON web token' unless jwt
14
12
 
15
13
  @jwt = jwt
16
14
  @key = key
@@ -30,7 +28,7 @@ module JWT
30
28
  verify_signature
31
29
  verify_claims
32
30
  end
33
- raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
31
+ raise JWT::DecodeError, 'Not enough or too many segments' unless header && payload
34
32
 
35
33
  [payload, header]
36
34
  end
@@ -46,21 +44,21 @@ module JWT
46
44
 
47
45
  return if Array(@key).any? { |key| verify_signature_for?(key) }
48
46
 
49
- raise(JWT::VerificationError, 'Signature verification failed')
47
+ raise JWT::VerificationError, 'Signature verification failed'
50
48
  end
51
49
 
52
50
  def verify_algo
53
- raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms.empty?
54
- raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless alg_in_header
55
- raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') if allowed_and_valid_algorithms.empty?
51
+ raise JWT::IncorrectAlgorithm, 'An algorithm must be specified' if allowed_algorithms.empty?
52
+ raise JWT::IncorrectAlgorithm, 'Token is missing alg header' unless alg_in_header
53
+ raise JWT::IncorrectAlgorithm, 'Expected a different algorithm' if allowed_and_valid_algorithms.empty?
56
54
  end
57
55
 
58
56
  def set_key
59
57
  @key = find_key(&@keyfinder) if @keyfinder
60
58
  @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(header['kid']) if @options[:jwks]
61
- if (x5c_options = @options[:x5c])
62
- @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
63
- end
59
+ return unless (x5c_options = @options[:x5c])
60
+
61
+ @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
64
62
  end
65
63
 
66
64
  def verify_signature_for?(key)
@@ -92,13 +90,7 @@ module JWT
92
90
  end
93
91
 
94
92
  def resolve_allowed_algorithms
95
- algs = given_algorithms.map do |alg|
96
- if Algos.implementation?(alg)
97
- alg
98
- else
99
- Algos.create(alg)
100
- end
101
- end
93
+ algs = given_algorithms.map { |alg| JWA.resolve(alg) }
102
94
 
103
95
  sort_by_alg_header(algs)
104
96
  end
@@ -119,8 +111,7 @@ module JWT
119
111
  end
120
112
 
121
113
  def verify_claims
122
- Verify.verify_claims(payload, @options)
123
- Verify.verify_required_claims(payload, @options)
114
+ Claims.verify!(payload, @options)
124
115
  end
125
116
 
126
117
  def validate_segment_count!
@@ -128,7 +119,7 @@ module JWT
128
119
  return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
129
120
  return if segment_length == 2 && none_algorithm?
130
121
 
131
- raise(JWT::DecodeError, 'Not enough or too many segments')
122
+ raise JWT::DecodeError, 'Not enough or too many segments'
132
123
  end
133
124
 
134
125
  def segment_length
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ # Deprecations module to handle deprecation warnings in the gem
5
+ module Deprecations
6
+ class << self
7
+ def context
8
+ yield.tap { emit_warnings }
9
+ ensure
10
+ Thread.current[:jwt_warning_store] = nil
11
+ end
12
+
13
+ def warning(message, only_if_valid: false)
14
+ method_name = only_if_valid ? :store : :warn
15
+ case JWT.configuration.deprecation_warnings
16
+ when :once
17
+ return if record_warned(message)
18
+ when :warn
19
+ # noop
20
+ else
21
+ return
22
+ end
23
+
24
+ send(method_name, "[DEPRECATION WARNING] #{message}")
25
+ end
26
+
27
+ def store(message)
28
+ (Thread.current[:jwt_warning_store] ||= []) << message
29
+ end
30
+
31
+ def emit_warnings
32
+ return if Thread.current[:jwt_warning_store].nil?
33
+
34
+ Thread.current[:jwt_warning_store].each { |warning| warn(warning) }
35
+ end
36
+
37
+ private
38
+
39
+ def record_warned(message)
40
+ @warned ||= []
41
+ return true if @warned.include?(message)
42
+
43
+ @warned << message
44
+ false
45
+ end
46
+ end
47
+ end
48
+ end
data/lib/jwt/encode.rb CHANGED
@@ -1,20 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'algos'
4
- require_relative 'claims_validator'
3
+ require_relative 'jwa'
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 = resolve_algorithm(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
@@ -24,12 +20,6 @@ module JWT
24
20
 
25
21
  private
26
22
 
27
- def resolve_algorithm(algorithm)
28
- return algorithm if Algos.implementation?(algorithm)
29
-
30
- Algos.create(algorithm)
31
- end
32
-
33
23
  def encoded_header
34
24
  @encoded_header ||= encode_header
35
25
  end
@@ -47,7 +37,7 @@ module JWT
47
37
  end
48
38
 
49
39
  def encode_header
50
- encode_data(@headers)
40
+ encode_data(@headers.merge(@algorithm.header(signing_key: @key)))
51
41
  end
52
42
 
53
43
  def encode_payload
@@ -61,7 +51,7 @@ module JWT
61
51
  def validate_claims!
62
52
  return unless @payload.is_a?(Hash)
63
53
 
64
- ClaimsValidator.new(@payload).validate!
54
+ Claims::Numeric.new(@payload).verify!
65
55
  end
66
56
 
67
57
  def encode_signature
data/lib/jwt/error.rb CHANGED
@@ -17,6 +17,7 @@ module JWT
17
17
  class InvalidJtiError < DecodeError; end
18
18
  class InvalidPayload < DecodeError; end
19
19
  class MissingRequiredClaim < DecodeError; end
20
+ class Base64DecodeError < DecodeError; end
20
21
 
21
22
  class JWKError < DecodeError; end
22
23
  end
@@ -1,9 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
5
- module Ecdsa
6
- module_function
4
+ module JWA
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)
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ class Eddsa
6
+ include JWT::JWA::SigningAlgorithm
7
+
8
+ def initialize(alg)
9
+ @alg = alg
10
+ end
11
+
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")
15
+ end
16
+
17
+ signing_key.sign(data)
18
+ end
19
+
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")
23
+ end
24
+
25
+ verification_key.verify(signature, data)
26
+ rescue RbNaCl::CryptoError
27
+ false
28
+ end
29
+
30
+ register_algorithm(new('ED25519'))
31
+ register_algorithm(new('EdDSA'))
32
+ end
33
+ end
34
+ end
@@ -1,36 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
5
- module Hmac
6
- module_function
4
+ module JWA
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
@@ -44,6 +48,7 @@ module JWT
44
48
  OpenSSL.fixed_length_secure_compare(a, b)
45
49
  end
46
50
  else
51
+ # :nocov:
47
52
  def fixed_length_secure_compare(a, b)
48
53
  raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
49
54
 
@@ -53,6 +58,7 @@ module JWT
53
58
  b.each_byte { |byte| res |= byte ^ l.shift }
54
59
  res == 0
55
60
  end
61
+ # :nocov:
56
62
  end
57
63
  module_function :fixed_length_secure_compare
58
64
 
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
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')
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
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*')
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ class None
6
+ include JWT::JWA::SigningAlgorithm
7
+
8
+ def initialize
9
+ @alg = 'none'
10
+ end
11
+
12
+ def sign(*)
13
+ ''
14
+ end
15
+
16
+ def verify(*)
17
+ true
18
+ end
19
+
20
+ register_algorithm(new)
21
+ end
22
+ end
23
+ end
data/lib/jwt/jwa/ps.rb ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ class Ps
6
+ include JWT::JWA::SigningAlgorithm
7
+
8
+ def initialize(alg)
9
+ @alg = alg
10
+ @digest_algorithm = alg.sub('PS', 'sha')
11
+ end
12
+
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.")
16
+ end
17
+
18
+ signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm)
19
+ end
20
+
21
+ def verify(data:, signature:, verification_key:)
22
+ verification_key.verify_pss(digest_algorithm, signature, data, salt_length: :auto, mgf1_hash: digest_algorithm)
23
+ rescue OpenSSL::PKey::PKeyError
24
+ raise JWT::VerificationError, 'Signature verification raised'
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
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ class Rsa
6
+ include JWT::JWA::SigningAlgorithm
7
+
8
+ def initialize(alg)
9
+ @alg = alg
10
+ @digest = OpenSSL::Digest.new(alg.sub('RS', 'SHA'))
11
+ end
12
+
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")
16
+ end
17
+
18
+ signing_key.sign(digest, data)
19
+ end
20
+
21
+ def verify(data:, signature:, verification_key:)
22
+ verification_key.verify(digest, signature, data)
23
+ rescue OpenSSL::PKey::PKeyError
24
+ raise JWT::VerificationError, 'Signature verification raised'
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
34
+ end
35
+ end
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
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ module Unsupported
6
+ class << self
7
+ include JWT::JWA::SigningAlgorithm
8
+
9
+ def sign(*)
10
+ raise_sign_error!('Unsupported signing method')
11
+ end
12
+
13
+ def verify(*)
14
+ raise JWT::VerificationError, 'Algorithm not supported'
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end