jwt 2.3.0 → 2.10.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +60 -53
  3. data/CHANGELOG.md +194 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +99 -0
  6. data/README.md +360 -106
  7. data/lib/jwt/base64.rb +19 -2
  8. data/lib/jwt/claims/audience.rb +30 -0
  9. data/lib/jwt/claims/crit.rb +35 -0
  10. data/lib/jwt/claims/decode_verifier.rb +40 -0
  11. data/lib/jwt/claims/expiration.rb +32 -0
  12. data/lib/jwt/claims/issued_at.rb +22 -0
  13. data/lib/jwt/claims/issuer.rb +34 -0
  14. data/lib/jwt/claims/jwt_id.rb +35 -0
  15. data/lib/jwt/claims/not_before.rb +32 -0
  16. data/lib/jwt/claims/numeric.rb +77 -0
  17. data/lib/jwt/claims/required.rb +33 -0
  18. data/lib/jwt/claims/subject.rb +30 -0
  19. data/lib/jwt/claims/verification_methods.rb +20 -0
  20. data/lib/jwt/claims/verifier.rb +61 -0
  21. data/lib/jwt/claims.rb +74 -0
  22. data/lib/jwt/claims_validator.rb +7 -24
  23. data/lib/jwt/configuration/container.rb +52 -0
  24. data/lib/jwt/configuration/decode_configuration.rb +70 -0
  25. data/lib/jwt/configuration/jwk_configuration.rb +28 -0
  26. data/lib/jwt/configuration.rb +23 -0
  27. data/lib/jwt/decode.rb +70 -61
  28. data/lib/jwt/deprecations.rb +49 -0
  29. data/lib/jwt/encode.rb +18 -57
  30. data/lib/jwt/encoded_token.rb +139 -0
  31. data/lib/jwt/error.rb +36 -0
  32. data/lib/jwt/json.rb +1 -1
  33. data/lib/jwt/jwa/compat.rb +32 -0
  34. data/lib/jwt/jwa/ecdsa.rb +90 -0
  35. data/lib/jwt/jwa/eddsa.rb +35 -0
  36. data/lib/jwt/jwa/hmac.rb +82 -0
  37. data/lib/jwt/jwa/hmac_rbnacl.rb +50 -0
  38. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +47 -0
  39. data/lib/jwt/jwa/none.rb +24 -0
  40. data/lib/jwt/jwa/ps.rb +35 -0
  41. data/lib/jwt/jwa/rsa.rb +35 -0
  42. data/lib/jwt/jwa/signing_algorithm.rb +63 -0
  43. data/lib/jwt/jwa/unsupported.rb +20 -0
  44. data/lib/jwt/jwa/wrapper.rb +44 -0
  45. data/lib/jwt/jwa.rb +58 -0
  46. data/lib/jwt/jwk/ec.rb +163 -63
  47. data/lib/jwt/jwk/hmac.rb +68 -24
  48. data/lib/jwt/jwk/key_base.rb +46 -6
  49. data/lib/jwt/jwk/key_finder.rb +20 -35
  50. data/lib/jwt/jwk/kid_as_key_digest.rb +16 -0
  51. data/lib/jwt/jwk/okp_rbnacl.rb +109 -0
  52. data/lib/jwt/jwk/rsa.rb +141 -54
  53. data/lib/jwt/jwk/set.rb +82 -0
  54. data/lib/jwt/jwk/thumbprint.rb +26 -0
  55. data/lib/jwt/jwk.rb +16 -11
  56. data/lib/jwt/token.rb +112 -0
  57. data/lib/jwt/verify.rb +16 -81
  58. data/lib/jwt/version.rb +53 -11
  59. data/lib/jwt/x5c_key_finder.rb +52 -0
  60. data/lib/jwt.rb +28 -4
  61. data/ruby-jwt.gemspec +15 -5
  62. metadata +75 -28
  63. data/.github/workflows/test.yml +0 -74
  64. data/.gitignore +0 -11
  65. data/.rspec +0 -2
  66. data/.rubocop.yml +0 -97
  67. data/.rubocop_todo.yml +0 -185
  68. data/.sourcelevel.yml +0 -18
  69. data/Appraisals +0 -10
  70. data/Gemfile +0 -5
  71. data/Rakefile +0 -14
  72. data/lib/jwt/algos/ecdsa.rb +0 -35
  73. data/lib/jwt/algos/eddsa.rb +0 -30
  74. data/lib/jwt/algos/hmac.rb +0 -34
  75. data/lib/jwt/algos/none.rb +0 -15
  76. data/lib/jwt/algos/ps.rb +0 -43
  77. data/lib/jwt/algos/rsa.rb +0 -19
  78. data/lib/jwt/algos/unsupported.rb +0 -17
  79. data/lib/jwt/algos.rb +0 -44
  80. data/lib/jwt/default_options.rb +0 -16
  81. data/lib/jwt/security_utils.rb +0 -57
  82. data/lib/jwt/signature.rb +0 -39
