jwt 2.5.0 → 2.8.2

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +101 -23
  3. data/CONTRIBUTING.md +7 -7
  4. data/README.md +125 -47
  5. data/lib/jwt/base64.rb +16 -2
  6. data/lib/jwt/claims_validator.rb +1 -1
  7. data/lib/jwt/configuration/container.rb +14 -3
  8. data/lib/jwt/configuration/jwk_configuration.rb +1 -1
  9. data/lib/jwt/decode.rb +49 -32
  10. data/lib/jwt/deprecations.rb +48 -0
  11. data/lib/jwt/encode.rb +23 -19
  12. data/lib/jwt/error.rb +1 -0
  13. data/lib/jwt/{algos → jwa}/ecdsa.rb +19 -7
  14. data/lib/jwt/jwa/eddsa.rb +42 -0
  15. data/lib/jwt/jwa/hmac.rb +75 -0
  16. data/lib/jwt/jwa/hmac_rbnacl.rb +50 -0
  17. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +46 -0
  18. data/lib/jwt/{algos → jwa}/none.rb +4 -2
  19. data/lib/jwt/jwa/ps.rb +30 -0
  20. data/lib/jwt/jwa/rsa.rb +25 -0
  21. data/lib/jwt/{algos → jwa}/unsupported.rb +1 -1
  22. data/lib/jwt/jwa/wrapper.rb +26 -0
  23. data/lib/jwt/jwa.rb +62 -0
  24. data/lib/jwt/jwk/ec.rb +168 -116
  25. data/lib/jwt/jwk/hmac.rb +64 -28
  26. data/lib/jwt/jwk/key_base.rb +33 -11
  27. data/lib/jwt/jwk/key_finder.rb +19 -35
  28. data/lib/jwt/jwk/okp_rbnacl.rb +110 -0
  29. data/lib/jwt/jwk/rsa.rb +142 -77
  30. data/lib/jwt/jwk/set.rb +80 -0
  31. data/lib/jwt/jwk.rb +14 -11
  32. data/lib/jwt/verify.rb +16 -12
  33. data/lib/jwt/version.rb +20 -3
  34. data/lib/jwt/x5c_key_finder.rb +2 -5
  35. data/lib/jwt.rb +4 -1
  36. data/ruby-jwt.gemspec +11 -4
  37. metadata +35 -27
  38. data/.codeclimate.yml +0 -8
  39. data/.github/workflows/coverage.yml +0 -27
  40. data/.github/workflows/test.yml +0 -67
  41. data/.gitignore +0 -13
  42. data/.reek.yml +0 -22
  43. data/.rspec +0 -2
  44. data/.rubocop.yml +0 -67
  45. data/.sourcelevel.yml +0 -17
  46. data/Appraisals +0 -13
  47. data/Gemfile +0 -7
  48. data/Rakefile +0 -16
  49. data/lib/jwt/algos/eddsa.rb +0 -35
  50. data/lib/jwt/algos/hmac.rb +0 -36
  51. data/lib/jwt/algos/ps.rb +0 -43
  52. data/lib/jwt/algos/rsa.rb +0 -22
  53. data/lib/jwt/algos.rb +0 -44
  54. data/lib/jwt/security_utils.rb +0 -59
  55. data/lib/jwt/signature.rb +0 -35
data/lib/jwt/decode.rb CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  require 'json'
4
4
 
5
- require 'jwt/signature'
6
5
  require 'jwt/verify'
7
6
  require 'jwt/x5c_key_finder'
7
+
8
8
  # JWT::Decode module
9
9
  module JWT
10
10
  # Decoding logic for JWT
11
11
  class Decode
12
12
  def initialize(jwt, key, verify, options, &keyfinder)
13
- raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
13
+ raise JWT::DecodeError, 'Nil JSON web token' unless jwt
14
14
 
15
15
  @jwt = jwt
16
16
  @key = key
@@ -24,13 +24,13 @@ module JWT
24
24
  def decode_segments
25
25
  validate_segment_count!
26
26
  if @verify
