jwt 1.5.0 → 2.5.0
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 +5 -5
- data/.codeclimate.yml +8 -0
- data/.github/workflows/coverage.yml +27 -0
- data/.github/workflows/test.yml +67 -0
- data/.gitignore +13 -0
- data/.reek.yml +22 -0
- data/.rspec +2 -0
- data/.rubocop.yml +67 -0
- data/.sourcelevel.yml +17 -0
- data/AUTHORS +119 -0
- data/Appraisals +13 -0
- data/CHANGELOG.md +786 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +99 -0
- data/Gemfile +7 -0
- data/LICENSE +7 -0
- data/README.md +639 -0
- data/Rakefile +13 -14
- data/lib/jwt/algos/ecdsa.rb +64 -0
- data/lib/jwt/algos/eddsa.rb +35 -0
- data/lib/jwt/algos/hmac.rb +36 -0
- data/lib/jwt/algos/none.rb +17 -0
- data/lib/jwt/algos/ps.rb +43 -0
- data/lib/jwt/algos/rsa.rb +22 -0
- data/lib/jwt/algos/unsupported.rb +19 -0
- data/lib/jwt/algos.rb +44 -0
- data/lib/jwt/base64.rb +19 -0
- data/lib/jwt/claims_validator.rb +37 -0
- data/lib/jwt/configuration/container.rb +21 -0
- data/lib/jwt/configuration/decode_configuration.rb +46 -0
- data/lib/jwt/configuration/jwk_configuration.rb +27 -0
- data/lib/jwt/configuration.rb +15 -0
- data/lib/jwt/decode.rb +145 -0
- data/lib/jwt/encode.rb +69 -0
- data/lib/jwt/error.rb +22 -0
- data/lib/jwt/json.rb +10 -22
- data/lib/jwt/jwk/ec.rb +199 -0
- data/lib/jwt/jwk/hmac.rb +67 -0
- data/lib/jwt/jwk/key_base.rb +35 -0
- data/lib/jwt/jwk/key_finder.rb +62 -0
- data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
- data/lib/jwt/jwk/rsa.rb +138 -0
- data/lib/jwt/jwk/thumbprint.rb +26 -0
- data/lib/jwt/jwk.rb +52 -0
- data/lib/jwt/security_utils.rb +59 -0
- data/lib/jwt/signature.rb +35 -0
- data/lib/jwt/verify.rb +113 -0
- data/lib/jwt/version.rb +28 -0
- data/lib/jwt/x5c_key_finder.rb +55 -0
- data/lib/jwt.rb +20 -215
- data/ruby-jwt.gemspec +35 -0
- metadata +138 -30
- data/Manifest +0 -6
- data/jwt.gemspec +0 -34
- data/spec/helper.rb +0 -2
- data/spec/jwt_spec.rb +0 -434
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jwt/security_utils'
|
4
|
+
require 'openssl'
|
5
|
+
require 'jwt/algos'
|
6
|
+
begin
|
7
|
+
require 'rbnacl'
|
8
|
+
rescue LoadError
|
9
|
+
raise if defined?(RbNaCl)
|
10
|
+
end
|
11
|
+
|
12
|
+
# JWT::Signature module
|
13
|
+
module JWT
|
14
|
+
# Signature logic for JWT
|
15
|
+
module Signature
|
16
|
+
module_function
|
17
|
+
|
18
|
+
ToSign = Struct.new(:algorithm, :msg, :key)
|
19
|
+
ToVerify = Struct.new(:algorithm, :public_key, :signing_input, :signature)
|
20
|
+
|
21
|
+
def sign(algorithm, msg, key)
|
22
|
+
algo, code = Algos.find(algorithm)
|
23
|
+
algo.sign ToSign.new(code, msg, key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def verify(algorithm, key, signing_input, signature)
|
27
|
+
algo, code = Algos.find(algorithm)
|
28
|
+
algo.verify(ToVerify.new(code, key, signing_input, signature))
|
29
|
+
rescue OpenSSL::PKey::PKeyError
|
30
|
+
raise JWT::VerificationError, 'Signature verification raised'
|
31
|
+
ensure
|
32
|
+
OpenSSL.errors.clear
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/jwt/verify.rb
ADDED
@@ -0,0 +1,113 @@
|
|
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 @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')
|
76
|
+
end
|
77
|
+
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
|
+
end
|
113
|
+
end
|
data/lib/jwt/version.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Moments version builder module
|
4
|
+
module JWT
|
5
|
+
def self.gem_version
|
6
|
+
Gem::Version.new VERSION::STRING
|
7
|
+
end
|
8
|
+
|
9
|
+
# Moments version builder module
|
10
|
+
module VERSION
|
11
|
+
# major version
|
12
|
+
MAJOR = 2
|
13
|
+
# minor version
|
14
|
+
MINOR = 5
|
15
|
+
# tiny version
|
16
|
+
TINY = 0
|
17
|
+
# alpha, beta, etc. tag
|
18
|
+
PRE = nil
|
19
|
+
|
20
|
+
# Build version string
|
21
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.openssl_3?
|
25
|
+
return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL')
|
26
|
+
return true if OpenSSL::OPENSSL_VERSION_NUMBER >= 3 * 0x10000000
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'jwt/error'
|
5
|
+
|
6
|
+
module JWT
|
7
|
+
# If the x5c header certificate chain can be validated by trusted root
|
8
|
+
# certificates, and none of the certificates are revoked, returns the public
|
9
|
+
# key from the first certificate.
|
10
|
+
# See https://tools.ietf.org/html/rfc7515#section-4.1.6
|
11
|
+
class X5cKeyFinder
|
12
|
+
def initialize(root_certificates, crls = nil)
|
13
|
+
raise(ArgumentError, 'Root certificates must be specified') unless root_certificates
|
14
|
+
|
15
|
+
@store = build_store(root_certificates, crls)
|
16
|
+
end
|
17
|
+
|
18
|
+
def from(x5c_header_or_certificates)
|
19
|
+
signing_certificate, *certificate_chain = parse_certificates(x5c_header_or_certificates)
|
20
|
+
store_context = OpenSSL::X509::StoreContext.new(@store, signing_certificate, certificate_chain)
|
21
|
+
|
22
|
+
if store_context.verify
|
23
|
+
signing_certificate.public_key
|
24
|
+
else
|
25
|
+
error = "Certificate verification failed: #{store_context.error_string}."
|
26
|
+
if (current_cert = store_context.current_cert)
|
27
|
+
error = "#{error} Certificate subject: #{current_cert.subject}."
|
28
|
+
end
|
29
|
+
|
30
|
+
raise(JWT::VerificationError, error)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def build_store(root_certificates, crls)
|
37
|
+
store = OpenSSL::X509::Store.new
|
38
|
+
store.purpose = OpenSSL::X509::PURPOSE_ANY
|
39
|
+
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
|
40
|
+
root_certificates.each { |certificate| store.add_cert(certificate) }
|
41
|
+
crls&.each { |crl| store.add_crl(crl) }
|
42
|
+
store
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_certificates(x5c_header_or_certificates)
|
46
|
+
if x5c_header_or_certificates.all? { |obj| obj.is_a?(OpenSSL::X509::Certificate) }
|
47
|
+
x5c_header_or_certificates
|
48
|
+
else
|
49
|
+
x5c_header_or_certificates.map do |encoded|
|
50
|
+
OpenSSL::X509::Certificate.new(::JWT::Base64.url_decode(encoded))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/jwt.rb
CHANGED
@@ -1,226 +1,31 @@
|
|
1
|
-
#
|
2
|
-
# JSON Web Token implementation
|
3
|
-
#
|
4
|
-
# Should be up to date with the latest spec:
|
5
|
-
# http://self-issued.info/docs/draft-jones-json-web-token-06.html
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
require '
|
8
|
-
require '
|
3
|
+
require 'jwt/version'
|
4
|
+
require 'jwt/base64'
|
9
5
|
require 'jwt/json'
|
6
|
+
require 'jwt/decode'
|
7
|
+
require 'jwt/configuration'
|
8
|
+
require 'jwt/encode'
|
9
|
+
require 'jwt/error'
|
10
|
+
require 'jwt/jwk'
|
10
11
|
|
12
|
+
# JSON Web Token implementation
|
13
|
+
#
|
14
|
+
# Should be up to date with the latest spec:
|
15
|
+
# https://tools.ietf.org/html/rfc7519
|
11
16
|
module JWT
|
12
|
-
|
13
|
-
class VerificationError < DecodeError; end
|
14
|
-
class ExpiredSignature < DecodeError; end
|
15
|
-
class IncorrectAlgorithm < DecodeError; end
|
16
|
-
class ImmatureSignature < DecodeError; end
|
17
|
-
class InvalidIssuerError < DecodeError; end
|
18
|
-
class InvalidIatError < DecodeError; end
|
19
|
-
class InvalidAudError < DecodeError; end
|
20
|
-
class InvalidSubError < DecodeError; end
|
21
|
-
class InvalidJtiError < DecodeError; end
|
22
|
-
extend JWT::Json
|
23
|
-
|
24
|
-
NAMED_CURVES = {
|
25
|
-
'prime256v1' => 'ES256',
|
26
|
-
'secp384r1' => 'ES384',
|
27
|
-
'secp521r1' => 'ES512',
|
28
|
-
}
|
17
|
+
extend ::JWT::Configuration
|
29
18
|
|
30
19
|
module_function
|
31
20
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
elsif ['ES256', 'ES384', 'ES512'].include?(algorithm)
|
38
|
-
sign_ecdsa(algorithm, msg, key)
|
39
|
-
else
|
40
|
-
raise NotImplementedError.new('Unsupported signing method')
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def sign_rsa(algorithm, msg, private_key)
|
45
|
-
private_key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
|
46
|
-
end
|
47
|
-
|
48
|
-
def sign_ecdsa(algorithm, msg, private_key)
|
49
|
-
key_algorithm = NAMED_CURVES[private_key.group.curve_name]
|
50
|
-
if algorithm != key_algorithm
|
51
|
-
raise IncorrectAlgorithm.new("payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided")
|
52
|
-
end
|
53
|
-
|
54
|
-
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
|
55
|
-
private_key.dsa_sign_asn1(digest.digest(msg))
|
56
|
-
end
|
57
|
-
|
58
|
-
def verify_rsa(algorithm, public_key, signing_input, signature)
|
59
|
-
public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
|
60
|
-
end
|
61
|
-
|
62
|
-
def verify_ecdsa(algorithm, public_key, signing_input, signature)
|
63
|
-
key_algorithm = NAMED_CURVES[public_key.group.curve_name]
|
64
|
-
if algorithm != key_algorithm
|
65
|
-
raise IncorrectAlgorithm.new("payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided")
|
66
|
-
end
|
67
|
-
|
68
|
-
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
|
69
|
-
public_key.dsa_verify_asn1(digest.digest(signing_input), signature)
|
70
|
-
end
|
71
|
-
|
72
|
-
def sign_hmac(algorithm, msg, key)
|
73
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
|
74
|
-
end
|
75
|
-
|
76
|
-
def base64url_decode(str)
|
77
|
-
str += '=' * (4 - str.length.modulo(4))
|
78
|
-
Base64.decode64(str.tr('-_', '+/'))
|
79
|
-
end
|
80
|
-
|
81
|
-
def base64url_encode(str)
|
82
|
-
Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
|
83
|
-
end
|
84
|
-
|
85
|
-
def encoded_header(algorithm='HS256', header_fields={})
|
86
|
-
header = {'typ' => 'JWT', 'alg' => algorithm}.merge(header_fields)
|
87
|
-
base64url_encode(encode_json(header))
|
21
|
+
def encode(payload, key, algorithm = 'HS256', header_fields = {})
|
22
|
+
Encode.new(payload: payload,
|
23
|
+
key: key,
|
24
|
+
algorithm: algorithm,
|
25
|
+
headers: header_fields).segments
|
88
26
|
end
|
89
27
|
|
90
|
-
def
|
91
|
-
|
28
|
+
def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter
|
29
|
+
Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
|
92
30
|
end
|
93
|
-
|
94
|
-
def encoded_signature(signing_input, key, algorithm)
|
95
|
-
if algorithm == 'none'
|
96
|
-
''
|
97
|
-
else
|
98
|
-
signature = sign(algorithm, signing_input, key)
|
99
|
-
base64url_encode(signature)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def encode(payload, key, algorithm='HS256', header_fields={})
|
104
|
-
algorithm ||= 'none'
|
105
|
-
segments = []
|
106
|
-
segments << encoded_header(algorithm, header_fields)
|
107
|
-
segments << encoded_payload(payload)
|
108
|
-
segments << encoded_signature(segments.join('.'), key, algorithm)
|
109
|
-
segments.join('.')
|
110
|
-
end
|
111
|
-
|
112
|
-
def raw_segments(jwt, verify=true)
|
113
|
-
segments = jwt.split('.')
|
114
|
-
required_num_segments = verify ? [3] : [2,3]
|
115
|
-
raise JWT::DecodeError.new('Not enough or too many segments') unless required_num_segments.include? segments.length
|
116
|
-
segments
|
117
|
-
end
|
118
|
-
|
119
|
-
def decode_header_and_payload(header_segment, payload_segment)
|
120
|
-
header = decode_json(base64url_decode(header_segment))
|
121
|
-
payload = decode_json(base64url_decode(payload_segment))
|
122
|
-
[header, payload]
|
123
|
-
end
|
124
|
-
|
125
|
-
def decoded_segments(jwt, verify=true)
|
126
|
-
header_segment, payload_segment, crypto_segment = raw_segments(jwt, verify)
|
127
|
-
header, payload = decode_header_and_payload(header_segment, payload_segment)
|
128
|
-
signature = base64url_decode(crypto_segment.to_s) if verify
|
129
|
-
signing_input = [header_segment, payload_segment].join('.')
|
130
|
-
[header, payload, signature, signing_input]
|
131
|
-
end
|
132
|
-
|
133
|
-
def decode(jwt, key=nil, verify=true, options={}, &keyfinder)
|
134
|
-
raise JWT::DecodeError.new('Nil JSON web token') unless jwt
|
135
|
-
|
136
|
-
header, payload, signature, signing_input = decoded_segments(jwt, verify)
|
137
|
-
raise JWT::DecodeError.new('Not enough or too many segments') unless header && payload
|
138
|
-
|
139
|
-
default_options = {
|
140
|
-
:verify_expiration => true,
|
141
|
-
:verify_not_before => true,
|
142
|
-
:verify_iss => false,
|
143
|
-
:verify_iat => false,
|
144
|
-
:verify_jti => false,
|
145
|
-
:verify_aud => false,
|
146
|
-
:verify_sub => false,
|
147
|
-
:leeway => 0
|
148
|
-
}
|
149
|
-
|
150
|
-
options = default_options.merge(options)
|
151
|
-
|
152
|
-
if verify
|
153
|
-
algo, key = signature_algorithm_and_key(header, key, &keyfinder)
|
154
|
-
if options[:algorithm] && algo != options[:algorithm]
|
155
|
-
raise JWT::IncorrectAlgorithm.new('Expected a different algorithm')
|
156
|
-
end
|
157
|
-
verify_signature(algo, key, signing_input, signature)
|
158
|
-
end
|
159
|
-
|
160
|
-
if options[:verify_expiration] && payload.include?('exp')
|
161
|
-
raise JWT::ExpiredSignature.new('Signature has expired') unless payload['exp'].to_i > (Time.now.to_i - options[:leeway])
|
162
|
-
end
|
163
|
-
if options[:verify_not_before] && payload.include?('nbf')
|
164
|
-
raise JWT::ImmatureSignature.new('Signature nbf has not been reached') unless payload['nbf'].to_i < (Time.now.to_i + options[:leeway])
|
165
|
-
end
|
166
|
-
if options[:verify_iss] && payload.include?('iss')
|
167
|
-
raise JWT::InvalidIssuerError.new("Invalid issuer. Expected #{options['iss']}, received #{payload['iss']}") unless payload['iss'].to_s == options['iss'].to_s
|
168
|
-
end
|
169
|
-
if options[:verify_iat] && payload.include?('iat')
|
170
|
-
raise JWT::InvalidIatError.new('Invalid iat') unless (payload['iat'].is_a?(Integer) and payload['iat'].to_i <= Time.now.to_i)
|
171
|
-
end
|
172
|
-
if options[:verify_aud] && payload.include?('aud')
|
173
|
-
if payload['aud'].is_a?(Array)
|
174
|
-
raise JWT::InvalidAudError.new('Invalid audience') unless payload['aud'].include?(options['aud'])
|
175
|
-
else
|
176
|
-
raise JWT::InvalidAudError.new("Invalid audience. Expected #{options['aud']}, received #{payload['aud']}") unless payload['aud'].to_s == options['aud'].to_s
|
177
|
-
end
|
178
|
-
end
|
179
|
-
if options[:verify_sub] && payload.include?('sub')
|
180
|
-
raise JWT::InvalidSubError.new("Invalid subject. Expected #{options['sub']}, received #{payload['sub']}") unless payload['sub'].to_s == options['sub'].to_s
|
181
|
-
end
|
182
|
-
if options[:verify_jti] && payload.include?('jti')
|
183
|
-
raise JWT::InvalidJtiError.new('need iat for verify jwt id') unless payload.include?('iat')
|
184
|
-
raise JWT::InvalidJtiError.new('Not a uniq jwt id') unless options['jti'].to_s == Digest::MD5.hexdigest("#{key}:#{payload['iat']}")
|
185
|
-
end
|
186
|
-
|
187
|
-
return payload,header
|
188
|
-
end
|
189
|
-
|
190
|
-
def signature_algorithm_and_key(header, key, &keyfinder)
|
191
|
-
if keyfinder
|
192
|
-
key = keyfinder.call(header)
|
193
|
-
end
|
194
|
-
[header['alg'], key]
|
195
|
-
end
|
196
|
-
|
197
|
-
def verify_signature(algo, key, signing_input, signature)
|
198
|
-
begin
|
199
|
-
if ['HS256', 'HS384', 'HS512'].include?(algo)
|
200
|
-
raise JWT::VerificationError.new('Signature verification failed') unless secure_compare(signature, sign_hmac(algo, signing_input, key))
|
201
|
-
elsif ['RS256', 'RS384', 'RS512'].include?(algo)
|
202
|
-
raise JWT::VerificationError.new('Signature verification failed') unless verify_rsa(algo, key, signing_input, signature)
|
203
|
-
elsif ['ES256', 'ES384', 'ES512'].include?(algo)
|
204
|
-
raise JWT::VerificationError.new('Signature verification failed') unless verify_ecdsa(algo, key, signing_input, signature)
|
205
|
-
else
|
206
|
-
raise JWT::VerificationError.new('Algorithm not supported')
|
207
|
-
end
|
208
|
-
rescue OpenSSL::PKey::PKeyError
|
209
|
-
raise JWT::VerificationError.new('Signature verification failed')
|
210
|
-
ensure
|
211
|
-
OpenSSL.errors.clear
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
# From devise
|
216
|
-
# constant-time comparison algorithm to prevent timing attacks
|
217
|
-
def secure_compare(a, b)
|
218
|
-
return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize
|
219
|
-
l = a.unpack "C#{a.bytesize}"
|
220
|
-
|
221
|
-
res = 0
|
222
|
-
b.each_byte { |byte| res |= byte ^ l.shift }
|
223
|
-
res == 0
|
224
|
-
end
|
225
|
-
|
226
31
|
end
|
data/ruby-jwt.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'jwt/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'jwt'
|
9
|
+
spec.version = JWT.gem_version
|
10
|
+
spec.authors = [
|
11
|
+
'Tim Rudat'
|
12
|
+
]
|
13
|
+
spec.email = 'timrudat@gmail.com'
|
14
|
+
spec.summary = 'JSON Web Token implementation in Ruby'
|
15
|
+
spec.description = 'A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.'
|
16
|
+
spec.homepage = 'https://github.com/jwt/ruby-jwt'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
spec.required_ruby_version = '>= 2.5'
|
19
|
+
spec.metadata = {
|
20
|
+
'bug_tracker_uri' => 'https://github.com/jwt/ruby-jwt/issues',
|
21
|
+
'changelog_uri' => "https://github.com/jwt/ruby-jwt/blob/v#{JWT.gem_version}/CHANGELOG.md"
|
22
|
+
}
|
23
|
+
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|gemfiles|coverage|bin)/}) }
|
25
|
+
spec.executables = []
|
26
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
27
|
+
spec.require_paths = %w[lib]
|
28
|
+
|
29
|
+
spec.add_development_dependency 'appraisal'
|
30
|
+
spec.add_development_dependency 'bundler'
|
31
|
+
spec.add_development_dependency 'rake'
|
32
|
+
spec.add_development_dependency 'reek'
|
33
|
+
spec.add_development_dependency 'rspec'
|
34
|
+
spec.add_development_dependency 'simplecov'
|
35
|
+
end
|