jwt 2.7.1 → 2.10.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +157 -27
- data/README.md +207 -101
- data/lib/jwt/base64.rb +19 -2
- data/lib/jwt/claims/audience.rb +30 -0
- data/lib/jwt/claims/crit.rb +35 -0
- data/lib/jwt/claims/decode_verifier.rb +40 -0
- data/lib/jwt/claims/expiration.rb +32 -0
- data/lib/jwt/claims/issued_at.rb +22 -0
- data/lib/jwt/claims/issuer.rb +34 -0
- data/lib/jwt/claims/jwt_id.rb +35 -0
- data/lib/jwt/claims/not_before.rb +32 -0
- data/lib/jwt/claims/numeric.rb +77 -0
- data/lib/jwt/claims/required.rb +33 -0
- data/lib/jwt/claims/subject.rb +30 -0
- data/lib/jwt/claims/verification_methods.rb +20 -0
- data/lib/jwt/claims/verifier.rb +61 -0
- data/lib/jwt/claims.rb +74 -0
- data/lib/jwt/claims_validator.rb +6 -25
- data/lib/jwt/configuration/container.rb +34 -3
- data/lib/jwt/configuration/decode_configuration.rb +24 -0
- data/lib/jwt/configuration/jwk_configuration.rb +2 -1
- data/lib/jwt/configuration.rb +8 -0
- data/lib/jwt/decode.rb +34 -82
- data/lib/jwt/deprecations.rb +49 -0
- data/lib/jwt/encode.rb +18 -67
- data/lib/jwt/encoded_token.rb +139 -0
- data/lib/jwt/error.rb +35 -0
- data/lib/jwt/json.rb +1 -1
- data/lib/jwt/jwa/compat.rb +32 -0
- data/lib/jwt/{algos → jwa}/ecdsa.rb +40 -26
- data/lib/jwt/jwa/eddsa.rb +35 -0
- data/lib/jwt/{algos → jwa}/hmac.rb +31 -20
- data/lib/jwt/jwa/hmac_rbnacl.rb +50 -0
- data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +47 -0
- data/lib/jwt/jwa/none.rb +24 -0
- data/lib/jwt/jwa/ps.rb +35 -0
- data/lib/jwt/jwa/rsa.rb +35 -0
- data/lib/jwt/jwa/signing_algorithm.rb +63 -0
- data/lib/jwt/jwa/unsupported.rb +20 -0
- data/lib/jwt/jwa/wrapper.rb +44 -0
- data/lib/jwt/jwa.rb +58 -0
- data/lib/jwt/jwk/ec.rb +44 -30
- data/lib/jwt/jwk/hmac.rb +2 -3
- data/lib/jwt/jwk/key_base.rb +4 -1
- data/lib/jwt/jwk/key_finder.rb +5 -4
- data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
- data/lib/jwt/jwk/okp_rbnacl.rb +3 -4
- data/lib/jwt/jwk/rsa.rb +2 -3
- data/lib/jwt/jwk/set.rb +3 -1
- data/lib/jwt/jwk.rb +2 -1
- data/lib/jwt/token.rb +112 -0
- data/lib/jwt/verify.rb +16 -89
- data/lib/jwt/version.rb +33 -11
- data/lib/jwt/x5c_key_finder.rb +2 -5
- data/lib/jwt.rb +24 -1
- data/ruby-jwt.gemspec +4 -0
- metadata +76 -18
- data/lib/jwt/algos/algo_wrapper.rb +0 -26
- data/lib/jwt/algos/eddsa.rb +0 -33
- data/lib/jwt/algos/hmac_rbnacl.rb +0 -53
- data/lib/jwt/algos/hmac_rbnacl_fixed.rb +0 -52
- data/lib/jwt/algos/none.rb +0 -19
- data/lib/jwt/algos/ps.rb +0 -41
- data/lib/jwt/algos/rsa.rb +0 -23
- data/lib/jwt/algos/unsupported.rb +0 -19
- data/lib/jwt/algos.rb +0 -66
data/lib/jwt/jwk/ec.rb
CHANGED
|
@@ -4,6 +4,7 @@ require 'forwardable'
|
|
|
4
4
|
|
|
5
5
|
module JWT
|
|
6
6
|
module JWK
|
|
7
|
+
# JWK representation for Elliptic Curve (EC) keys
|
|
7
8
|
class EC < KeyBase # rubocop:disable Metrics/ClassLength
|
|
8
9
|
KTY = 'EC'
|
|
9
10
|
KTYS = [KTY, OpenSSL::PKey::EC, JWT::JWK::EC].freeze
|
|
@@ -11,6 +12,7 @@ module JWT
|
|
|
11
12
|
EC_PUBLIC_KEY_ELEMENTS = %i[kty crv x y].freeze
|
|
12
13
|
EC_PRIVATE_KEY_ELEMENTS = %i[d].freeze
|
|
13
14
|
EC_KEY_ELEMENTS = (EC_PRIVATE_KEY_ELEMENTS + EC_PUBLIC_KEY_ELEMENTS).freeze
|
|
15
|
+
ZERO_BYTE = "\0".b.freeze
|
|
14
16
|
|
|
15
17
|
def initialize(key, params = nil, options = {})
|
|
16
18
|
params ||= {}
|
|
@@ -64,9 +66,7 @@ module JWT
|
|
|
64
66
|
end
|
|
65
67
|
|
|
66
68
|
def []=(key, value)
|
|
67
|
-
if EC_KEY_ELEMENTS.include?(key.to_sym)
|
|
68
|
-
raise ArgumentError, 'cannot overwrite cryptographic key attributes'
|
|
69
|
-
end
|
|
69
|
+
raise ArgumentError, 'cannot overwrite cryptographic key attributes' if EC_KEY_ELEMENTS.include?(key.to_sym)
|
|
70
70
|
|
|
71
71
|
super(key, value)
|
|
72
72
|
end
|
|
@@ -143,7 +143,6 @@ module JWT
|
|
|
143
143
|
if ::JWT.openssl_3?
|
|
144
144
|
def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/MethodLength
|
|
145
145
|
curve = EC.to_openssl_curve(jwk_crv)
|
|
146
|
-
|
|
147
146
|
x_octets = decode_octets(jwk_x)
|
|
148
147
|
y_octets = decode_octets(jwk_y)
|
|
149
148
|
|
|
@@ -153,26 +152,26 @@ module JWT
|
|
|
153
152
|
)
|
|
154
153
|
|
|
155
154
|
sequence = if jwk_d
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
155
|
+
# https://datatracker.ietf.org/doc/html/rfc5915.html
|
|
156
|
+
# ECPrivateKey ::= SEQUENCE {
|
|
157
|
+
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
|
|
158
|
+
# privateKey OCTET STRING,
|
|
159
|
+
# parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
|
|
160
|
+
# publicKey [1] BIT STRING OPTIONAL
|
|
161
|
+
# }
|
|
162
|
+
|
|
163
|
+
OpenSSL::ASN1::Sequence([
|
|
164
|
+
OpenSSL::ASN1::Integer(1),
|
|
165
|
+
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
|
|
166
|
+
OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
|
|
167
|
+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
|
|
168
|
+
])
|
|
169
|
+
else
|
|
170
|
+
OpenSSL::ASN1::Sequence([
|
|
171
|
+
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
|
|
172
|
+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
|
|
173
|
+
])
|
|
174
|
+
end
|
|
176
175
|
|
|
177
176
|
OpenSSL::PKey::EC.new(sequence.to_der)
|
|
178
177
|
end
|
|
@@ -205,12 +204,27 @@ module JWT
|
|
|
205
204
|
end
|
|
206
205
|
end
|
|
207
206
|
|
|
208
|
-
def decode_octets(
|
|
209
|
-
::JWT::Base64.url_decode(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
207
|
+
def decode_octets(base64_encoded_coordinate)
|
|
208
|
+
bytes = ::JWT::Base64.url_decode(base64_encoded_coordinate)
|
|
209
|
+
# Some base64 encoders on some platform omit a single 0-byte at
|
|
210
|
+
# the start of either Y or X coordinate of the elliptic curve point.
|
|
211
|
+
# This leads to an encoding error when data is passed to OpenSSL BN.
|
|
212
|
+
# It is know to have happend to exported JWKs on a Java application and
|
|
213
|
+
# on a Flutter/Dart application (both iOS and Android). All that is
|
|
214
|
+
# needed to fix the problem is adding a leading 0-byte. We know the
|
|
215
|
+
# required byte is 0 because with any other byte the point is no longer
|
|
216
|
+
# on the curve - and OpenSSL will actually communicate this via another
|
|
217
|
+
# exception. The indication of a stripped byte will be the fact that the
|
|
218
|
+
# coordinates - once decoded into bytes - should always be an even
|
|
219
|
+
# bytesize. For example, with a P-521 curve, both x and y must be 66 bytes.
|
|
220
|
+
# With a P-256 curve, both x and y must be 32 and so on. The simplest way
|
|
221
|
+
# to check for this truncation is thus to check whether the number of bytes
|
|
222
|
+
# is odd, and restore the leading 0-byte if it is.
|
|
223
|
+
if bytes.bytesize.odd?
|
|
224
|
+
ZERO_BYTE + bytes
|
|
225
|
+
else
|
|
226
|
+
bytes
|
|
227
|
+
end
|
|
214
228
|
end
|
|
215
229
|
|
|
216
230
|
class << self
|
data/lib/jwt/jwk/hmac.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module JWT
|
|
4
4
|
module JWK
|
|
5
|
+
# JWK for HMAC keys
|
|
5
6
|
class HMAC < KeyBase
|
|
6
7
|
KTY = 'oct'
|
|
7
8
|
KTYS = [KTY, String, JWT::JWK::HMAC].freeze
|
|
@@ -61,9 +62,7 @@ module JWT
|
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def []=(key, value)
|
|
64
|
-
if HMAC_KEY_ELEMENTS.include?(key.to_sym)
|
|
65
|
-
raise ArgumentError, 'cannot overwrite cryptographic key attributes'
|
|
66
|
-
end
|
|
65
|
+
raise ArgumentError, 'cannot overwrite cryptographic key attributes' if HMAC_KEY_ELEMENTS.include?(key.to_sym)
|
|
67
66
|
|
|
68
67
|
super(key, value)
|
|
69
68
|
end
|
data/lib/jwt/jwk/key_base.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module JWT
|
|
4
4
|
module JWK
|
|
5
|
+
# Base for JWK implementations
|
|
5
6
|
class KeyBase
|
|
6
7
|
def self.inherited(klass)
|
|
7
8
|
super
|
|
@@ -38,12 +39,14 @@ module JWT
|
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
def ==(other)
|
|
41
|
-
self[:kid] == other[:kid]
|
|
42
|
+
other.is_a?(::JWT::JWK::KeyBase) && self[:kid] == other[:kid]
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
alias eql? ==
|
|
45
46
|
|
|
46
47
|
def <=>(other)
|
|
48
|
+
return nil unless other.is_a?(::JWT::JWK::KeyBase)
|
|
49
|
+
|
|
47
50
|
self[:kid] <=> other[:kid]
|
|
48
51
|
end
|
|
49
52
|
|
data/lib/jwt/jwk/key_finder.rb
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module JWT
|
|
4
4
|
module JWK
|
|
5
|
+
# @api private
|
|
5
6
|
class KeyFinder
|
|
6
7
|
def initialize(options)
|
|
7
8
|
@allow_nil_kid = options[:allow_nil_kid]
|
|
8
9
|
jwks_or_loader = options[:jwks]
|
|
9
10
|
|
|
10
11
|
@jwks_loader = if jwks_or_loader.respond_to?(:call)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
jwks_or_loader
|
|
13
|
+
else
|
|
14
|
+
->(_options) { jwks_or_loader }
|
|
15
|
+
end
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def key_for(kid)
|
data/lib/jwt/jwk/okp_rbnacl.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module JWT
|
|
4
4
|
module JWK
|
|
5
|
+
# JSON Web Key (JWK) representation for Ed25519 keys
|
|
5
6
|
class OKPRbNaCl < KeyBase
|
|
6
7
|
KTY = 'OKP'
|
|
7
8
|
KTYS = [KTY, JWT::JWK::OKPRbNaCl, RbNaCl::Signatures::Ed25519::SigningKey, RbNaCl::Signatures::Ed25519::VerifyKey].freeze
|
|
@@ -10,7 +11,7 @@ module JWT
|
|
|
10
11
|
|
|
11
12
|
def initialize(key, params = nil, options = {})
|
|
12
13
|
params ||= {}
|
|
13
|
-
|
|
14
|
+
Deprecations.warning('Using the OKP JWK for Ed25519 keys is deprecated and will be removed in a future version of ruby-jwt. Please use the ruby-eddsa gem instead.')
|
|
14
15
|
# For backwards compatibility when kid was a String
|
|
15
16
|
params = { kid: params } if params.is_a?(String)
|
|
16
17
|
|
|
@@ -83,9 +84,7 @@ module JWT
|
|
|
83
84
|
x: ::JWT::Base64.url_encode(verify_key.to_bytes)
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
if signing_key
|
|
87
|
-
params[:d] = ::JWT::Base64.url_encode(signing_key.to_bytes)
|
|
88
|
-
end
|
|
87
|
+
params[:d] = ::JWT::Base64.url_encode(signing_key.to_bytes) if signing_key
|
|
89
88
|
|
|
90
89
|
params
|
|
91
90
|
end
|
data/lib/jwt/jwk/rsa.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module JWT
|
|
4
4
|
module JWK
|
|
5
|
+
# JSON Web Key (JWK) representation of a RSA key
|
|
5
6
|
class RSA < KeyBase # rubocop:disable Metrics/ClassLength
|
|
6
7
|
BINARY = 2
|
|
7
8
|
KTY = 'RSA'
|
|
@@ -64,9 +65,7 @@ module JWT
|
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
def []=(key, value)
|
|
67
|
-
if RSA_KEY_ELEMENTS.include?(key.to_sym)
|
|
68
|
-
raise ArgumentError, 'cannot overwrite cryptographic key attributes'
|
|
69
|
-
end
|
|
68
|
+
raise ArgumentError, 'cannot overwrite cryptographic key attributes' if RSA_KEY_ELEMENTS.include?(key.to_sym)
|
|
70
69
|
|
|
71
70
|
super(key, value)
|
|
72
71
|
end
|
data/lib/jwt/jwk/set.rb
CHANGED
|
@@ -4,6 +4,8 @@ require 'forwardable'
|
|
|
4
4
|
|
|
5
5
|
module JWT
|
|
6
6
|
module JWK
|
|
7
|
+
# JSON Web Key Set (JWKS) representation
|
|
8
|
+
# https://tools.ietf.org/html/rfc7517
|
|
7
9
|
class Set
|
|
8
10
|
include Enumerable
|
|
9
11
|
extend Forwardable
|
|
@@ -25,7 +27,7 @@ module JWT
|
|
|
25
27
|
jwks.map { |k| JWT::JWK.new(k, nil, options) }
|
|
26
28
|
else
|
|
27
29
|
raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK'
|
|
28
|
-
|
|
30
|
+
end
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def export(options = {})
|
data/lib/jwt/jwk.rb
CHANGED
|
@@ -4,6 +4,7 @@ require_relative 'jwk/key_finder'
|
|
|
4
4
|
require_relative 'jwk/set'
|
|
5
5
|
|
|
6
6
|
module JWT
|
|
7
|
+
# JSON Web Key (JWK)
|
|
7
8
|
module JWK
|
|
8
9
|
class << self
|
|
9
10
|
def create_from(key, params = nil, options = {})
|
|
@@ -52,4 +53,4 @@ require_relative 'jwk/key_base'
|
|
|
52
53
|
require_relative 'jwk/ec'
|
|
53
54
|
require_relative 'jwk/rsa'
|
|
54
55
|
require_relative 'jwk/hmac'
|
|
55
|
-
require_relative 'jwk/okp_rbnacl' if
|
|
56
|
+
require_relative 'jwk/okp_rbnacl' if JWT.rbnacl?
|
data/lib/jwt/token.rb
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JWT
|
|
4
|
+
# Represents a JWT token
|
|
5
|
+
#
|
|
6
|
+
# Basic token signed using the HS256 algorithm:
|
|
7
|
+
#
|
|
8
|
+
# token = JWT::Token.new(payload: {pay: 'load'})
|
|
9
|
+
# token.sign!(algorithm: 'HS256', key: 'secret')
|
|
10
|
+
# token.jwt # => eyJhb....
|
|
11
|
+
#
|
|
12
|
+
# Custom headers will be combined with generated headers:
|
|
13
|
+
# token = JWT::Token.new(payload: {pay: 'load'}, header: {custom: "value"})
|
|
14
|
+
# token.sign!(algorithm: 'HS256', key: 'secret')
|
|
15
|
+
# token.header # => {"custom"=>"value", "alg"=>"HS256"}
|
|
16
|
+
#
|
|
17
|
+
class Token
|
|
18
|
+
include Claims::VerificationMethods
|
|
19
|
+
|
|
20
|
+
# Initializes a new Token instance.
|
|
21
|
+
#
|
|
22
|
+
# @param header [Hash] the header of the JWT token.
|
|
23
|
+
# @param payload [Hash] the payload of the JWT token.
|
|
24
|
+
def initialize(payload:, header: {})
|
|
25
|
+
@header = header&.transform_keys(&:to_s)
|
|
26
|
+
@payload = payload
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Returns the decoded signature of the JWT token.
|
|
30
|
+
#
|
|
31
|
+
# @return [String] the decoded signature of the JWT token.
|
|
32
|
+
def signature
|
|
33
|
+
@signature ||= ::JWT::Base64.url_decode(encoded_signature || '')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns the encoded signature of the JWT token.
|
|
37
|
+
#
|
|
38
|
+
# @return [String] the encoded signature of the JWT token.
|
|
39
|
+
def encoded_signature
|
|
40
|
+
@encoded_signature ||= ::JWT::Base64.url_encode(signature)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns the decoded header of the JWT token.
|
|
44
|
+
#
|
|
45
|
+
# @return [Hash] the header of the JWT token.
|
|
46
|
+
attr_reader :header
|
|
47
|
+
|
|
48
|
+
# Returns the encoded header of the JWT token.
|
|
49
|
+
#
|
|
50
|
+
# @return [String] the encoded header of the JWT token.
|
|
51
|
+
def encoded_header
|
|
52
|
+
@encoded_header ||= ::JWT::Base64.url_encode(JWT::JSON.generate(header))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns the payload of the JWT token.
|
|
56
|
+
#
|
|
57
|
+
# @return [Hash] the payload of the JWT token.
|
|
58
|
+
attr_reader :payload
|
|
59
|
+
|
|
60
|
+
# Returns the encoded payload of the JWT token.
|
|
61
|
+
#
|
|
62
|
+
# @return [String] the encoded payload of the JWT token.
|
|
63
|
+
def encoded_payload
|
|
64
|
+
@encoded_payload ||= ::JWT::Base64.url_encode(JWT::JSON.generate(payload))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns the signing input of the JWT token.
|
|
68
|
+
#
|
|
69
|
+
# @return [String] the signing input of the JWT token.
|
|
70
|
+
def signing_input
|
|
71
|
+
@signing_input ||= [encoded_header, encoded_payload].join('.')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns the JWT token as a string.
|
|
75
|
+
#
|
|
76
|
+
# @return [String] the JWT token as a string.
|
|
77
|
+
# @raise [JWT::EncodeError] if the token is not signed or other encoding issues
|
|
78
|
+
def jwt
|
|
79
|
+
@jwt ||= (@signature && [encoded_header, @detached_payload ? '' : encoded_payload, encoded_signature].join('.')) || raise(::JWT::EncodeError, 'Token is not signed')
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Detaches the payload according to https://datatracker.ietf.org/doc/html/rfc7515#appendix-F
|
|
83
|
+
#
|
|
84
|
+
def detach_payload!
|
|
85
|
+
@detached_payload = true
|
|
86
|
+
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Signs the JWT token.
|
|
91
|
+
#
|
|
92
|
+
# @param algorithm [String, Object] the algorithm to use for signing.
|
|
93
|
+
# @param key [String] the key to use for signing.
|
|
94
|
+
# @return [void]
|
|
95
|
+
# @raise [JWT::EncodeError] if the token is already signed or other problems when signing
|
|
96
|
+
def sign!(algorithm:, key:)
|
|
97
|
+
raise ::JWT::EncodeError, 'Token already signed' if @signature
|
|
98
|
+
|
|
99
|
+
JWA.resolve(algorithm).tap do |algo|
|
|
100
|
+
header.merge!(algo.header)
|
|
101
|
+
@signature = algo.sign(data: signing_input, signing_key: key)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Returns the JWT token as a string.
|
|
108
|
+
#
|
|
109
|
+
# @return [String] the JWT token as a string.
|
|
110
|
+
alias to_s jwt
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/jwt/verify.rb
CHANGED
|
@@ -1,113 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative 'error'
|
|
4
4
|
|
|
5
5
|
module JWT
|
|
6
|
-
#
|
|
6
|
+
# @deprecated This class is deprecated and will be removed in the next major version of ruby-jwt.
|
|
7
7
|
class Verify
|
|
8
|
-
DEFAULTS = {
|
|
9
|
-
|
|
10
|
-
}.freeze
|
|
8
|
+
DEFAULTS = { leeway: 0 }.freeze
|
|
9
|
+
METHODS = %w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub verify_required_claims].freeze
|
|
11
10
|
|
|
11
|
+
private_constant(:DEFAULTS, :METHODS)
|
|
12
12
|
class << self
|
|
13
|
-
|
|
14
|
-
define_method
|
|
13
|
+
METHODS.each do |method_name|
|
|
14
|
+
define_method(method_name) do |payload, options|
|
|
15
15
|
new(payload, options).send(method_name)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# @deprecated This method is deprecated and will be removed in the next major version of ruby-jwt.
|
|
19
20
|
def verify_claims(payload, options)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Verify.send(key, payload, options) if val
|
|
24
|
-
end
|
|
21
|
+
Deprecations.warning('The ::JWT::Verify.verify_claims method is deprecated and will be removed in the next major version of ruby-jwt')
|
|
22
|
+
::JWT::Claims.verify!(payload, options)
|
|
23
|
+
true
|
|
25
24
|
end
|
|
26
25
|
end
|
|
27
26
|
|
|
27
|
+
# @deprecated This class is deprecated and will be removed in the next major version of ruby-jwt.
|
|
28
28
|
def initialize(payload, options)
|
|
29
|
+
Deprecations.warning('The ::JWT::Verify class is deprecated and will be removed in the next major version of ruby-jwt')
|
|
29
30
|
@payload = payload
|
|
30
31
|
@options = DEFAULTS.merge(options)
|
|
31
32
|
end
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
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 @payload.include?('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 @payload.include?('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')
|
|
34
|
+
METHODS.each do |method_name|
|
|
35
|
+
define_method(method_name) do
|
|
36
|
+
::JWT::Claims.verify!(@payload, @options.merge(method_name => true))
|
|
76
37
|
end
|
|
77
38
|
end
|
|
78
|
-
|
|
79
|
-
def verify_not_before
|
|
80
|
-
return unless @payload.include?('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 @payload.include?(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
39
|
end
|
|
113
40
|
end
|
data/lib/jwt/version.rb
CHANGED
|
@@ -1,43 +1,65 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# JSON Web Token implementation
|
|
4
|
+
#
|
|
5
|
+
# Should be up to date with the latest spec:
|
|
6
|
+
# https://tools.ietf.org/html/rfc7519
|
|
4
7
|
module JWT
|
|
8
|
+
# Returns the gem version of the JWT library.
|
|
9
|
+
#
|
|
10
|
+
# @return [Gem::Version] the gem version.
|
|
5
11
|
def self.gem_version
|
|
6
|
-
Gem::Version.new
|
|
12
|
+
Gem::Version.new(VERSION::STRING)
|
|
7
13
|
end
|
|
8
14
|
|
|
9
|
-
#
|
|
15
|
+
# @api private
|
|
10
16
|
module VERSION
|
|
11
|
-
# major version
|
|
12
17
|
MAJOR = 2
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# tiny version
|
|
16
|
-
TINY = 1
|
|
17
|
-
# alpha, beta, etc. tag
|
|
18
|
+
MINOR = 10
|
|
19
|
+
TINY = 3
|
|
18
20
|
PRE = nil
|
|
19
21
|
|
|
20
|
-
# Build version string
|
|
21
22
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
# Checks if the OpenSSL version is 3 or greater.
|
|
26
|
+
#
|
|
27
|
+
# @return [Boolean] true if OpenSSL version is 3 or greater, false otherwise.
|
|
28
|
+
# @api private
|
|
24
29
|
def self.openssl_3?
|
|
25
30
|
return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL')
|
|
26
|
-
|
|
31
|
+
|
|
32
|
+
true if 3 * 0x10000000 <= OpenSSL::OPENSSL_VERSION_NUMBER
|
|
27
33
|
end
|
|
28
34
|
|
|
35
|
+
# Checks if the RbNaCl library is defined.
|
|
36
|
+
#
|
|
37
|
+
# @return [Boolean] true if RbNaCl is defined, false otherwise.
|
|
38
|
+
# @api private
|
|
29
39
|
def self.rbnacl?
|
|
30
40
|
defined?(::RbNaCl)
|
|
31
41
|
end
|
|
32
42
|
|
|
43
|
+
# Checks if the RbNaCl library version is 6.0.0 or greater.
|
|
44
|
+
#
|
|
45
|
+
# @return [Boolean] true if RbNaCl version is 6.0.0 or greater, false otherwise.
|
|
46
|
+
# @api private
|
|
33
47
|
def self.rbnacl_6_or_greater?
|
|
34
48
|
rbnacl? && ::Gem::Version.new(::RbNaCl::VERSION) >= ::Gem::Version.new('6.0.0')
|
|
35
49
|
end
|
|
36
50
|
|
|
51
|
+
# Checks if there is an OpenSSL 3 HMAC empty key regression.
|
|
52
|
+
#
|
|
53
|
+
# @return [Boolean] true if there is an OpenSSL 3 HMAC empty key regression, false otherwise.
|
|
54
|
+
# @api private
|
|
37
55
|
def self.openssl_3_hmac_empty_key_regression?
|
|
38
56
|
openssl_3? && openssl_version <= ::Gem::Version.new('3.0.0')
|
|
39
57
|
end
|
|
40
58
|
|
|
59
|
+
# Returns the OpenSSL version.
|
|
60
|
+
#
|
|
61
|
+
# @return [Gem::Version] the OpenSSL version.
|
|
62
|
+
# @api private
|
|
41
63
|
def self.openssl_version
|
|
42
64
|
@openssl_version ||= ::Gem::Version.new(OpenSSL::VERSION)
|
|
43
65
|
end
|
data/lib/jwt/x5c_key_finder.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'base64'
|
|
4
|
-
require 'jwt/error'
|
|
5
|
-
|
|
6
3
|
module JWT
|
|
7
4
|
# If the x5c header certificate chain can be validated by trusted root
|
|
8
5
|
# certificates, and none of the certificates are revoked, returns the public
|
|
@@ -10,7 +7,7 @@ module JWT
|
|
|
10
7
|
# See https://tools.ietf.org/html/rfc7515#section-4.1.6
|
|
11
8
|
class X5cKeyFinder
|
|
12
9
|
def initialize(root_certificates, crls = nil)
|
|
13
|
-
raise
|
|
10
|
+
raise ArgumentError, 'Root certificates must be specified' unless root_certificates
|
|
14
11
|
|
|
15
12
|
@store = build_store(root_certificates, crls)
|
|
16
13
|
end
|
|
@@ -27,7 +24,7 @@ module JWT
|
|
|
27
24
|
error = "#{error} Certificate subject: #{current_cert.subject}."
|
|
28
25
|
end
|
|
29
26
|
|
|
30
|
-
raise
|
|
27
|
+
raise JWT::VerificationError, error
|
|
31
28
|
end
|
|
32
29
|
end
|
|
33
30
|
|
data/lib/jwt.rb
CHANGED
|
@@ -5,9 +5,16 @@ require 'jwt/base64'
|
|
|
5
5
|
require 'jwt/json'
|
|
6
6
|
require 'jwt/decode'
|
|
7
7
|
require 'jwt/configuration'
|
|
8
|
+
require 'jwt/deprecations'
|
|
8
9
|
require 'jwt/encode'
|
|
9
10
|
require 'jwt/error'
|
|
10
11
|
require 'jwt/jwk'
|
|
12
|
+
require 'jwt/claims'
|
|
13
|
+
require 'jwt/encoded_token'
|
|
14
|
+
require 'jwt/token'
|
|
15
|
+
|
|
16
|
+
require 'jwt/claims_validator'
|
|
17
|
+
require 'jwt/verify'
|
|
11
18
|
|
|
12
19
|
# JSON Web Token implementation
|
|
13
20
|
#
|
|
@@ -18,6 +25,13 @@ module JWT
|
|
|
18
25
|
|
|
19
26
|
module_function
|
|
20
27
|
|
|
28
|
+
# Encodes a payload into a JWT.
|
|
29
|
+
#
|
|
30
|
+
# @param payload [Hash] the payload to encode.
|
|
31
|
+
# @param key [String] the key used to sign the JWT.
|
|
32
|
+
# @param algorithm [String] the algorithm used to sign the JWT.
|
|
33
|
+
# @param header_fields [Hash] additional headers to include in the JWT.
|
|
34
|
+
# @return [String] the encoded JWT.
|
|
21
35
|
def encode(payload, key, algorithm = 'HS256', header_fields = {})
|
|
22
36
|
Encode.new(payload: payload,
|
|
23
37
|
key: key,
|
|
@@ -25,7 +39,16 @@ module JWT
|
|
|
25
39
|
headers: header_fields).segments
|
|
26
40
|
end
|
|
27
41
|
|
|
42
|
+
# Decodes a JWT to extract the payload and header
|
|
43
|
+
#
|
|
44
|
+
# @param jwt [String] the JWT to decode.
|
|
45
|
+
# @param key [String] the key used to verify the JWT.
|
|
46
|
+
# @param verify [Boolean] whether to verify the JWT signature.
|
|
47
|
+
# @param options [Hash] additional options for decoding.
|
|
48
|
+
# @return [Array<Hash>] the decoded payload and headers.
|
|
28
49
|
def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter
|
|
29
|
-
|
|
50
|
+
Deprecations.context do
|
|
51
|
+
Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
|
|
52
|
+
end
|
|
30
53
|
end
|
|
31
54
|
end
|
data/ruby-jwt.gemspec
CHANGED
|
@@ -31,9 +31,13 @@ Gem::Specification.new do |spec|
|
|
|
31
31
|
spec.executables = []
|
|
32
32
|
spec.require_paths = %w[lib]
|
|
33
33
|
|
|
34
|
+
spec.add_dependency 'base64'
|
|
35
|
+
|
|
34
36
|
spec.add_development_dependency 'appraisal'
|
|
35
37
|
spec.add_development_dependency 'bundler'
|
|
38
|
+
spec.add_development_dependency 'logger'
|
|
36
39
|
spec.add_development_dependency 'rake'
|
|
37
40
|
spec.add_development_dependency 'rspec'
|
|
41
|
+
spec.add_development_dependency 'rubocop'
|
|
38
42
|
spec.add_development_dependency 'simplecov'
|
|
39
43
|
end
|