27
- decode_crypto
27
+ decode_signature
28
28
  verify_algo
29
29
  set_key
30
30
  verify_signature
31
31
  verify_claims
32
32
  end
33
- raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
33
+ raise JWT::DecodeError, 'Not enough or too many segments' unless header && payload
34
34
 
35
35
  [payload, header]
36
36
  end
@@ -46,45 +46,62 @@ module JWT
46
46
 
47
47
  return if Array(@key).any? { |key| verify_signature_for?(key) }
48
48
 
49
- raise(JWT::VerificationError, 'Signature verification failed')
49
+ raise JWT::VerificationError, 'Signature verification failed'
50
50
  end
51
51
 
52
52
  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 algorithm
55
- raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless options_includes_algo_in_header?
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?
56
56
  end
57
57
 
58
58
  def set_key
59
59
  @key = find_key(&@keyfinder) if @keyfinder
60
- @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks]).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
60
+ @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(header['kid']) if @options[:jwks]
61
+ return unless (x5c_options = @options[:x5c])
62
+
63
+ @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
64
64
  end
65
65
 
66
66
  def verify_signature_for?(key)
67
- Signature.verify(algorithm, key, signing_input, @signature)
67
+ allowed_and_valid_algorithms.any? do |alg|
68
+ alg.verify(data: signing_input, signature: @signature, verification_key: key)
69
+ end
68
70
  end
69
71
 
70
- def options_includes_algo_in_header?
71
- allowed_algorithms.any? { |alg| alg.casecmp(algorithm).zero? }
72
+ def allowed_and_valid_algorithms
73
+ @allowed_and_valid_algorithms ||= allowed_algorithms.select { |alg| alg.valid_alg?(alg_in_header) }
72
74
  end
73
75
 
74
- def allowed_algorithms
75
- # Order is very important - first check for string keys, next for symbols
76
- algos = if @options.key?('algorithm')
77
- @options['algorithm']
78
- elsif @options.key?(:algorithm)
79
- @options[:algorithm]
80
- elsif @options.key?('algorithms')
81
- @options['algorithms']
82
- elsif @options.key?(:algorithms)
83
- @options[:algorithms]
84
- else
85
- []
76
+ # Order is very important - first check for string keys, next for symbols
77
+ ALGORITHM_KEYS = ['algorithm',
78
+ :algorithm,
79
+ 'algorithms',
80
+ :algorithms].freeze
81
+
82
+ def given_algorithms
83
+ ALGORITHM_KEYS.each do |alg_key|
84
+ alg = @options[alg_key]
85
+ return Array(alg) if alg
86
86
  end
87
- Array(algos)
87
+ []
88
+ end
89
+
90
+ def allowed_algorithms
91
+ @allowed_algorithms ||= resolve_allowed_algorithms
92
+ end
93
+
94
+ def resolve_allowed_algorithms
95
+ algs = given_algorithms.map { |alg| JWA.create(alg) }
96
+
97
+ sort_by_alg_header(algs)
98
+ end
99
+
100
+ # Move algorithms matching the JWT alg header to the beginning of the list
101
+ def sort_by_alg_header(algs)
102
+ return algs if algs.size <= 1
103
+
104
+ algs.partition { |alg| alg.valid_alg?(alg_in_header) }.flatten
88
105
  end
89
106
 
90
107
  def find_key(&keyfinder)
@@ -105,7 +122,7 @@ module JWT
105
122
  return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
106
123
  return if segment_length == 2 && none_algorithm?
107
124
 
108
- raise(JWT::DecodeError, 'Not enough or too many segments')
125
+ raise JWT::DecodeError, 'Not enough or too many segments'
109
126
  end
110
127
 
111
128
  def segment_length
@@ -113,14 +130,14 @@ module JWT
113
130
  end
114
131
 
115
132
  def none_algorithm?
116
- algorithm == 'none'
133
+ alg_in_header == 'none'
117
134
  end
118
135
 
119
- def decode_crypto
136
+ def decode_signature
120
137
  @signature = ::JWT::Base64.url_decode(@segments[2] || '')
