jwt 2.8.2 → 3.1.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +149 -31
  3. data/CODE_OF_CONDUCT.md +14 -14
  4. data/CONTRIBUTING.md +9 -10
  5. data/README.md +299 -234
  6. data/UPGRADING.md +47 -0
  7. data/lib/jwt/base64.rb +4 -10
  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 +45 -0
  17. data/lib/jwt/claims/required.rb +33 -0
  18. data/lib/jwt/claims/subject.rb +30 -0
  19. data/lib/jwt/claims/verifier.rb +61 -0
  20. data/lib/jwt/claims.rb +67 -0
  21. data/lib/jwt/configuration/container.rb +20 -1
  22. data/lib/jwt/configuration/decode_configuration.rb +24 -0
  23. data/lib/jwt/configuration/jwk_configuration.rb +1 -0
  24. data/lib/jwt/configuration.rb +8 -0
  25. data/lib/jwt/decode.rb +42 -81
  26. data/lib/jwt/encode.rb +17 -60
  27. data/lib/jwt/encoded_token.rb +236 -0
  28. data/lib/jwt/error.rb +32 -1
  29. data/lib/jwt/json.rb +1 -1
  30. data/lib/jwt/jwa/ecdsa.rb +59 -24
  31. data/lib/jwt/jwa/hmac.rb +22 -19
  32. data/lib/jwt/jwa/none.rb +8 -3
  33. data/lib/jwt/jwa/ps.rb +21 -15
  34. data/lib/jwt/jwa/rsa.rb +21 -10
  35. data/lib/jwt/jwa/signing_algorithm.rb +62 -0
  36. data/lib/jwt/jwa/unsupported.rb +9 -8
  37. data/lib/jwt/jwa.rb +76 -35
  38. data/lib/jwt/jwk/ec.rb +54 -65
  39. data/lib/jwt/jwk/hmac.rb +5 -6
  40. data/lib/jwt/jwk/key_base.rb +16 -1
  41. data/lib/jwt/jwk/key_finder.rb +35 -8
  42. data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
  43. data/lib/jwt/jwk/rsa.rb +7 -4
  44. data/lib/jwt/jwk/set.rb +2 -0
  45. data/lib/jwt/jwk.rb +1 -1
  46. data/lib/jwt/token.rb +131 -0
  47. data/lib/jwt/version.rb +24 -19
  48. data/lib/jwt.rb +18 -4
  49. data/ruby-jwt.gemspec +2 -0
  50. metadata +49 -15
  51. data/lib/jwt/claims_validator.rb +0 -37
  52. data/lib/jwt/deprecations.rb +0 -48
  53. data/lib/jwt/jwa/eddsa.rb +0 -42
  54. data/lib/jwt/jwa/hmac_rbnacl.rb +0 -50
  55. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +0 -46
  56. data/lib/jwt/jwa/wrapper.rb +0 -26
  57. data/lib/jwt/jwk/okp_rbnacl.rb +0 -110
  58. data/lib/jwt/verify.rb +0 -117
