jwt 2.8.1 → 2.9.3

Sign up to get free protection for your applications and to get access to all the features.
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: []