jwt 2.8.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.
data/lib/jwt/jwa/rsa.rb CHANGED
@@ -2,24 +2,35 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
- module Rsa
6
- module_function
5
+ class Rsa
6
+ include JWT::JWA::SigningAlgorithm
7
7
 
8
- SUPPORTED = %w[RS256 RS384 RS512].freeze
8
+ def initialize(alg)
9
+ @alg = alg
10
+ @digest = OpenSSL::Digest.new(alg.sub('RS', 'SHA'))
11
+ end
9
12
 
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
+ 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")
13
16
  end
14
17
 
15
- key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
18
+ signing_key.sign(digest, data)
16
19
  end
17
20
 
18
- def verify(algorithm, public_key, signing_input, signature)
19
- public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
21
+ def verify(data:, signature:, verification_key:)
22
+ verification_key.verify(digest, signature, data)
20
23
  rescue OpenSSL::PKey::PKeyError
21
24
  raise JWT::VerificationError, 'Signature verification raised'
22
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
23
34
  end
24
35
  end
25
36
  end
@@ -0,0 +1,60 @@
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
+ klass.include(JWT::JWA::Compat)
15
+ end
16
+
17
+ attr_reader :alg
18
+
19
+ def valid_alg?(alg_to_check)
20
+ alg&.casecmp(alg_to_check)&.zero? == true
21
+ end
22
+
23
+ def header(*)
24
+ { 'alg' => alg }
25
+ end
26
+
27
+ def sign(*)
28
+ raise_sign_error!('Algorithm implementation is missing the sign method')
29
+ end
30
+
31
+ def verify(*)
32
+ raise_verify_error!('Algorithm implementation is missing the verify method')
33
+ end
34
+
35
+ def raise_verify_error!(message)
36
+ raise(DecodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
37
+ end
38
+
39
+ def raise_sign_error!(message)
40
+ raise(EncodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
41
+ end
42
+ end
43
+
44
+ class << self
45
+ def register_algorithm(algo)
46
+ algorithms[algo.alg.to_s.downcase] = algo
47
+ end
48
+
49
+ def find(algo)
50
+ algorithms.fetch(algo.to_s.downcase, Unsupported)
51
+ end
52
+
53
+ private
54
+
55
+ def algorithms
56
+ @algorithms ||= {}
57
+ end
58
+ end
59
+ end
60
+ end
@@ -3,16 +3,16 @@
3
3
  module JWT
4
4
  module JWA
5
5
  module Unsupported
6
- module_function
6
+ class << self
7
+ include JWT::JWA::SigningAlgorithm
7
8
 
8
- SUPPORTED = [].freeze
9
+ def sign(*)
10
+ raise_sign_error!('Unsupported signing method')
11
+ end
9
12
 
10
- def sign(*)
11
- raise NotImplementedError, 'Unsupported signing method'
12
- end
13
-
14
- def verify(*)
15
- raise JWT::VerificationError, 'Algorithm not supported'
13
+ def verify(*)
14
+ raise JWT::VerificationError, 'Algorithm not supported'
15
+ end
16
16
  end
17
17
  end
18
18
  end
@@ -3,23 +3,40 @@
3
3
  module JWT
4
4
  module JWA
5
5
  class Wrapper
6
- attr_reader :alg, :cls
6
+ include SigningAlgorithm
7
7
 
8
- def initialize(alg, cls)
9
- @alg = alg
10
- @cls = cls
8
+ def initialize(algorithm)
9
+ @algorithm = algorithm
10
+ end
11
+
12
+ def alg
13
+ return @algorithm.alg if @algorithm.respond_to?(:alg)
14
+
15
+ super
11
16
  end
12
17
 
13
18
  def valid_alg?(alg_to_check)
14
- alg&.casecmp(alg_to_check)&.zero? == true
19
+ return @algorithm.valid_alg?(alg_to_check) if @algorithm.respond_to?(:valid_alg?)
20
+
21
+ super
15
22
  end
16
23
 
17
- def sign(data:, signing_key:)
18
- cls.sign(alg, data, signing_key)
24
+ def header(*args, **kwargs)
25
+ return @algorithm.header(*args, **kwargs) if @algorithm.respond_to?(:header)
26
+
27
+ super
19
28
  end
20
29
 
21
- def verify(data:, signature:, verification_key:)
22
- cls.verify(alg, verification_key, data, signature)
30
+ def sign(*args, **kwargs)
31
+ return @algorithm.sign(*args, **kwargs) if @algorithm.respond_to?(:sign)
32
+
33
+ super
34
+ end
35
+
36
+ def verify(*args, **kwargs)
37
+ return @algorithm.verify(*args, **kwargs) if @algorithm.respond_to?(:verify)
38
+
39
+ super
23
40
  end
24
41
  end
25
42
  end
data/lib/jwt/jwa.rb CHANGED
@@ -8,54 +8,42 @@ rescue LoadError
8
8
  raise if defined?(RbNaCl)
9
9
  end
10
10
 
11
- require_relative 'jwa/hmac'
12
- require_relative 'jwa/eddsa'
11
+ require_relative 'jwa/compat'
12
+ require_relative 'jwa/signing_algorithm'
13
13
  require_relative 'jwa/ecdsa'
14
- require_relative 'jwa/rsa'
15
- require_relative 'jwa/ps'
14
+ require_relative 'jwa/hmac'
16
15
  require_relative 'jwa/none'
16
+ require_relative 'jwa/ps'
17
+ require_relative 'jwa/rsa'
17
18
  require_relative 'jwa/unsupported'
18
19
  require_relative 'jwa/wrapper'
19
20
 
21
+ if JWT.rbnacl?
22
+ require_relative 'jwa/eddsa'
23
+ end
24
+
25
+ if JWT.rbnacl_6_or_greater?
26
+ require_relative 'jwa/hmac_rbnacl'
27
+ elsif JWT.rbnacl?
28
+ require_relative 'jwa/hmac_rbnacl_fixed'
29
+ end
30
+
20
31
  module JWT
21
32
  module JWA
22
- ALGOS = [Hmac, Ecdsa, Rsa, Eddsa, Ps, None, Unsupported].tap do |l|
23
- if ::JWT.rbnacl_6_or_greater?
24
- require_relative 'jwa/hmac_rbnacl'
25
- l << Algos::HmacRbNaCl
26
- elsif ::JWT.rbnacl?
27
- require_relative 'jwa/hmac_rbnacl_fixed'
28
- l << Algos::HmacRbNaClFixed
29
- end
30
- end.freeze
31
-
32
33
  class << self
33
- def find(algorithm)
34
- indexed[algorithm&.downcase]
35
- end
36
-
37
- def create(algorithm)
38
- return algorithm if JWA.implementation?(algorithm)
34
+ def resolve(algorithm)
35
+ return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol)
39
36
 
40
- Wrapper.new(*find(algorithm))
41
- end
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
42
41
 
43
- def implementation?(algorithm)
44
- (algorithm.respond_to?(:valid_alg?) && algorithm.respond_to?(:verify)) ||
45
- (algorithm.respond_to?(:alg) && algorithm.respond_to?(:sign))
42
+ algorithm
46
43
  end
47
44
 
48
- private
49
-
50
- def indexed
51
- @indexed ||= begin
52
- fallback = [nil, Unsupported]
53
- ALGOS.each_with_object(Hash.new(fallback)) do |cls, hash|
54
- cls.const_get(:SUPPORTED).each do |alg|
55
- hash[alg.downcase] = [alg, cls]
56
- end
57
- end
58
- end
45
+ def create(algorithm)
46
+ resolve(algorithm)
59
47
  end
60
48
  end
61
49
  end
data/lib/jwt/jwk/ec.rb CHANGED
@@ -153,26 +153,26 @@ module JWT
153
153
  )
