jwt 2.4.1 → 2.9.3

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +177 -14
  3. data/CONTRIBUTING.md +7 -7
  4. data/README.md +180 -37
  5. data/lib/jwt/base64.rb +33 -0
  6. data/lib/jwt/claims/audience.rb +20 -0
  7. data/lib/jwt/claims/decode_verifier.rb +40 -0
  8. data/lib/jwt/claims/expiration.rb +22 -0
  9. data/lib/jwt/claims/issued_at.rb +15 -0
  10. data/lib/jwt/claims/issuer.rb +24 -0
  11. data/lib/jwt/claims/jwt_id.rb +25 -0
  12. data/lib/jwt/claims/not_before.rb +22 -0
  13. data/lib/jwt/claims/numeric.rb +55 -0
  14. data/lib/jwt/claims/required.rb +23 -0
  15. data/lib/jwt/claims/subject.rb +20 -0
  16. data/lib/jwt/claims/verifier.rb +62 -0
  17. data/lib/jwt/claims.rb +82 -0
  18. data/lib/jwt/claims_validator.rb +3 -24
  19. data/lib/jwt/configuration/container.rb +32 -0
  20. data/lib/jwt/configuration/decode_configuration.rb +46 -0
  21. data/lib/jwt/configuration/jwk_configuration.rb +27 -0
  22. data/lib/jwt/configuration.rb +15 -0
  23. data/lib/jwt/decode.rb +54 -41
  24. data/lib/jwt/deprecations.rb +48 -0
  25. data/lib/jwt/encode.rb +21 -21
  26. data/lib/jwt/error.rb +1 -0
  27. data/lib/jwt/jwa/compat.rb +29 -0
  28. data/lib/jwt/jwa/ecdsa.rb +93 -0
  29. data/lib/jwt/jwa/eddsa.rb +34 -0
  30. data/lib/jwt/jwa/hmac.rb +83 -0
  31. data/lib/jwt/jwa/hmac_rbnacl.rb +49 -0
  32. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +46 -0
  33. data/lib/jwt/jwa/none.rb +23 -0
  34. data/lib/jwt/jwa/ps.rb +36 -0
  35. data/lib/jwt/jwa/rsa.rb +36 -0
  36. data/lib/jwt/jwa/signing_algorithm.rb +60 -0
  37. data/lib/jwt/jwa/unsupported.rb +19 -0
  38. data/lib/jwt/jwa/wrapper.rb +43 -0
  39. data/lib/jwt/jwa.rb +50 -0
  40. data/lib/jwt/jwk/ec.rb +162 -65
  41. data/lib/jwt/jwk/hmac.rb +69 -24
  42. data/lib/jwt/jwk/key_base.rb +45 -7
  43. data/lib/jwt/jwk/key_finder.rb +19 -35
  44. data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
  45. data/lib/jwt/jwk/okp_rbnacl.rb +110 -0
  46. data/lib/jwt/jwk/rsa.rb +141 -54
  47. data/lib/jwt/jwk/set.rb +80 -0
  48. data/lib/jwt/jwk/thumbprint.rb +26 -0
  49. data/lib/jwt/jwk.rb +14 -11
  50. data/lib/jwt/verify.rb +10 -89
  51. data/lib/jwt/version.rb +24 -2
  52. data/lib/jwt/x5c_key_finder.rb +3 -6
  53. data/lib/jwt.rb +12 -4
  54. data/ruby-jwt.gemspec +11 -4
  55. metadata +59 -31
  56. data/.codeclimate.yml +0 -8
  57. data/.github/workflows/coverage.yml +0 -27
  58. data/.github/workflows/test.yml +0 -66
  59. data/.gitignore +0 -13
  60. data/.reek.yml +0 -22
  61. data/.rspec +0 -2
  62. data/.rubocop.yml +0 -67
  63. data/.sourcelevel.yml +0 -17
  64. data/Appraisals +0 -13
  65. data/Gemfile +0 -7
  66. data/Rakefile +0 -16
  67. data/lib/jwt/algos/ecdsa.rb +0 -64
  68. data/lib/jwt/algos/eddsa.rb +0 -33
  69. data/lib/jwt/algos/hmac.rb +0 -36
  70. data/lib/jwt/algos/none.rb +0 -17
  71. data/lib/jwt/algos/ps.rb +0 -43
  72. data/lib/jwt/algos/rsa.rb +0 -22
  73. data/lib/jwt/algos/unsupported.rb +0 -19
  74. data/lib/jwt/algos.rb +0 -44
  75. data/lib/jwt/default_options.rb +0 -18
  76. data/lib/jwt/security_utils.rb +0 -59
  77. data/lib/jwt/signature.rb +0 -35