121
138
  end
122
139
 
123
- def algorithm
140
+ def alg_in_header
124
141
  header['alg']
125
142
  end
126
143
 
@@ -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,24 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './algos'
4
- require_relative './claims_validator'
3
+ require_relative 'jwa'
4
+ require_relative 'claims_validator'
5
5
 
6
6
  # JWT::Encode module
7
7
  module JWT
8
8
  # Encoding logic for JWT
9
9
  class Encode
10
- ALG_NONE = 'none'
11
- ALG_KEY = 'alg'
10
+ ALG_KEY = 'alg'
12
11
 
13
12
  def initialize(options)
14
- @payload = options[:payload]
15
- @key = options[:key]
16
- _, @algorithm = Algos.find(options[:algorithm])
17
- @headers = options[:headers].transform_keys(&:to_s)
13
+ @payload = options[:payload]
14
+ @key = options[:key]
15
+ @algorithm = JWA.create(options[:algorithm])
16
+ @headers = options[:headers].transform_keys(&:to_s)
17
+ @headers[ALG_KEY] = @algorithm.alg
18
18
  end
19
19
 
20
20
  def segments
21
- @segments ||= combine(encoded_header_and_payload, encoded_signature)
21
+ validate_claims!
22
+ combine(encoded_header_and_payload, encoded_signature)
22
23
  end
23
24
 
24
25
  private
@@ -40,25 +41,28 @@ module JWT
40
41
  end
41
42
 
42
43
  def encode_header
43
- @headers[ALG_KEY] = @algorithm
44
- encode(@headers)
44
+ encode_data(@headers)
45
45
  end
46
46
 
47
47
  def encode_payload
48
- if @payload.is_a?(Hash)
49
- ClaimsValidator.new(@payload).validate!
50
- end
48
+ encode_data(@payload)
49
+ end
51
50
 
52
- encode(@payload)
51
+ def signature
52
+ @algorithm.sign(data: encoded_header_and_payload, signing_key: @key)
53
53
  end
54
54
 
55
- def encode_signature
56
- return '' if @algorithm == ALG_NONE
55
+ def validate_claims!
56
+ return unless @payload.is_a?(Hash)
57
57
 
58
- ::JWT::Base64.url_encode(JWT::Signature.sign(@algorithm, encoded_header_and_payload, @key))
58
+ ClaimsValidator.new(@payload).validate!
59
+ end
60
+
61
+ def encode_signature
62
+ ::JWT::Base64.url_encode(signature)
59
63
  end
60
64
 
61
- def encode(data)
65
+ def encode_data(data)
62
66
  ::JWT::Base64.url_encode(JWT::JSON.generate(data))
63
67
  end
64
68
 
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,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
4
+ module JWA
5
5
  module Ecdsa
6
6
  module_function
7
7
 
@@ -30,8 +30,7 @@ module JWT
30
30
 
31
31
  SUPPORTED = NAMED_CURVES.map { |_, c| c[:algorithm] }.uniq.freeze
32
32
 
33
- def sign(to_sign)
34
- algorithm, msg, key = to_sign.values
33
+ def sign(algorithm, msg, key)
35
34
  curve_definition = curve_by_name(key.group.curve_name)
36
35
  key_algorithm = curve_definition[:algorithm]
37
36
  if algorithm != key_algorithm
@@ -39,11 +38,10 @@ module JWT
39
38
  end
40
39
 
41
40
  digest = OpenSSL::Digest.new(curve_definition[:digest])
42
- SecurityUtils.asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
41
+ asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
43
42
  end
44
43
 
45
- def verify(to_verify)
46
- algorithm, public_key, signing_input, signature = to_verify.values
44
+ def verify(algorithm, public_key, signing_input, signature)
47
45
  curve_definition = curve_by_name(public_key.group.curve_name)
48
46
  key_algorithm = curve_definition[:algorithm]
49
47
  if algorithm != key_algorithm
@@ -51,7 +49,9 @@ module JWT
51
49
  end
52
50
 
53
51
  digest = OpenSSL::Digest.new(curve_definition[:digest])