data/lib/jwt/error.rb CHANGED
@@ -1,21 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
+ # The EncodeError class is raised when there is an error encoding a JWT.
4
5
  class EncodeError < StandardError; end
6
+
7
+ # The DecodeError class is raised when there is an error decoding a JWT.
5
8
  class DecodeError < StandardError; end
9
+
10
+ # The RequiredDependencyError class is raised when a required dependency is missing.
6
11
  class RequiredDependencyError < StandardError; end
7
12
 
13
+ # The VerificationError class is raised when there is an error verifying a JWT.
8
14
  class VerificationError < DecodeError; end
15
+
16
+ # The ExpiredSignature class is raised when the JWT signature has expired.
9
17
  class ExpiredSignature < DecodeError; end
18
+
19
+ # The IncorrectAlgorithm class is raised when the JWT algorithm is incorrect.
10
20
  class IncorrectAlgorithm < DecodeError; end
21
+
22
+ # The ImmatureSignature class is raised when the JWT signature is immature.
11
23
  class ImmatureSignature < DecodeError; end
24
+
25
+ # The InvalidIssuerError class is raised when the JWT issuer is invalid.
12
26
  class InvalidIssuerError < DecodeError; end
27
+
28
+ # The UnsupportedEcdsaCurve class is raised when the ECDSA curve is unsupported.
29
+ class UnsupportedEcdsaCurve < IncorrectAlgorithm; end
30
+
31
+ # The InvalidIatError class is raised when the JWT issued at (iat) claim is invalid.
13
32
  class InvalidIatError < DecodeError; end
33
+
34
+ # The InvalidAudError class is raised when the JWT audience (aud) claim is invalid.
14
35
  class InvalidAudError < DecodeError; end
36
+
37
+ # The InvalidSubError class is raised when the JWT subject (sub) claim is invalid.
15
38
  class InvalidSubError < DecodeError; end
39
+
40
+ # The InvalidCritError class is raised when the JWT crit header is invalid.
41
+ class InvalidCritError < DecodeError; end
42
+
43
+ # The InvalidJtiError class is raised when the JWT ID (jti) claim is invalid.
16
44
  class InvalidJtiError < DecodeError; end
45
+
46
+ # The InvalidPayload class is raised when the JWT payload is invalid.
17
47
  class InvalidPayload < DecodeError; end
48
+
49
+ # The MissingRequiredClaim class is raised when a required claim is missing from the JWT.
18
50
  class MissingRequiredClaim < DecodeError; end
19
51
 
52
+ # The Base64DecodeError class is raised when there is an error decoding a Base64-encoded string.
53
+ class Base64DecodeError < DecodeError; end
54
+
55
+ # The JWKError class is raised when there is an error with the JSON Web Key (JWK).
20
56
  class JWKError < DecodeError; end
21
57
  end