data/lib/jwt/decode.rb CHANGED
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
-
5
- require 'jwt/signature'
6
- require 'jwt/verify'
7
4
  require 'jwt/x5c_key_finder'
5
+
8
6
  # JWT::Decode module
9
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
@@ -24,13 +22,13 @@ module JWT
24
22
  def decode_segments
25
23
  validate_segment_count!
26
24
  if @verify
27
- decode_crypto
25
+ decode_signature
28
26
  verify_algo
29
27
  set_key
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,45 +44,63 @@ 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 algorithm
55
- raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless options_includes_algo_in_header?
51
+ raise JWT::IncorrectAlgorithm, 'An algorithm must be specified' if allowed_algorithms.empty?
52
+ raise JWT::DecodeError, 'Token header not a JSON object' unless header.is_a?(Hash)
53
+ raise JWT::IncorrectAlgorithm, 'Token is missing alg header' unless alg_in_header
54
+ raise JWT::IncorrectAlgorithm, 'Expected a different algorithm' if allowed_and_valid_algorithms.empty?
56
55
  end
57
56
 
58
57
  def set_key
59
58
  @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
59
+ @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(header['kid']) if @options[:jwks]
60
+ return unless (x5c_options = @options[:x5c])
61
+
62
+ @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
64
63
  end
65
64
 
66
65
  def verify_signature_for?(key)
67
- Signature.verify(algorithm, key, signing_input, @signature)
66
+ allowed_and_valid_algorithms.any? do |alg|
67
+ alg.verify(data: signing_input, signature: @signature, verification_key: key)
68
+ end
68
69
  end
69
70
 
70
- def options_includes_algo_in_header?
71
- allowed_algorithms.any? { |alg| alg.casecmp(algorithm).zero? }
71
+ def allowed_and_valid_algorithms
72
+ @allowed_and_valid_algorithms ||= allowed_algorithms.select { |alg| alg.valid_alg?(alg_in_header) }
72
73
  end
73
74
 
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
- []
75
+ # Order is very important - first check for string keys, next for symbols
76
+ ALGORITHM_KEYS = ['algorithm',
77
+ :algorithm,
78
+ 'algorithms',
79
+ :algorithms].freeze
80
+
81
+ def given_algorithms
82
+ ALGORITHM_KEYS.each do |alg_key|
83
+ alg = @options[alg_key]
84
+ return Array(alg) if alg
86
85
  end
87
- Array(algos)
86
+ []
87
+ end
88
+
89
+ def allowed_algorithms
90
+ @allowed_algorithms ||= resolve_allowed_algorithms
91
+ end
92
+
93
+ def resolve_allowed_algorithms
94
+ algs = given_algorithms.map { |alg| JWA.resolve(alg) }
95
+
96
+ sort_by_alg_header(algs)
97
+ end
98
+
99
+ # Move algorithms matching the JWT alg header to the beginning of the list
100
+ def sort_by_alg_header(algs)
101
+ return algs if algs.size <= 1
102
+
103
+ algs.partition { |alg| alg.valid_alg?(alg_in_header) }.flatten
88
104
  end
89
105
 
90
106
  def find_key(&keyfinder)
@@ -96,8 +112,7 @@ module JWT
96
112
  end
97
113
 
98
114
  def verify_claims