154
154
 
155
155
  sequence = if jwk_d
156
- # https://datatracker.ietf.org/doc/html/rfc5915.html
157
- # ECPrivateKey ::= SEQUENCE {
158
- # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
159
- # privateKey OCTET STRING,
160
- # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
161
- # publicKey [1] BIT STRING OPTIONAL
162
- # }
163
-
164
- OpenSSL::ASN1::Sequence([
165
- OpenSSL::ASN1::Integer(1),
166
- OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
167
- OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
168
- OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
169
- ])
170
- else
171
- OpenSSL::ASN1::Sequence([
172
- OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
173
- OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
174
- ])
175
- end
156
+ # https://datatracker.ietf.org/doc/html/rfc5915.html
157
+ # ECPrivateKey ::= SEQUENCE {
158
+ # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
159
+ # privateKey OCTET STRING,
160
+ # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
161
+ # publicKey [1] BIT STRING OPTIONAL
162
+ # }
163
+
164
+ OpenSSL::ASN1::Sequence([
165
+ OpenSSL::ASN1::Integer(1),
166
+ OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
167
+ OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
168
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
169
+ ])
170
+ else
171
+ OpenSSL::ASN1::Sequence([
172
+ OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
173
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
174
+ ])
175
+ end
176
176
 
177
177
  OpenSSL::PKey::EC.new(sequence.to_der)
