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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +149 -31
- data/CODE_OF_CONDUCT.md +14 -14
- data/CONTRIBUTING.md +9 -10
- data/README.md +299 -234
- data/UPGRADING.md +47 -0
- data/lib/jwt/base64.rb +4 -10
- 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 +45 -0
- data/lib/jwt/claims/required.rb +33 -0
- data/lib/jwt/claims/subject.rb +30 -0
- data/lib/jwt/claims/verifier.rb +61 -0
- data/lib/jwt/claims.rb +67 -0
- data/lib/jwt/configuration/container.rb +20 -1
- data/lib/jwt/configuration/decode_configuration.rb +24 -0
- data/lib/jwt/configuration/jwk_configuration.rb +1 -0
- data/lib/jwt/configuration.rb +8 -0
- data/lib/jwt/decode.rb +42 -81
- data/lib/jwt/encode.rb +17 -60
- data/lib/jwt/encoded_token.rb +236 -0
- data/lib/jwt/error.rb +32 -1
- data/lib/jwt/json.rb +1 -1
- data/lib/jwt/jwa/ecdsa.rb +59 -24
- data/lib/jwt/jwa/hmac.rb +22 -19
- data/lib/jwt/jwa/none.rb +8 -3
- data/lib/jwt/jwa/ps.rb +21 -15
- data/lib/jwt/jwa/rsa.rb +21 -10
- data/lib/jwt/jwa/signing_algorithm.rb +62 -0
- data/lib/jwt/jwa/unsupported.rb +9 -8
- data/lib/jwt/jwa.rb +76 -35
- data/lib/jwt/jwk/ec.rb +54 -65
- data/lib/jwt/jwk/hmac.rb +5 -6
- data/lib/jwt/jwk/key_base.rb +16 -1
- data/lib/jwt/jwk/key_finder.rb +35 -8
- data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
- data/lib/jwt/jwk/rsa.rb +7 -4
- data/lib/jwt/jwk/set.rb +2 -0
- data/lib/jwt/jwk.rb +1 -1
- data/lib/jwt/token.rb +131 -0
- data/lib/jwt/version.rb +24 -19
- data/lib/jwt.rb +18 -4
- data/ruby-jwt.gemspec +2 -0
- metadata +49 -15
- data/lib/jwt/claims_validator.rb +0 -37
- data/lib/jwt/deprecations.rb +0 -48
- data/lib/jwt/jwa/eddsa.rb +0 -42
- data/lib/jwt/jwa/hmac_rbnacl.rb +0 -50
- data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +0 -46
- data/lib/jwt/jwa/wrapper.rb +0 -26
- data/lib/jwt/jwk/okp_rbnacl.rb +0 -110
- data/lib/jwt/verify.rb +0 -117
data/lib/jwt/deprecations.rb
DELETED
@@ -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
|
data/lib/jwt/jwa/hmac_rbnacl.rb
DELETED
@@ -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
|
data/lib/jwt/jwa/wrapper.rb
DELETED
@@ -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
|
data/lib/jwt/jwk/okp_rbnacl.rb
DELETED
@@ -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
|