99
- Verify.verify_claims(payload, @options)
100
- Verify.verify_required_claims(payload, @options)
115
+ Claims::DecodeVerifier.verify!(payload, @options)
101
116
  end
102
117
 
103
118
  def validate_segment_count!
@@ -105,7 +120,7 @@ module JWT
105
120
  return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
106
121
  return if segment_length == 2 && none_algorithm?
107
122
 
108
- raise(JWT::DecodeError, 'Not enough or too many segments')
123
+ raise JWT::DecodeError, 'Not enough or too many segments'
109
124
  end
110
125
 
111
126
  def segment_length
@@ -113,16 +128,14 @@ module JWT
113
128
  end
114
129
 
115
130
  def none_algorithm?
116
- algorithm.casecmp('none').zero?
131
+ alg_in_header == 'none'
117
132
  end
118
133
 
119
- def decode_crypto
120
- @signature = Base64.urlsafe_decode64(@segments[2] || '')
121
- rescue ArgumentError
122
- raise(JWT::DecodeError, 'Invalid segment encoding')
134
+ def decode_signature
135
+ @signature = ::JWT::Base64.url_decode(@segments[2] || '')
123
136
  end
124
137
 
125
- def algorithm
138
+ def alg_in_header
126
139
  header['alg']
127
140
  end
128
141
 
@@ -139,8 +152,8 @@ module JWT
139
152
  end
140
153
 
141
154
  def parse_and_decode(segment)
142
- JWT::JSON.parse(Base64.urlsafe_decode64(segment))
143
- rescue ::JSON::ParserError, ArgumentError
155
+ JWT::JSON.parse(::JWT::Base64.url_decode(segment))
156
+ rescue ::JSON::ParserError
144
157
  raise JWT::DecodeError, 'Invalid segment encoding'
145
158
  end
146
159
  end
@@ -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,21 @@
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_NONE = 'none'
11
- ALG_KEY = 'alg'
12
-
13
9
  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)
10
+ @payload = options[:payload]
11
+ @key = options[:key]
12
+ @algorithm = JWA.resolve(options[:algorithm])
13
+ @headers = options[:headers].transform_keys(&:to_s)
18
14
  end
19
15
 
20
16
  def segments
21
- @segments ||= combine(encoded_header_and_payload, encoded_signature)
17
+ validate_claims!
18
+ combine(encoded_header_and_payload, encoded_signature)
22
19
  end
23
20
 
24
21
  private
@@ -40,26 +37,29 @@ module JWT
40
37
  end
41
38
 
42
39
  def encode_header
43
- @headers[ALG_KEY] = @algorithm
44
- encode(@headers)
40
+ encode_data(@headers.merge(@algorithm.header(signing_key: @key)))
45
41
  end
46
42
 
47
43
  def encode_payload
48
- if @payload.is_a?(Hash)
49
- ClaimsValidator.new(@payload).validate!
50
- end
44
+ encode_data(@payload)
45
+ end
51
46
 
52
- encode(@payload)
47
+ def signature
48
+ @algorithm.sign(data: encoded_header_and_payload, signing_key: @key)
53
49
  end
54
50
 
55
- def encode_signature
56
- return '' if @algorithm == ALG_NONE
51
+ def validate_claims!
52
+ return unless @payload.is_a?(Hash)
57
53
 
58
- Base64.urlsafe_encode64(JWT::Signature.sign(@algorithm, encoded_header_and_payload, @key), padding: false)
54
+ Claims.verify_payload!(@payload, :numeric)
55
+ end
56
+
57
+ def encode_signature
58
+ ::JWT::Base64.url_encode(signature)
59
59
  end
60
60
 
61
- def encode(data)
62
- Base64.urlsafe_encode64(JWT::JSON.generate(data), padding: false)
61
+ def encode_data(data)
62
+ ::JWT::Base64.url_encode(JWT::JSON.generate(data))
63
63
  end
64
64
 