@@ -1,48 +0,0 @@
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/jwa/eddsa.rb DELETED
@@ -1,42 +0,0 @@
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
@@ -1,50 +0,0 @@
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
@@ -1,46 +0,0 @@
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,26 +0,0 @@
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
@@ -1,110 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JWT
4
- module JWK
5
- class OKPRbNaCl < KeyBase
6
- KTY = 'OKP'
7
- KTYS = [KTY, JWT::JWK::OKPRbNaCl, RbNaCl::Signatures::Ed25519::SigningKey, RbNaCl::Signatures::Ed25519::VerifyKey].freeze
8
- OKP_PUBLIC_KEY_ELEMENTS = %i[kty n x].freeze
9
- OKP_PRIVATE_KEY_ELEMENTS = %i[d].freeze
10
-
11
- def initialize(key, params = nil, options = {})
12
- params ||= {}
13
-
14
- # For backwards compatibility when kid was a String
15
- params = { kid: params } if params.is_a?(String)
16
-
17
- key_params = extract_key_params(key)
18
-
19
- params = params.transform_keys(&:to_sym)
20
- check_jwk_params!(key_params, params)
21
- super(options, key_params.merge(params))
22
- end
23
-
24
- def verify_key
25
- return @verify_key if defined?(@verify_key)
26
-
27
- @verify_key = verify_key_from_parameters
28
- end
29
-
30
- def signing_key
31
- return @signing_key if defined?(@signing_key)
32
-
33
- @signing_key = signing_key_from_parameters
34
- end
35
-
36
- def key_digest
37
- Thumbprint.new(self).to_s
38
- end
39
-
40
- def private?
41
- !signing_key.nil?
42
- end
43
-
44
- def members
45
- OKP_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
46
- end
47
-
48
- def export(options = {})
49
- exported = parameters.clone
50
- exported.reject! { |k, _| OKP_PRIVATE_KEY_ELEMENTS.include?(k) } unless private? && options[:include_private] == true
51
- exported
52
- end
53
-
54
- private
55
-
56
- def extract_key_params(key)
57
- case key
58
- when JWT::JWK::KeyBase
59
- key.export(include_private: true)
60
- when RbNaCl::Signatures::Ed25519::SigningKey
61
- @signing_key = key
62
- @verify_key = key.verify_key
63
- parse_okp_key_params(@verify_key, @signing_key)
64
- when RbNaCl::Signatures::Ed25519::VerifyKey
65
- @signing_key = nil
66
- @verify_key = key
67
- parse_okp_key_params(@verify_key)
68
- when Hash
69
- key.transform_keys(&:to_sym)
70
- else
71
- raise ArgumentError, 'key must be of type RbNaCl::Signatures::Ed25519::SigningKey, RbNaCl::Signatures::Ed25519::VerifyKey or Hash with key parameters'
72
- end
73
- end
74
-
75
- def check_jwk_params!(key_params, _given_params)
76
- raise JWT::JWKError, "Incorrect 'kty' value: #{key_params[:kty]}, expected #{KTY}" unless key_params[:kty] == KTY
77
- end
78
-
79
- def parse_okp_key_params(verify_key, signing_key = nil)
80
- params = {
81
- kty: KTY,
82
- crv: 'Ed25519',
83
- x: ::JWT::Base64.url_encode(verify_key.to_bytes)
84
- }
85
-
86
- if signing_key
87
- params[:d] = ::JWT::Base64.url_encode(signing_key.to_bytes)
88
- end
89
-
90
- params
91
- end
92
-
93
- def verify_key_from_parameters
94
- RbNaCl::Signatures::Ed25519::VerifyKey.new(::JWT::Base64.url_decode(self[:x]))
95
- end
96
-
97
- def signing_key_from_parameters
98
- return nil unless self[:d]
99
-
100
- RbNaCl::Signatures::Ed25519::SigningKey.new(::JWT::Base64.url_decode(self[:d]))
101
- end
102
-
103
- class << self
104
- def import(jwk_data)
105
- new(jwk_data)
106
- end
107
- end
108
- end
109
- end
110
- end
data/lib/jwt/verify.rb DELETED
@@ -1,117 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'jwt/error'
4
-
5
- module JWT
6
- # JWT verify methods
7
- class Verify
8
- DEFAULTS = {
9
- leeway: 0
10
- }.freeze
11
-
12
- class << self
13
- %w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub verify_required_claims].each do |method_name|
14
- define_method method_name do |payload, options|
15
- new(payload, options).send(method_name)
16
- end
17
- end
18
-
19
- def verify_claims(payload, options)
20
- options.each do |key, val|
21
- next unless key.to_s =~ /verify/
22
-
23
- Verify.send(key, payload, options) if val
24
- end
25
- end
26
- end
27
-
28
- def initialize(payload, options)
29
- @payload = payload
30
- @options = DEFAULTS.merge(options)
31
- end
32
-
33
- def verify_aud
34
- return unless (options_aud = @options[:aud])
35
-
36
- aud = @payload['aud']
37
- raise JWT::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{aud || '<none>'}" if ([*aud] & [*options_aud]).empty?
38
- end
39
-
40
- def verify_expiration
41
- return unless contains_key?(@payload, 'exp')
42
- raise JWT::ExpiredSignature, 'Signature has expired' if @payload['exp'].to_i <= (Time.now.to_i - exp_leeway)
43
- end
44
-
45
- def verify_iat
46
- return unless contains_key?(@payload, 'iat')
47
-
48
- iat = @payload['iat']
49
- raise JWT::InvalidIatError, 'Invalid iat' if !iat.is_a?(Numeric) || iat.to_f > Time.now.to_f
50
- end
51
-
52
- def verify_iss
53
- return unless (options_iss = @options[:iss])
54
-
55
- iss = @payload['iss']
56
-
57
- options_iss = Array(options_iss).map { |item| item.is_a?(Symbol) ? item.to_s : item }
58
-
59
- case iss
60
- when *options_iss
61
- nil
62
- else
63
- raise JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{iss || '<none>'}"
64
- end
65
- end
66
-
67
- def verify_jti
68
- options_verify_jti = @options[:verify_jti]
69
- jti = @payload['jti']
70
-
71
- if options_verify_jti.respond_to?(:call)
72
- verified = options_verify_jti.arity == 2 ? options_verify_jti.call(jti, @payload) : options_verify_jti.call(jti)
73
- raise JWT::InvalidJtiError, 'Invalid jti' unless verified
74
- elsif jti.to_s.strip.empty?
75
- raise JWT::InvalidJtiError, 'Missing jti'
76
- end
77
- end
78
-
79
- def verify_not_before
80
- return unless contains_key?(@payload, 'nbf')
81
- raise JWT::ImmatureSignature, 'Signature nbf has not been reached' if @payload['nbf'].to_i > (Time.now.to_i + nbf_leeway)
82
- end
83
-
84
- def verify_sub
85
- return unless (options_sub = @options[:sub])
86
-
87
- sub = @payload['sub']
88
- raise JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{sub || '<none>'}" unless sub.to_s == options_sub.to_s
89
- end
90
-
91
- def verify_required_claims
92
- return unless (options_required_claims = @options[:required_claims])
93
-
94
- options_required_claims.each do |required_claim|
95
- raise JWT::MissingRequiredClaim, "Missing required claim #{required_claim}" unless contains_key?(@payload, required_claim)
96
- end
97
- end
98
-
99
- private
100
-
101
- def global_leeway
102
- @options[:leeway]
103
- end
104
-
105
- def exp_leeway
106
- @options[:exp_leeway] || global_leeway
107
- end
108
-
109
- def nbf_leeway
110
- @options[:nbf_leeway] || global_leeway
111
- end
112
-
113
- def contains_key?(payload, key)
114
- payload.respond_to?(:key?) && payload.key?(key)
115
- end
116
- end
117
- end