178
178
  end
@@ -8,10 +8,10 @@ module JWT
8
8
  jwks_or_loader = options[:jwks]
9
9
 
10
10
  @jwks_loader = if jwks_or_loader.respond_to?(:call)
11
- jwks_or_loader
12
- else
13
- ->(_options) { jwks_or_loader }
14
- end
11
+ jwks_or_loader
12
+ else
13
+ ->(_options) { jwks_or_loader }
14
+ end
15
15
  end
16
16
 
17
17
  def key_for(kid)
data/lib/jwt/jwk/set.rb CHANGED
@@ -25,7 +25,7 @@ module JWT
25
25
  jwks.map { |k| JWT::JWK.new(k, nil, options) }
26
26
  else
27
27
  raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK'
28
- end
28
+ end
29
29
  end
30
30
 
31
31
  def export(options = {})
data/lib/jwt/verify.rb CHANGED
@@ -1,27 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'jwt/error'
3
+ require_relative 'error'
4
4
 
5
5
  module JWT
6
- # JWT verify methods
7
6
  class Verify
8
- DEFAULTS = {
9
- leeway: 0
10
- }.freeze
7
+ DEFAULTS = { leeway: 0 }.freeze
8
+ METHODS = %w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub verify_required_claims].freeze
11
9
 
12
10
  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|
11
+ METHODS.each do |method_name|
12
+ define_method(method_name) do |payload, options|
15
13
  new(payload, options).send(method_name)
16
14
  end
17
15
  end
18
16
 
19
17
  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
18
+ ::JWT::Claims.verify!(payload, options)
19
+ true
25
20
  end
26
21
  end
27
22
 
@@ -30,88 +25,10 @@ module JWT
30
25
  @options = DEFAULTS.merge(options)
31
26
  end
32
27
 
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>'}")
28
+ METHODS.each do |method_name|
29
+ define_method(method_name) do
30
+ ::JWT::Claims.verify!(@payload, @options.merge(method_name => true))
64
31
  end
65
32
  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
33
  end
117
34
  end
data/lib/jwt/version.rb CHANGED
@@ -11,9 +11,9 @@ module JWT
11
11
  # major version
12
12
  MAJOR = 2
13
13
  # minor version
14
- MINOR = 8
14
+ MINOR = 9
15
15
  # tiny version
16
- TINY = 1
16
+ TINY = 3
17
17
  # alpha, beta, etc. tag
18
18
  PRE = nil
19
19
 
@@ -7,7 +7,7 @@ module JWT
7
7
  # See https://tools.ietf.org/html/rfc7515#section-4.1.6
8
8
  class X5cKeyFinder
9
9
  def initialize(root_certificates, crls = nil)