data/lib/jwt/json.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require 'json'
4
4
 
5
5
  module JWT
6
- # JSON wrapper
6
+ # @api private
7
7
  class JSON
8
8
  class << self
9
9
  def generate(data)
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ # Provides backwards compatibility for algorithms
6
+ # @api private
7
+ module Compat
8
+ # @api private
9
+ module ClassMethods
10
+ def from_algorithm(algorithm)
11
+ new(algorithm)
12
+ end
13
+
14
+ def sign(algorithm, msg, key)
15
+ Deprecations.warning('Support for calling sign with positional arguments will be removed in future ruby-jwt versions')
16
+
17
+ from_algorithm(algorithm).sign(data: msg, signing_key: key)
18
+ end
19
+
20
+ def verify(algorithm, key, signing_input, signature)
21
+ Deprecations.warning('Support for calling verify with positional arguments will be removed in future ruby-jwt versions')
22
+
23
+ from_algorithm(algorithm).verify(data: signing_input, signature: signature, verification_key: key)
24
+ end
25
+ end
26
+
27
+ def self.included(klass)
28
+ klass.extend(ClassMethods)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ # ECDSA signing algorithm
6
+ class Ecdsa
7
+ include JWT::JWA::SigningAlgorithm
8
+
9
+ def initialize(alg, digest)
10
+ @alg = alg
11
+ @digest = OpenSSL::Digest.new(digest)
12
+ end
13
+
14
+ def sign(data:, signing_key:)
15
+ curve_definition = curve_by_name(signing_key.group.curve_name)
16
+ key_algorithm = curve_definition[:algorithm]
17
+ raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided" if alg != key_algorithm
18
+
19
+ asn1_to_raw(signing_key.dsa_sign_asn1(digest.digest(data)), signing_key)
20
+ end
21
+
22
+ def verify(data:, signature:, verification_key:)
23
+ curve_definition = curve_by_name(verification_key.group.curve_name)
24
+ key_algorithm = curve_definition[:algorithm]
25
+ raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided" if alg != key_algorithm
26
+
27
+ verification_key.dsa_verify_asn1(digest.digest(data), raw_to_asn1(signature, verification_key))
28
+ rescue OpenSSL::PKey::PKeyError
29
+ raise JWT::VerificationError, 'Signature verification raised'
30
+ end
31
+
32
+ NAMED_CURVES = {
33
+ 'prime256v1' => {
34
+ algorithm: 'ES256',
35
+ digest: 'sha256'
36
+ },
37
+ 'secp256r1' => { # alias for prime256v1
38
+ algorithm: 'ES256',
39
+ digest: 'sha256'
40
+ },
41
+ 'secp384r1' => {
42
+ algorithm: 'ES384',
43
+ digest: 'sha384'
44
+ },
45
+ 'secp521r1' => {
46
+ algorithm: 'ES512',
47
+ digest: 'sha512'
48
+ },
49
+ 'secp256k1' => {
50
+ algorithm: 'ES256K',
51
+ digest: 'sha256'
52
+ }
53
+ }.freeze
54
+
55
+ NAMED_CURVES.each_value do |v|
56
+ register_algorithm(new(v[:algorithm], v[:digest]))
57
+ end
58
+
59
+ def self.from_algorithm(algorithm)
60
+ new(algorithm, algorithm.downcase.gsub('es', 'sha'))
61
+ end
62
+
63
+ def self.curve_by_name(name)
64
+ NAMED_CURVES.fetch(name) do
65
+ raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ attr_reader :digest
72
+
73
+ def curve_by_name(name)
74
+ self.class.curve_by_name(name)
75
+ end
76
+
77
+ def raw_to_asn1(signature, private_key)
78
+ byte_size = (private_key.group.degree + 7) / 8
79
+ sig_bytes = signature[0..(byte_size - 1)]
80
+ sig_char = signature[byte_size..-1] || ''
81
+ OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
82
+ end
83
+
84
+ def asn1_to_raw(signature, public_key)
85
+ byte_size = (public_key.group.degree + 7) / 8
86
+ OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ # Implementation of the EdDSA family of algorithms
6
+ class Eddsa
7
+ include JWT::JWA::SigningAlgorithm
8
+
9
+ def initialize(alg)
10
+ @alg = alg
11
+ end
12
+
13
+ def sign(data:, signing_key:)
14
+ raise_sign_error!("Key given is a #{signing_key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey") unless signing_key.is_a?(RbNaCl::Signatures::Ed25519::SigningKey)
15
+
16
+ Deprecations.warning('Using Ed25519 keys is deprecated and will be removed in a future version of ruby-jwt. Please use the ruby-eddsa gem instead.')
17
+
18
+ signing_key.sign(data)
19
+ end
20
+
21
+ def verify(data:, signature:, verification_key:)
22
+ raise_verify_error!("key given is a #{verification_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey") unless verification_key.is_a?(RbNaCl::Signatures::Ed25519::VerifyKey)
23
+
24
+ Deprecations.warning('Using Ed25519 keys is deprecated and will be removed in a future version of ruby-jwt. Please use the ruby-eddsa gem instead.')
25
+
26
+ verification_key.verify(signature, data)
27
+ rescue RbNaCl::CryptoError
28
+ false
29
+ end
30
+
31
+ register_algorithm(new('ED25519'))
32
+ register_algorithm(new('EdDSA'))
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ # Implementation of the HMAC family of algorithms
6
+ class Hmac
7
+ include JWT::JWA::SigningAlgorithm
8
+
9
+ def self.from_algorithm(algorithm)
10
+ new(algorithm, OpenSSL::Digest.new(algorithm.downcase.gsub('hs', 'sha')))
11
+ end
12
+
13
+ def initialize(alg, digest)
14
+ @alg = alg
15
+ @digest = digest
16
+ end
17
+
18
+ def sign(data:, signing_key:)
19
+ signing_key ||= ''
20
+ raise_verify_error!('HMAC key expected to be a String') unless signing_key.is_a?(String)
21
+
22
+ OpenSSL::HMAC.digest(digest.new, signing_key, data)
23
+ rescue OpenSSL::HMACError => e
24
+ raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret') if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure'
25
+
26
+ raise e
27
+ end
28
+
29
+ def verify(data:, signature:, verification_key:)
30
+ SecurityUtils.secure_compare(signature, sign(data: data, signing_key: verification_key))
31
+ end
32
+
33
+ register_algorithm(new('HS256', OpenSSL::Digest::SHA256))
34
+ register_algorithm(new('HS384', OpenSSL::Digest::SHA384))
35
+ register_algorithm(new('HS512', OpenSSL::Digest::SHA512))
36
+
37
+ private
38
+
39
+ attr_reader :digest
40
+
41
+ # Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb
42
+ # rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
43
+ module SecurityUtils
44
+ # Constant time string comparison, for fixed length strings.
45
+ #
46
+ # The values compared should be of fixed length, such as strings
47
+ # that have already been processed by HMAC. Raises in case of length mismatch.
48
+
49
+ if defined?(OpenSSL.fixed_length_secure_compare)
50
+ def fixed_length_secure_compare(a, b)
51
+ OpenSSL.fixed_length_secure_compare(a, b)
52
+ end
53
+ else
54
+ # :nocov:
55
+ def fixed_length_secure_compare(a, b)
56
+ raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
57
+
58
+ l = a.unpack "C#{a.bytesize}"
59
+
60
+ res = 0
61
+ b.each_byte { |byte| res |= byte ^ l.shift }
62
+ res == 0
63
+ end
64
+ # :nocov:
65
+ end
66
+ module_function :fixed_length_secure_compare
67
+
68
+ # Secure string comparison for strings of variable length.
69
+ #
70
+ # While a timing attack would not be able to discern the content of
71
+ # a secret compared via secure_compare, it is possible to determine
72
+ # the secret length. This should be considered when using secure_compare
73
+ # to compare weak, short secrets to user input.
74
+ def secure_compare(a, b)
75
+ a.bytesize == b.bytesize && fixed_length_secure_compare(a, b)
76
+ end
77
+ module_function :secure_compare
78
+ end
79
+ # rubocop:enable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ # Implementation of the HMAC family of algorithms (using RbNaCl)
6
+ class HmacRbNaCl
7
+ include JWT::JWA::SigningAlgorithm
8
+
9
+ def self.from_algorithm(algorithm)
10
+ new(algorithm, ::RbNaCl::HMAC.const_get(algorithm.upcase.gsub('HS', 'SHA')))
11
+ end
12
+
13
+ def initialize(alg, hmac)
14
+ @alg = alg
15
+ @hmac = hmac
16
+ end
17
+
18
+ def sign(data:, 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
+ hmac.auth(key_for_rbnacl(hmac, signing_key).encode('binary'), data.encode('binary'))
21
+ end
22
+
23
+ def verify(data:, signature:, verification_key:)
24
+ Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
25
+ hmac.verify(key_for_rbnacl(hmac, verification_key).encode('binary'), signature.encode('binary'), data.encode('binary'))
26
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
27
+ false
28
+ end
29
+
30
+ register_algorithm(new('HS512256', ::RbNaCl::HMAC::SHA512256))
31
+
32
+ private
33
+
34
+ attr_reader :hmac
35
+
36
+ def key_for_rbnacl(hmac, key)
37
+ key ||= ''
38
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
39
+
40
+ return padded_empty_key(hmac.key_bytes) if key == ''
41
+
42
+ key
43
+ end
44
+
45
+ def padded_empty_key(length)
46
+ Array.new(length, 0x0).pack('C*').encode('binary')
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ # Implementation of the HMAC family of algorithms (using RbNaCl prior to a certain version)
6
+ class HmacRbNaClFixed
7
+ include JWT::JWA::SigningAlgorithm
8
+
9
+ def self.from_algorithm(algorithm)
10
+ new(algorithm, ::RbNaCl::HMAC.const_get(algorithm.upcase.gsub('HS', 'SHA')))
11
+ end
12
+
13
+ def initialize(alg, hmac)
14
+ @alg = alg
15
+ @hmac = hmac
16
+ end
17
+
18
+ def sign(data:, signing_key:)
19
+ signing_key ||= ''
20
+ Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
21
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless signing_key.is_a?(String)
22
+
23
+ hmac.auth(padded_key_bytes(signing_key, hmac.key_bytes), data.encode('binary'))
24
+ end
25
+
26
+ def verify(data:, signature:, verification_key:)
27
+ verification_key ||= ''
28
+ Deprecations.warning("The use of the algorithm #{alg} is deprecated and will be removed in the next major version of ruby-jwt")
29
+ raise JWT::DecodeError, 'HMAC key expected to be a String' unless verification_key.is_a?(String)
30
+
31
+ hmac.verify(padded_key_bytes(verification_key, hmac.key_bytes), signature.encode('binary'), data.encode('binary'))
32
+ rescue ::RbNaCl::BadAuthenticatorError, ::RbNaCl::LengthError
33
+ false
34
+ end
35
+
36
+ register_algorithm(new('HS512256', ::RbNaCl::HMAC::SHA512256))
37
+
38
+ private
39
+
40
+ attr_reader :hmac
41
+
42
+ def padded_key_bytes(key, bytesize)
43
+ key.bytes.fill(0, key.bytesize...bytesize).pack('C*')
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ # Implementation of the none algorithm
6
+ class None
7
+ include JWT::JWA::SigningAlgorithm
8
+
9
+ def initialize
10
+ @alg = 'none'
11
+ end
12
+
13
+ def sign(*)
14
+ ''
15
+ end
16
+
17
+ def verify(*)
18
+ true
19
+ end
20
+
21
+ register_algorithm(new)
22
+ end
23
+ end
24
+ end
data/lib/jwt/jwa/ps.rb ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ # Implementation of the RSASSA-PSS family of algorithms
6
+ class Ps
7
+ include JWT::JWA::SigningAlgorithm
8
+
9
+ def initialize(alg)
10
+ @alg = alg
11
+ @digest_algorithm = alg.sub('PS', 'sha')
12
+ end
13
+
14
+ def sign(data:, signing_key:)
15
+ raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance.") unless signing_key.is_a?(::OpenSSL::PKey::RSA)
16
+
17
+ signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm)
18
+ end
19
+
20
+ def verify(data:, signature:, verification_key:)
21
+ verification_key.verify_pss(digest_algorithm, signature, data, salt_length: :auto, mgf1_hash: digest_algorithm)
22
+ rescue OpenSSL::PKey::PKeyError
23
+ raise JWT::VerificationError, 'Signature verification raised'
24
+ end
25
+
26
+ register_algorithm(new('PS256'))
27
+ register_algorithm(new('PS384'))
28
+ register_algorithm(new('PS512'))
29
+
30
+ private
31
+
32
+ attr_reader :digest_algorithm
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ # Implementation of the RSA family of algorithms
6
+ class Rsa
7
+ include JWT::JWA::SigningAlgorithm
8
+
9
+ def initialize(alg)
10
+ @alg = alg
11
+ @digest = OpenSSL::Digest.new(alg.sub('RS', 'SHA'))
12
+ end
13
+
14
+ def sign(data:, signing_key:)
15
+ raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance") unless signing_key.is_a?(OpenSSL::PKey::RSA)
16
+
17
+ signing_key.sign(digest, data)
18
+ end
19
+
20
+ def verify(data:, signature:, verification_key:)
21
+ verification_key.verify(digest, signature, data)
22
+ rescue OpenSSL::PKey::PKeyError
23
+ raise JWT::VerificationError, 'Signature verification raised'
24
+ end
25
+
26
+ register_algorithm(new('RS256'))
27
+ register_algorithm(new('RS384'))
28
+ register_algorithm(new('RS512'))
29
+
30
+ private
31
+
32
+ attr_reader :digest
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ # JSON Web Algorithms
5
+ module JWA
6
+ # Base functionality for signing algorithms
7
+ module SigningAlgorithm
8
+ # Class methods for the SigningAlgorithm module
9
+ module ClassMethods
10
+ def register_algorithm(algo)
11
+ ::JWT::JWA.register_algorithm(algo)
12
+ end
13
+ end
14
+
15
+ def self.included(klass)
16
+ klass.extend(ClassMethods)
17
+ klass.include(JWT::JWA::Compat)
18
+ end
19
+
20
+ attr_reader :alg
21
+
22
+ def valid_alg?(alg_to_check)
23
+ alg&.casecmp(alg_to_check)&.zero? == true
24
+ end
25
+
26
+ def header(*)
27
+ { 'alg' => alg }
28
+ end
29
+
30
+ def sign(*)
31
+ raise_sign_error!('Algorithm implementation is missing the sign method')
32
+ end
33
+
34
+ def verify(*)
35
+ raise_verify_error!('Algorithm implementation is missing the verify method')
36
+ end
37
+
38
+ def raise_verify_error!(message)
39
+ raise(DecodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
40
+ end
41
+
42
+ def raise_sign_error!(message)
43
+ raise(EncodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
44
+ end
45
+ end
46
+
47
+ class << self
48
+ def register_algorithm(algo)
49
+ algorithms[algo.alg.to_s.downcase] = algo
50
+ end
51
+
52
+ def find(algo)
53
+ algorithms.fetch(algo.to_s.downcase, Unsupported)
54
+ end
55
+
56
+ private
57
+
58
+ def algorithms
59
+ @algorithms ||= {}
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ # Represents an unsupported algorithm
6
+ module Unsupported
7
+ class << self
8
+ include JWT::JWA::SigningAlgorithm
9
+
10
+ def sign(*)
11
+ raise_sign_error!('Unsupported signing method')
12
+ end
13
+
14
+ def verify(*)
15
+ raise JWT::VerificationError, 'Algorithm not supported'
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWA
5
+ # @api private
6
+ class Wrapper
7
+ include SigningAlgorithm
8
+
9
+ def initialize(algorithm)
10
+ @algorithm = algorithm
11
+ end
12
+
13
+ def alg
14
+ return @algorithm.alg if @algorithm.respond_to?(:alg)
15
+
16
+ super
17
+ end
18
+
19
+ def valid_alg?(alg_to_check)
20
+ return @algorithm.valid_alg?(alg_to_check) if @algorithm.respond_to?(:valid_alg?)
21
+
22
+ super
23
+ end
24
+
25
+ def header(*args, **kwargs)
26
+ return @algorithm.header(*args, **kwargs) if @algorithm.respond_to?(:header)
27
+
28
+ super
29
+ end
30
+
31
+ def sign(*args, **kwargs)
32
+ return @algorithm.sign(*args, **kwargs) if @algorithm.respond_to?(:sign)
33
+
34
+ super
35
+ end
36
+
37
+ def verify(*args, **kwargs)
38
+ return @algorithm.verify(*args, **kwargs) if @algorithm.respond_to?(:verify)
39
+
40
+ super
41
+ end
42
+ end
43
+ end
44
+ end
data/lib/jwt/jwa.rb ADDED
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ begin
6
+ require 'rbnacl'
7
+ rescue LoadError
8
+ raise if defined?(RbNaCl)
9
+ end
10
+
11
+ require_relative 'jwa/compat'
12
+ require_relative 'jwa/signing_algorithm'
13
+ require_relative 'jwa/ecdsa'
14
+ require_relative 'jwa/hmac'
15
+ require_relative 'jwa/none'
16
+ require_relative 'jwa/ps'
17
+ require_relative 'jwa/rsa'
18
+ require_relative 'jwa/unsupported'
19
+ require_relative 'jwa/wrapper'
20
+
21
+ require_relative 'jwa/eddsa' if JWT.rbnacl?
22
+
23
+ if JWT.rbnacl_6_or_greater?
24
+ require_relative 'jwa/hmac_rbnacl'
25
+ elsif JWT.rbnacl?
26
+ require_relative 'jwa/hmac_rbnacl_fixed'
27
+ end
28
+
29
+ module JWT
30
+ # The JWA module contains all supported algorithms.
31
+ module JWA
32
+ class << self
33
+ # @api private
34
+ def resolve(algorithm)
35
+ return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol)
36
+
37
+ unless algorithm.is_a?(SigningAlgorithm)
38
+ Deprecations.warning('Custom algorithms are required to include JWT::JWA::SigningAlgorithm. Custom algorithms that do not include this module may stop working in the next major version of ruby-jwt.')
39
+ return Wrapper.new(algorithm)
40
+ end
41
+
42
+ algorithm
43
+ end
44
+
45
+ # @api private
46
+ def resolve_and_sort(algorithms:, preferred_algorithm:)
47
+ algs = Array(algorithms).map { |alg| JWA.resolve(alg) }
48
+ algs.partition { |alg| alg.valid_alg?(preferred_algorithm) }.flatten
49
+ end
50
+
51
+ # @deprecated The `::JWT::JWA.create` method is deprecated and will be removed in the next major version of ruby-jwt.
52
+ def create(algorithm)
53
+ Deprecations.warning('The ::JWT::JWA.create method is deprecated and will be removed in the next major version of ruby-jwt.')
54
+ resolve(algorithm)
55
+ end
56
+ end
57
+ end
58
+ end