jwt 2.7.1 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
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