10
- raise(ArgumentError, 'Root certificates must be specified') unless root_certificates
10
+ raise ArgumentError, 'Root certificates must be specified' unless root_certificates
11
11
 
12
12
  @store = build_store(root_certificates, crls)
13
13
  end
@@ -24,7 +24,7 @@ module JWT
24
24
  error = "#{error} Certificate subject: #{current_cert.subject}."
25
25
  end
26
26
 
27
- raise(JWT::VerificationError, error)
27
+ raise JWT::VerificationError, error
28
28
  end
29
29
  end
30
30
 
data/lib/jwt.rb CHANGED
@@ -9,6 +9,10 @@ require 'jwt/deprecations'
9
9
  require 'jwt/encode'
10
10
  require 'jwt/error'
11
11
  require 'jwt/jwk'
12
+ require 'jwt/claims'
13
+
14
+ require 'jwt/claims_validator'
15
+ require 'jwt/verify'
12
16
 
13
17
  # JSON Web Token implementation
14
18
  #
@@ -27,6 +31,8 @@ module JWT
27
31
  end
28
32
 
29
33
  def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter
30
- Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
34
+ Deprecations.context do
35
+ Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
36
+ end
31
37
  end
32
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.1
4
+ version: 2.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Rudat
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-29 00:00:00.000000000 Z
11
+ date: 2024-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -123,6 +123,18 @@ files:
123
123
  - README.md
124
124
  - lib/jwt.rb
125
125
  - lib/jwt/base64.rb
126
+ - lib/jwt/claims.rb
127
+ - lib/jwt/claims/audience.rb
128
+ - lib/jwt/claims/decode_verifier.rb
129
+ - lib/jwt/claims/expiration.rb
130
+ - lib/jwt/claims/issued_at.rb
131
+ - lib/jwt/claims/issuer.rb
132
+ - lib/jwt/claims/jwt_id.rb
133
+ - lib/jwt/claims/not_before.rb
134
+ - lib/jwt/claims/numeric.rb
135
+ - lib/jwt/claims/required.rb
136
+ - lib/jwt/claims/subject.rb
137
+ - lib/jwt/claims/verifier.rb
126
138
  - lib/jwt/claims_validator.rb
127
139
  - lib/jwt/configuration.rb
128
140
  - lib/jwt/configuration/container.rb
@@ -134,6 +146,7 @@ files:
134
146
  - lib/jwt/error.rb
135
147
  - lib/jwt/json.rb
136
148
  - lib/jwt/jwa.rb
149
+ - lib/jwt/jwa/compat.rb
137
150
  - lib/jwt/jwa/ecdsa.rb
138
151
  - lib/jwt/jwa/eddsa.rb
139
152
  - lib/jwt/jwa/hmac.rb
@@ -142,6 +155,7 @@ files:
142
155
  - lib/jwt/jwa/none.rb
143
156
  - lib/jwt/jwa/ps.rb
144
157
  - lib/jwt/jwa/rsa.rb
158
+ - lib/jwt/jwa/signing_algorithm.rb
145
159
  - lib/jwt/jwa/unsupported.rb
146
160
  - lib/jwt/jwa/wrapper.rb
147
161
  - lib/jwt/jwk.rb
@@ -163,9 +177,9 @@ licenses:
163
177
  - MIT
164
178
  metadata:
165
179
  bug_tracker_uri: https://github.com/jwt/ruby-jwt/issues
166
- changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.8.1/CHANGELOG.md
180
+ changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.9.3/CHANGELOG.md
167
181
  rubygems_mfa_required: 'true'
168
- post_install_message:
182
+ post_install_message:
169
183
  rdoc_options: []
170
184
  require_paths:
171
185
  - lib
@@ -180,8 +194,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
194
  - !ruby/object:Gem::Version
181
195
  version: '0'
182
196
  requirements: []
183
- rubygems_version: 3.3.7
184
- signing_key:
197
+ rubygems_version: 3.5.16
198
+ signing_key:
185
199
  specification_version: 4
186
200
  summary: JSON Web Token implementation in Ruby
187
201
  test_files: []