65
65
  def combine(*parts)
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
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ module Compat
6
+ module ClassMethods
7
+ def from_algorithm(algorithm)
8
+ new(algorithm)
9
+ end
10
+
11
+ def sign(algorithm, msg, key)
12
+ Deprecations.warning('Support for calling sign with positional arguments will be removed in future ruby-jwt versions')
13
+
14
+ from_algorithm(algorithm).sign(data: msg, signing_key: key)
15
+ end
16
+
17
+ def verify(algorithm, key, signing_input, signature)
18
+ Deprecations.warning('Support for calling verify with positional arguments will be removed in future ruby-jwt versions')
19
+
20
+ from_algorithm(algorithm).verify(data: signing_input, signature: signature, verification_key: key)
21
+ end
22
+ end
23
+
24
+ def self.included(klass)
25
+ klass.extend(ClassMethods)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
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
34
+
35
+ NAMED_CURVES = {
36
+ 'prime256v1' => {
37
+ algorithm: 'ES256',
38
+ digest: 'sha256'
39
+ },
40
+ 'secp256r1' => { # alias for prime256v1
41
+ algorithm: 'ES256',
42
+ digest: 'sha256'
43
+ },
44
+ 'secp384r1' => {
45
+ algorithm: 'ES384',
46
+ digest: 'sha384'
47
+ },
48
+ 'secp521r1' => {
49
+ algorithm: 'ES512',
50
+ digest: 'sha512'
51
+ },
52
+ 'secp256k1' => {
53
+ algorithm: 'ES256K',
54
+ digest: 'sha256'
55
+ }
56
+ }.freeze
57
+
58
+ NAMED_CURVES.each_value do |v|
59
+ register_algorithm(new(v[:algorithm], v[:digest]))
60
+ end
61
+
62
+ def self.from_algorithm(algorithm)
63
+ new(algorithm, algorithm.downcase.gsub('es', 'sha'))
64
+ end
65
+
66
+ def self.curve_by_name(name)
67
+ NAMED_CURVES.fetch(name) do
68
+ raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ attr_reader :digest
75
+
76
+ def curve_by_name(name)
77
+ self.class.curve_by_name(name)
78
+ end
79
+
80
+ def raw_to_asn1(signature, private_key)
81
+ byte_size = (private_key.group.degree + 7) / 8
82
+ sig_bytes = signature[0..(byte_size - 1)]
83
+ sig_char = signature[byte_size..-1] || ''
84
+ OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
85
+ end
86
+
87
+ def asn1_to_raw(signature, public_key)
88
+ byte_size = (public_key.group.degree + 7) / 8
89
+ OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
90
+ end
91
+ end
92
+ end
93
+ end
@@ -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
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ class Hmac
6
+ include JWT::JWA::SigningAlgorithm
7
+
8
+ def self.from_algorithm(algorithm)
9
+ new(algorithm, OpenSSL::Digest.new(algorithm.downcase.gsub('hs', 'sha')))
10
+ end
11
+
12
+ def initialize(alg, digest)
13
+ @alg = alg
14
+ @digest = digest
15
+ end
16
+
17
+ def sign(data:, signing_key:)
18
+ signing_key ||= ''
19
+ raise_verify_error!('HMAC key expected to be a String') unless signing_key.is_a?(String)
20
+
21
+ OpenSSL::HMAC.digest(digest.new, signing_key, data)
22
+ rescue OpenSSL::HMACError => e
23
+ if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure'
24
+ raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret')
25
+ end
26
+
27
+ raise e
28
+ end
29
+
30
+ def verify(data:, signature:, verification_key:)
31
+ SecurityUtils.secure_compare(signature, sign(data: data, signing_key: verification_key))
32
+ end
33
+
34
+ register_algorithm(new('HS256', OpenSSL::Digest::SHA256))
35
+ register_algorithm(new('HS384', OpenSSL::Digest::SHA384))
36
+ register_algorithm(new('HS512', OpenSSL::Digest::SHA512))
37
+
38
+ private
39
+
40
+ attr_reader :digest
41
+
42
+ # Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb
43
+ # rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
44
+ module SecurityUtils
45
+ # Constant time string comparison, for fixed length strings.
46
+ #
47
+ # The values compared should be of fixed length, such as strings
48
+ # that have already been processed by HMAC. Raises in case of length mismatch.
49
+
50
+ if defined?(OpenSSL.fixed_length_secure_compare)
51
+ def fixed_length_secure_compare(a, b)
52
+ OpenSSL.fixed_length_secure_compare(a, b)
53
+ end
54
+ else
55
+ # :nocov:
56
+ def fixed_length_secure_compare(a, b)
57
+ raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
58
+
59
+ l = a.unpack "C#{a.bytesize}"
60
+
61
+ res = 0
62
+ b.each_byte { |byte| res |= byte ^ l.shift }
63
+ res == 0
64
+ end
65
+ # :nocov:
66
+ end
67
+ module_function :fixed_length_secure_compare
68
+
69
+ # Secure string comparison for strings of variable length.
70
+ #
71
+ # While a timing attack would not be able to discern the content of
72
+ # a secret compared via secure_compare, it is possible to determine
73
+ # the secret length. This should be considered when using secure_compare
74
+ # to compare weak, short secrets to user input.
75
+ def secure_compare(a, b)
76
+ a.bytesize == b.bytesize && fixed_length_secure_compare(a, b)
77
+ end
78
+ module_function :secure_compare
79
+ end
80
+ # rubocop:enable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ class HmacRbNaCl
6
+ include JWT::JWA::SigningAlgorithm
7
+
8
+ def self.from_algorithm(algorithm)
9
+ new(algorithm, ::RbNaCl::HMAC.const_get(algorithm.upcase.gsub('HS', 'SHA')))
10
+ end
11
+
12
+ def initialize(alg, hmac)
13
+ @alg = alg
14
+ @hmac = hmac
15
+ end
16
+
17
+ def sign(data:, signing_key:)
18
+ Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
19
+ hmac.auth(key_for_rbnacl(hmac, signing_key).encode('binary'), data.encode('binary'))
20
+ end
21
+
22
+ def verify(data:, signature:, 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
+ hmac.verify(key_for_rbnacl(hmac, verification_key).encode('binary'), signature.encode('binary'), data.encode('binary'))
25
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
26
+ false
27
+ end
28
+
29
+ register_algorithm(new('HS512256', ::RbNaCl::HMAC::SHA512256))
30
+
31
+ private
32
+
33
+ attr_reader :hmac
34
+
35
+ def key_for_rbnacl(hmac, key)
36
+ key ||= ''
37
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
38
+
39
+ return padded_empty_key(hmac.key_bytes) if key == ''
40
+
41
+ key
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
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ class HmacRbNaClFixed
6
+ include JWT::JWA::SigningAlgorithm
7
+
8
+ def self.from_algorithm(algorithm)
9
+ new(algorithm, ::RbNaCl::HMAC.const_get(algorithm.upcase.gsub('HS', 'SHA')))
10
+ end
11
+
12
+ def initialize(alg, hmac)
13
+ @alg = alg
14
+ @hmac = hmac
15
+ end
16
+
17
+ def sign(data:, signing_key:)
18
+ signing_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
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless signing_key.is_a?(String)
21
+
22
+ hmac.auth(padded_key_bytes(signing_key, hmac.key_bytes), data.encode('binary'))
23
+ end
24
+
25
+ def verify(data:, signature:, verification_key:)
26
+ verification_key ||= ''
27
+ Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
28
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless verification_key.is_a?(String)
29
+
30
+ hmac.verify(padded_key_bytes(verification_key, hmac.key_bytes), signature.encode('binary'), data.encode('binary'))
31
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
32
+ false
33
+ end
34
+
35
+ register_algorithm(new('HS512256', ::RbNaCl::HMAC::SHA512256))
36
+
37
+ private
38
+
39
+ attr_reader :hmac
40
+
41
+ def padded_key_bytes(key, bytesize)
42
+ key.bytes.fill(0, key.bytesize...bytesize).pack('C*')
43
+ end
44
+ end
45
+ end
46
+ 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