jwt 1.5.4 → 2.7.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 -13
- data/AUTHORS +119 -0
- data/CHANGELOG.md +812 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +99 -0
- data/README.md +400 -79
- data/lib/jwt/algos/algo_wrapper.rb +30 -0
- data/lib/jwt/algos/ecdsa.rb +62 -0
- data/lib/jwt/algos/eddsa.rb +33 -0
- data/lib/jwt/algos/hmac.rb +73 -0
- data/lib/jwt/algos/hmac_rbnacl.rb +53 -0
- data/lib/jwt/algos/hmac_rbnacl_fixed.rb +52 -0
- data/lib/jwt/algos/none.rb +19 -0
- data/lib/jwt/algos/ps.rb +41 -0
- data/lib/jwt/algos/rsa.rb +21 -0
- data/lib/jwt/algos/unsupported.rb +19 -0
- data/lib/jwt/algos.rb +67 -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 +141 -29
- data/lib/jwt/encode.rb +79 -0
- data/lib/jwt/error.rb +10 -0
- data/lib/jwt/json.rb +11 -9
- data/lib/jwt/jwk/ec.rb +236 -0
- data/lib/jwt/jwk/hmac.rb +103 -0
- data/lib/jwt/jwk/key_base.rb +55 -0
- data/lib/jwt/jwk/key_finder.rb +46 -0
- data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
- data/lib/jwt/jwk/okp_rbnacl.rb +110 -0
- data/lib/jwt/jwk/rsa.rb +203 -0
- data/lib/jwt/jwk/set.rb +80 -0
- data/lib/jwt/jwk/thumbprint.rb +26 -0
- data/lib/jwt/jwk.rb +55 -0
- data/lib/jwt/security_utils.rb +32 -0
- data/lib/jwt/verify.rb +59 -44
- data/lib/jwt/version.rb +25 -4
- data/lib/jwt/x5c_key_finder.rb +55 -0
- data/lib/jwt.rb +16 -162
- data/ruby-jwt.gemspec +19 -9
- metadata +64 -97
- data/.codeclimate.yml +0 -20
- data/.gitignore +0 -6
- data/.rspec +0 -2
- data/.rubocop.yml +0 -2
- data/.travis.yml +0 -13
- data/Gemfile +0 -4
- data/Manifest +0 -8
- data/Rakefile +0 -1
- data/spec/fixtures/certs/ec256-private.pem +0 -8
- data/spec/fixtures/certs/ec256-public.pem +0 -4
- data/spec/fixtures/certs/ec256-wrong-private.pem +0 -8
- data/spec/fixtures/certs/ec256-wrong-public.pem +0 -4
- data/spec/fixtures/certs/ec384-private.pem +0 -9
- data/spec/fixtures/certs/ec384-public.pem +0 -5
- data/spec/fixtures/certs/ec384-wrong-private.pem +0 -9
- data/spec/fixtures/certs/ec384-wrong-public.pem +0 -5
- data/spec/fixtures/certs/ec512-private.pem +0 -10
- data/spec/fixtures/certs/ec512-public.pem +0 -6
- data/spec/fixtures/certs/ec512-wrong-private.pem +0 -10
- data/spec/fixtures/certs/ec512-wrong-public.pem +0 -6
- data/spec/fixtures/certs/rsa-1024-private.pem +0 -15
- data/spec/fixtures/certs/rsa-1024-public.pem +0 -6
- data/spec/fixtures/certs/rsa-2048-private.pem +0 -27
- data/spec/fixtures/certs/rsa-2048-public.pem +0 -9
- data/spec/fixtures/certs/rsa-2048-wrong-private.pem +0 -27
- data/spec/fixtures/certs/rsa-2048-wrong-public.pem +0 -9
- data/spec/fixtures/certs/rsa-4096-private.pem +0 -51
- data/spec/fixtures/certs/rsa-4096-public.pem +0 -14
- data/spec/jwt/verify_spec.rb +0 -175
- data/spec/jwt_spec.rb +0 -232
- data/spec/spec_helper.rb +0 -31
data/lib/jwt/verify.rb
CHANGED
@@ -1,98 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'jwt/error'
|
2
4
|
|
3
5
|
module JWT
|
4
6
|
# JWT verify methods
|
5
7
|
class Verify
|
8
|
+
DEFAULTS = {
|
9
|
+
leeway: 0
|
10
|
+
}.freeze
|
11
|
+
|
6
12
|
class << self
|
7
|
-
%w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub].each do |method_name|
|
13
|
+
%w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub verify_required_claims].each do |method_name|
|
8
14
|
define_method method_name do |payload, options|
|
9
15
|
new(payload, options).send(method_name)
|
10
16
|
end
|
11
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
|
12
26
|
end
|
13
27
|
|
14
28
|
def initialize(payload, options)
|
15
29
|
@payload = payload
|
16
|
-
@options = options
|
30
|
+
@options = DEFAULTS.merge(options)
|
17
31
|
end
|
18
32
|
|
19
33
|
def verify_aud
|
20
|
-
return unless (options_aud =
|
34
|
+
return unless (options_aud = @options[:aud])
|
21
35
|
|
22
|
-
|
23
|
-
|
24
|
-
JWT::InvalidAudError,
|
25
|
-
'Invalid audience'
|
26
|
-
) unless @payload['aud'].include?(options_aud.to_s)
|
27
|
-
else
|
28
|
-
fail(
|
29
|
-
JWT::InvalidAudError,
|
30
|
-
"Invalid audience. Expected #{options_aud}, received #{@payload['aud'] || '<none>'}"
|
31
|
-
) unless @payload['aud'].to_s == options_aud.to_s
|
32
|
-
end
|
36
|
+
aud = @payload['aud']
|
37
|
+
raise(JWT::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{aud || '<none>'}") if ([*aud] & [*options_aud]).empty?
|
33
38
|
end
|
34
39
|
|
35
40
|
def verify_expiration
|
36
41
|
return unless @payload.include?('exp')
|
37
|
-
|
38
|
-
if @payload['exp'].to_i < (Time.now.to_i - leeway)
|
39
|
-
fail(JWT::ExpiredSignature, 'Signature has expired')
|
40
|
-
end
|
42
|
+
raise(JWT::ExpiredSignature, 'Signature has expired') if @payload['exp'].to_i <= (Time.now.to_i - exp_leeway)
|
41
43
|
end
|
42
44
|
|
43
45
|
def verify_iat
|
44
46
|
return unless @payload.include?('iat')
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
end
|
48
|
+
iat = @payload['iat']
|
49
|
+
raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(Numeric) || iat.to_f > Time.now.to_f
|
49
50
|
end
|
50
51
|
|
51
52
|
def verify_iss
|
52
|
-
return unless (options_iss =
|
53
|
+
return unless (options_iss = @options[:iss])
|
54
|
+
|
55
|
+
iss = @payload['iss']
|
53
56
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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>'}")
|
59
64
|
end
|
60
65
|
end
|
61
66
|
|
62
67
|
def verify_jti
|
63
|
-
options_verify_jti =
|
68
|
+
options_verify_jti = @options[:verify_jti]
|
69
|
+
jti = @payload['jti']
|
70
|
+
|
64
71
|
if options_verify_jti.respond_to?(:call)
|
65
|
-
|
66
|
-
|
67
|
-
|
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')
|
68
76
|
end
|
69
77
|
end
|
70
78
|
|
71
79
|
def verify_not_before
|
72
80
|
return unless @payload.include?('nbf')
|
73
|
-
|
74
|
-
if @payload['nbf'].to_i > (Time.now.to_i + leeway)
|
75
|
-
fail(JWT::ImmatureSignature, 'Signature nbf has not been reached')
|
76
|
-
end
|
81
|
+
raise(JWT::ImmatureSignature, 'Signature nbf has not been reached') if @payload['nbf'].to_i > (Time.now.to_i + nbf_leeway)
|
77
82
|
end
|
78
83
|
|
79
84
|
def verify_sub
|
80
|
-
return unless (options_sub =
|
85
|
+
return unless (options_sub = @options[:sub])
|
81
86
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
86
97
|
end
|
87
98
|
|
88
99
|
private
|
89
100
|
|
90
|
-
def
|
91
|
-
@options
|
101
|
+
def global_leeway
|
102
|
+
@options[:leeway]
|
103
|
+
end
|
104
|
+
|
105
|
+
def exp_leeway
|
106
|
+
@options[:exp_leeway] || global_leeway
|
92
107
|
end
|
93
108
|
|
94
|
-
def
|
95
|
-
|
109
|
+
def nbf_leeway
|
110
|
+
@options[:nbf_leeway] || global_leeway
|
96
111
|
end
|
97
112
|
end
|
98
113
|
end
|
data/lib/jwt/version.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Moments version builder module
|
4
4
|
module JWT
|
@@ -9,15 +9,36 @@ module JWT
|
|
9
9
|
# Moments version builder module
|
10
10
|
module VERSION
|
11
11
|
# major version
|
12
|
-
MAJOR =
|
12
|
+
MAJOR = 2
|
13
13
|
# minor version
|
14
|
-
MINOR =
|
14
|
+
MINOR = 7
|
15
15
|
# tiny version
|
16
|
-
TINY =
|
16
|
+
TINY = 0
|
17
17
|
# alpha, beta, etc. tag
|
18
18
|
PRE = nil
|
19
19
|
|
20
20
|
# Build version string
|
21
21
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
22
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
|
+
|
29
|
+
def self.rbnacl?
|
30
|
+
defined?(::RbNaCl)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.rbnacl_6_or_greater?
|
34
|
+
rbnacl? && ::Gem::Version.new(::RbNaCl::VERSION) >= ::Gem::Version.new('6.0.0')
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.openssl_3_hmac_empty_key_regression?
|
38
|
+
openssl_3? && openssl_version <= ::Gem::Version.new('3.0.0')
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.openssl_version
|
42
|
+
@openssl_version ||= ::Gem::Version.new(OpenSSL::VERSION)
|
43
|
+
end
|
23
44
|
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,177 +1,31 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jwt/version'
|
4
|
+
require 'jwt/base64'
|
5
|
+
require 'jwt/json'
|
3
6
|
require 'jwt/decode'
|
7
|
+
require 'jwt/configuration'
|
8
|
+
require 'jwt/encode'
|
4
9
|
require 'jwt/error'
|
5
|
-
require 'jwt/
|
10
|
+
require 'jwt/jwk'
|
6
11
|
|
7
12
|
# JSON Web Token implementation
|
8
13
|
#
|
9
14
|
# Should be up to date with the latest spec:
|
10
|
-
# https://tools.ietf.org/html/rfc7519
|
15
|
+
# https://tools.ietf.org/html/rfc7519
|
11
16
|
module JWT
|
12
|
-
extend JWT::
|
13
|
-
|
14
|
-
NAMED_CURVES = {
|
15
|
-
'prime256v1' => 'ES256',
|
16
|
-
'secp384r1' => 'ES384',
|
17
|
-
'secp521r1' => 'ES512'
|
18
|
-
}
|
17
|
+
extend ::JWT::Configuration
|
19
18
|
|
20
19
|
module_function
|
21
20
|
|
22
|
-
def sign(algorithm, msg, key)
|
23
|
-
if %w(HS256 HS384 HS512).include?(algorithm)
|
24
|
-
sign_hmac(algorithm, msg, key)
|
25
|
-
elsif %w(RS256 RS384 RS512).include?(algorithm)
|
26
|
-
sign_rsa(algorithm, msg, key)
|
27
|
-
elsif %w(ES256 ES384 ES512).include?(algorithm)
|
28
|
-
sign_ecdsa(algorithm, msg, key)
|
29
|
-
else
|
30
|
-
fail NotImplementedError, 'Unsupported signing method'
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def sign_rsa(algorithm, msg, private_key)
|
35
|
-
private_key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
|
36
|
-
end
|
37
|
-
|
38
|
-
def sign_ecdsa(algorithm, msg, private_key)
|
39
|
-
key_algorithm = NAMED_CURVES[private_key.group.curve_name]
|
40
|
-
if algorithm != key_algorithm
|
41
|
-
fail IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
|
42
|
-
end
|
43
|
-
|
44
|
-
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
|
45
|
-
asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
|
46
|
-
end
|
47
|
-
|
48
|
-
def verify_rsa(algorithm, public_key, signing_input, signature)
|
49
|
-
public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
|
50
|
-
end
|
51
|
-
|
52
|
-
def verify_ecdsa(algorithm, public_key, signing_input, signature)
|
53
|
-
key_algorithm = NAMED_CURVES[public_key.group.curve_name]
|
54
|
-
if algorithm != key_algorithm
|
55
|
-
fail IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
|
56
|
-
end
|
57
|
-
|
58
|
-
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
|
59
|
-
public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
|
60
|
-
end
|
61
|
-
|
62
|
-
def sign_hmac(algorithm, msg, key)
|
63
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
|
64
|
-
end
|
65
|
-
|
66
|
-
def base64url_encode(str)
|
67
|
-
Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
|
68
|
-
end
|
69
|
-
|
70
|
-
def encoded_header(algorithm = 'HS256', header_fields = {})
|
71
|
-
header = { 'typ' => 'JWT', 'alg' => algorithm }.merge(header_fields)
|
72
|
-
base64url_encode(encode_json(header))
|
73
|
-
end
|
74
|
-
|
75
|
-
def encoded_payload(payload)
|
76
|
-
base64url_encode(encode_json(payload))
|
77
|
-
end
|
78
|
-
|
79
|
-
def encoded_signature(signing_input, key, algorithm)
|
80
|
-
if algorithm == 'none'
|
81
|
-
''
|
82
|
-
else
|
83
|
-
signature = sign(algorithm, signing_input, key)
|
84
|
-
base64url_encode(signature)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
21
|
def encode(payload, key, algorithm = 'HS256', header_fields = {})
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
segments << encoded_signature(segments.join('.'), key, algorithm)
|
94
|
-
segments.join('.')
|
95
|
-
end
|
96
|
-
|
97
|
-
def decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
|
98
|
-
fail(JWT::DecodeError, 'Nil JSON web token') unless jwt
|
99
|
-
|
100
|
-
options = {
|
101
|
-
verify_expiration: true,
|
102
|
-
verify_not_before: true,
|
103
|
-
verify_iss: false,
|
104
|
-
verify_iat: false,
|
105
|
-
verify_jti: false,
|
106
|
-
verify_aud: false,
|
107
|
-
verify_sub: false,
|
108
|
-
leeway: 0
|
109
|
-
}
|
110
|
-
|
111
|
-
merged_options = options.merge(custom_options)
|
112
|
-
|
113
|
-
decoder = Decode.new jwt, key, verify, merged_options, &keyfinder
|
114
|
-
header, payload, signature, signing_input = decoder.decode_segments
|
115
|
-
decoder.verify
|
116
|
-
|
117
|
-
fail(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
|
118
|
-
|
119
|
-
if verify
|
120
|
-
algo, key = signature_algorithm_and_key(header, key, &keyfinder)
|
121
|
-
if merged_options[:algorithm] && algo != merged_options[:algorithm]
|
122
|
-
fail JWT::IncorrectAlgorithm, 'Expected a different algorithm'
|
123
|
-
end
|
124
|
-
verify_signature(algo, key, signing_input, signature)
|
125
|
-
end
|
126
|
-
|
127
|
-
[payload, header]
|
128
|
-
end
|
129
|
-
|
130
|
-
def signature_algorithm_and_key(header, key, &keyfinder)
|
131
|
-
key = keyfinder.call(header) if keyfinder
|
132
|
-
[header['alg'], key]
|
133
|
-
end
|
134
|
-
|
135
|
-
def verify_signature(algo, key, signing_input, signature)
|
136
|
-
if %w(HS256 HS384 HS512).include?(algo)
|
137
|
-
fail(JWT::VerificationError, 'Signature verification raised') unless secure_compare(signature, sign_hmac(algo, signing_input, key))
|
138
|
-
elsif %w(RS256 RS384 RS512).include?(algo)
|
139
|
-
fail(JWT::VerificationError, 'Signature verification raised') unless verify_rsa(algo, key, signing_input, signature)
|
140
|
-
elsif %w(ES256 ES384 ES512).include?(algo)
|
141
|
-
fail(JWT::VerificationError, 'Signature verification raised') unless verify_ecdsa(algo, key, signing_input, signature)
|
142
|
-
else
|
143
|
-
fail JWT::VerificationError, 'Algorithm not supported'
|
144
|
-
end
|
145
|
-
rescue OpenSSL::PKey::PKeyError
|
146
|
-
raise JWT::VerificationError, 'Signature verification raised'
|
147
|
-
ensure
|
148
|
-
OpenSSL.errors.clear
|
149
|
-
end
|
150
|
-
|
151
|
-
# From devise
|
152
|
-
# constant-time comparison algorithm to prevent timing attacks
|
153
|
-
def secure_compare(a, b)
|
154
|
-
return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize
|
155
|
-
l = a.unpack "C#{a.bytesize}"
|
156
|
-
|
157
|
-
res = 0
|
158
|
-
b.each_byte { |byte| res |= byte ^ l.shift }
|
159
|
-
res == 0
|
160
|
-
end
|
161
|
-
|
162
|
-
def raw_to_asn1(signature, private_key)
|
163
|
-
byte_size = (private_key.group.degree + 7) / 8
|
164
|
-
r = signature[0..(byte_size - 1)]
|
165
|
-
s = signature[byte_size..-1]
|
166
|
-
OpenSSL::ASN1::Sequence.new([r, s].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
|
167
|
-
end
|
168
|
-
|
169
|
-
def asn1_to_raw(signature, public_key)
|
170
|
-
byte_size = (public_key.group.degree + 7) / 8
|
171
|
-
OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
|
22
|
+
Encode.new(payload: payload,
|
23
|
+
key: key,
|
24
|
+
algorithm: algorithm,
|
25
|
+
headers: header_fields).segments
|
172
26
|
end
|
173
27
|
|
174
|
-
def
|
175
|
-
Decode.
|
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
|
176
30
|
end
|
177
31
|
end
|
data/ruby-jwt.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
5
|
require 'jwt/version'
|
4
6
|
|
@@ -6,24 +8,32 @@ Gem::Specification.new do |spec|
|
|
6
8
|
spec.name = 'jwt'
|
7
9
|
spec.version = JWT.gem_version
|
8
10
|
spec.authors = [
|
9
|
-
'Jeff Lindsay',
|
10
11
|
'Tim Rudat'
|
11
12
|
]
|
12
13
|
spec.email = 'timrudat@gmail.com'
|
13
14
|
spec.summary = 'JSON Web Token implementation in Ruby'
|
14
15
|
spec.description = 'A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.'
|
15
|
-
spec.homepage = '
|
16
|
+
spec.homepage = 'https://github.com/jwt/ruby-jwt'
|
16
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
|
+
'rubygems_mfa_required' => 'true'
|
23
|
+
}
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
f.match(%r{^(spec|gemfiles|coverage|bin)/}) || # Irrelevant folders
|
27
|
+
f.match(/^\.+/) || # Files and folders starting with .
|
28
|
+
f.match(/^(Appraisals|Gemfile|Rakefile)$/) # Irrelevant files
|
29
|
+
end
|
17
30
|
|
18
|
-
spec.
|
19
|
-
spec.
|
20
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
-
spec.require_paths = %w(lib)
|
31
|
+
spec.executables = []
|
32
|
+
spec.require_paths = %w[lib]
|
22
33
|
|
34
|
+
spec.add_development_dependency 'appraisal'
|
23
35
|
spec.add_development_dependency 'bundler'
|
24
36
|
spec.add_development_dependency 'rake'
|
25
37
|
spec.add_development_dependency 'rspec'
|
26
38
|
spec.add_development_dependency 'simplecov'
|
27
|
-
spec.add_development_dependency 'simplecov-json'
|
28
|
-
spec.add_development_dependency 'codeclimate-test-reporter'
|
29
39
|
end
|