54
- public_key.dsa_verify_asn1(digest.digest(signing_input), SecurityUtils.raw_to_asn1(signature, public_key))
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
55
  end
56
56
 
57
57
  def curve_by_name(name)
@@ -59,6 +59,18 @@ module JWT
59
59
  raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
60
60
  end
61
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
73
+ end
62
74
  end
63
75
  end
64
76
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ module Eddsa
6
+ SUPPORTED = %w[ED25519 EdDSA].freeze
7
+ SUPPORTED_DOWNCASED = SUPPORTED.map(&:downcase).freeze
8
+
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)
16
+
17
+ key.sign(msg)
18
+ end
19
+
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)
26
+
27
+ public_key.verify(signature, signing_input)
28
+ rescue RbNaCl::CryptoError
29
+ false
30
+ end
31
+
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
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ module Hmac
6
+ module_function
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 ||= ''
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'
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))
32
+ end
33
+
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)
45
+ end
46
+ else
47
+ # :nocov:
48
+ def fixed_length_secure_compare(a, b)
49
+ raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
50
+
51
+ l = a.unpack "C#{a.bytesize}"
52
+
53
+ res = 0
54
+ b.each_byte { |byte| res |= byte ^ l.shift }
55
+ res == 0
56
+ end
57
+ # :nocov:
58
+ end
59
+ module_function :fixed_length_secure_compare
60
+
61
+ # Secure string comparison for strings of variable length.
62
+ #
63
+ # While a timing attack would not be able to discern the content of
64
+ # a secret compared via secure_compare, it is possible to determine
65
+ # the secret length. This should be considered when using secure_compare
66
+ # to compare weak, short secrets to user input.
67
+ def secure_compare(a, b)
68
+ a.bytesize == b.bytesize && fixed_length_secure_compare(a, b)
69
+ end
70
+ module_function :secure_compare
71
+ end
72
+ # rubocop:enable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
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
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
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
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
4
+ module JWA
5
5
  module None
6
6
  module_function
7
7
 
8
8
  SUPPORTED = %w[none].freeze
9
9
 
10
- def sign(*); end
10
+ def sign(*)
11
+ ''
12
+ end
11
13
 
12
14
  def verify(*)
13
15
  true
data/lib/jwt/jwa/ps.rb ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ module Ps
6
+ # RSASSA-PSS signing algorithms
7
+
8
+ module_function
9
+
10
+ SUPPORTED = %w[PS256 PS384 PS512].freeze
11
+
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."
15
+ end
16
+
17
+ translated_algorithm = algorithm.sub('PS', 'sha')
18
+
19
+ key.sign_pss(translated_algorithm, msg, salt_length: :digest, mgf1_hash: translated_algorithm)
20
+ end
21
+
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)
25
+ rescue OpenSSL::PKey::PKeyError
26
+ raise JWT::VerificationError, 'Signature verification raised'
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ module Rsa
6
+ module_function
7
+
8
+ SUPPORTED = %w[RS256 RS384 RS512].freeze
9
+
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
+ end
14
+
15
+ key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
16
+ end
17
+
18
+ def verify(algorithm, public_key, signing_input, signature)
19
+ public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
20
+ rescue OpenSSL::PKey::PKeyError
21
+ raise JWT::VerificationError, 'Signature verification raised'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
- module Algos
4
+ module JWA
5
5
  module Unsupported
6
6
  module_function
7
7
 
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ class Wrapper
6
+ attr_reader :alg, :cls
7
+
8
+ def initialize(alg, cls)
9
+ @alg = alg
10
+ @cls = cls
11
+ end
12
+
13
+ def valid_alg?(alg_to_check)
14
+ alg&.casecmp(alg_to_check)&.zero? == true
15
+ end
16
+
17
+ def sign(data:, signing_key:)
18
+ cls.sign(alg, data, signing_key)
19
+ end
20
+
21
+ def verify(data:, signature:, verification_key:)
22
+ cls.verify(alg, verification_key, data, signature)
23
+ end
24
+ end
25
+ end
26
+ end