jwtb 2.0.0.beta2.bsk1
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 +7 -0
- data/.codeclimate.yml +20 -0
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +411 -0
- data/Gemfile +4 -0
- data/LICENSE +7 -0
- data/Manifest +8 -0
- data/README.md +443 -0
- data/Rakefile +11 -0
- data/jwtb.gemspec +31 -0
- data/lib/jwtb.rb +67 -0
- data/lib/jwtb/decode.rb +45 -0
- data/lib/jwtb/default_options.rb +14 -0
- data/lib/jwtb/encode.rb +51 -0
- data/lib/jwtb/error.rb +15 -0
- data/lib/jwtb/signature.rb +146 -0
- data/lib/jwtb/verify.rb +84 -0
- data/lib/jwtb/version.rb +24 -0
- data/spec/fixtures/certs/ec256-private.pem +8 -0
- data/spec/fixtures/certs/ec256-public.pem +4 -0
- data/spec/fixtures/certs/ec256-wrong-private.pem +8 -0
- data/spec/fixtures/certs/ec256-wrong-public.pem +4 -0
- data/spec/fixtures/certs/ec384-private.pem +9 -0
- data/spec/fixtures/certs/ec384-public.pem +5 -0
- data/spec/fixtures/certs/ec384-wrong-private.pem +9 -0
- data/spec/fixtures/certs/ec384-wrong-public.pem +5 -0
- data/spec/fixtures/certs/ec512-private.pem +10 -0
- data/spec/fixtures/certs/ec512-public.pem +6 -0
- data/spec/fixtures/certs/ec512-wrong-private.pem +10 -0
- data/spec/fixtures/certs/ec512-wrong-public.pem +6 -0
- data/spec/fixtures/certs/rsa-1024-private.pem +15 -0
- data/spec/fixtures/certs/rsa-1024-public.pem +6 -0
- data/spec/fixtures/certs/rsa-2048-private.pem +27 -0
- data/spec/fixtures/certs/rsa-2048-public.pem +9 -0
- data/spec/fixtures/certs/rsa-2048-wrong-private.pem +27 -0
- data/spec/fixtures/certs/rsa-2048-wrong-public.pem +9 -0
- data/spec/fixtures/certs/rsa-4096-private.pem +51 -0
- data/spec/fixtures/certs/rsa-4096-public.pem +14 -0
- data/spec/integration/readme_examples_spec.rb +216 -0
- data/spec/jwtb/verify_spec.rb +190 -0
- data/spec/jwtb_spec.rb +233 -0
- data/spec/spec_helper.rb +28 -0
- metadata +225 -0
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:test)
|
7
|
+
|
8
|
+
task default: :test
|
9
|
+
rescue LoadError
|
10
|
+
puts 'RSpec rake tasks not available. Please run "bundle install" to install missing dependencies.'
|
11
|
+
end
|
data/jwtb.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'jwtb/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'jwtb'
|
7
|
+
spec.version = JWTB.gem_version
|
8
|
+
spec.authors = [
|
9
|
+
'Tim Rudat', 'Larry Salibra'
|
10
|
+
]
|
11
|
+
spec.email = 'timrudat@gmail.com'
|
12
|
+
spec.summary = 'JSON Web Token implementation with Blockstack support in Ruby '
|
13
|
+
spec.description = 'A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard with additional support for Blockstack.'
|
14
|
+
spec.homepage = 'http://github.com/blockstack/ruby-jwt-blockstack'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
spec.required_ruby_version = '~> 2.1'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = %w(lib)
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'rspec'
|
26
|
+
spec.add_development_dependency 'simplecov'
|
27
|
+
spec.add_development_dependency 'simplecov-json'
|
28
|
+
spec.add_development_dependency 'codeclimate-test-reporter'
|
29
|
+
spec.add_development_dependency 'codacy-coverage'
|
30
|
+
spec.add_development_dependency 'rbnacl'
|
31
|
+
end
|
data/lib/jwtb.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'base64'
|
3
|
+
require 'jwtb/decode'
|
4
|
+
require 'jwtb/default_options'
|
5
|
+
require 'jwtb/encode'
|
6
|
+
require 'jwtb/error'
|
7
|
+
require 'jwtb/signature'
|
8
|
+
require 'jwtb/verify'
|
9
|
+
|
10
|
+
# JSON Web Token implementation
|
11
|
+
#
|
12
|
+
# Should be up to date with the latest spec:
|
13
|
+
# https://tools.ietf.org/html/rfc7519
|
14
|
+
module JWTB
|
15
|
+
include JWTB::DefaultOptions
|
16
|
+
|
17
|
+
module_function
|
18
|
+
|
19
|
+
def decoded_segments(jwt, verify = true)
|
20
|
+
raise(JWTB::DecodeError, 'Nil JSON web token') unless jwt
|
21
|
+
|
22
|
+
decoder = Decode.new jwt, verify
|
23
|
+
decoder.decode_segments
|
24
|
+
end
|
25
|
+
|
26
|
+
def encode(payload, key, algorithm = 'HS256', header_fields = {})
|
27
|
+
encoder = Encode.new payload, key, algorithm, header_fields
|
28
|
+
encoder.segments
|
29
|
+
end
|
30
|
+
|
31
|
+
def decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
|
32
|
+
raise(JWTB::DecodeError, 'Nil JSON web token') unless jwt
|
33
|
+
|
34
|
+
merged_options = DEFAULT_OPTIONS.merge(custom_options)
|
35
|
+
|
36
|
+
decoder = Decode.new jwt, verify
|
37
|
+
header, payload, signature, signing_input = decoder.decode_segments
|
38
|
+
decode_verify_signature(key, header, payload, signature, signing_input, merged_options, &keyfinder) if verify
|
39
|
+
|
40
|
+
Verify.verify_claims(payload, merged_options)
|
41
|
+
|
42
|
+
raise(JWTB::DecodeError, 'Not enough or too many segments') unless header && payload
|
43
|
+
|
44
|
+
[payload, header]
|
45
|
+
end
|
46
|
+
|
47
|
+
def decode_verify_signature(key, header, payload, signature, signing_input, options, &keyfinder)
|
48
|
+
algo, key = signature_algorithm_and_key(header, payload, key, &keyfinder)
|
49
|
+
|
50
|
+
raise(JWTB::IncorrectAlgorithm, 'An algorithm must be specified') unless options[:algorithm]
|
51
|
+
raise(JWTB::IncorrectAlgorithm, 'Expected a different algorithm') unless algo == options[:algorithm]
|
52
|
+
|
53
|
+
Signature.verify(algo, key, signing_input, signature)
|
54
|
+
end
|
55
|
+
|
56
|
+
def signature_algorithm_and_key(header, payload, key, &keyfinder)
|
57
|
+
if keyfinder
|
58
|
+
key = if keyfinder.arity == 2
|
59
|
+
yield(header, payload)
|
60
|
+
else
|
61
|
+
yield(header)
|
62
|
+
end
|
63
|
+
raise JWTB::DecodeError, 'No verification key available' unless key
|
64
|
+
end
|
65
|
+
[header['alg'], key]
|
66
|
+
end
|
67
|
+
end
|
data/lib/jwtb/decode.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# JWTB::Decode module
|
5
|
+
module JWTB
|
6
|
+
# Decoding logic for JWTB
|
7
|
+
class Decode
|
8
|
+
attr_reader :header, :payload, :signature
|
9
|
+
|
10
|
+
def self.base64url_decode(str)
|
11
|
+
str += '=' * (4 - str.length.modulo(4))
|
12
|
+
Base64.decode64(str.tr('-_', '+/'))
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(jwt, verify)
|
16
|
+
@jwt = jwt
|
17
|
+
@verify = verify
|
18
|
+
end
|
19
|
+
|
20
|
+
def decode_segments
|
21
|
+
header_segment, payload_segment, crypto_segment = raw_segments(@jwt, @verify)
|
22
|
+
@header, @payload = decode_header_and_payload(header_segment, payload_segment)
|
23
|
+
@signature = Decode.base64url_decode(crypto_segment.to_s) if @verify
|
24
|
+
signing_input = [header_segment, payload_segment].join('.')
|
25
|
+
[@header, @payload, @signature, signing_input]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def raw_segments(jwt, verify)
|
31
|
+
segments = jwt.split('.')
|
32
|
+
required_num_segments = verify ? [3] : [2, 3]
|
33
|
+
raise(JWTB::DecodeError, 'Not enough or too many segments') unless required_num_segments.include? segments.length
|
34
|
+
segments
|
35
|
+
end
|
36
|
+
|
37
|
+
def decode_header_and_payload(header_segment, payload_segment)
|
38
|
+
header = JSON.parse(Decode.base64url_decode(header_segment))
|
39
|
+
payload = JSON.parse(Decode.base64url_decode(payload_segment))
|
40
|
+
[header, payload]
|
41
|
+
rescue JSON::ParserError
|
42
|
+
raise JWTB::DecodeError, 'Invalid segment encoding'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module JWTB
|
2
|
+
module DefaultOptions
|
3
|
+
DEFAULT_OPTIONS = {
|
4
|
+
verify_expiration: true,
|
5
|
+
verify_not_before: true,
|
6
|
+
verify_iss: false,
|
7
|
+
verify_iat: false,
|
8
|
+
verify_jti: false,
|
9
|
+
verify_aud: false,
|
10
|
+
verify_sub: false,
|
11
|
+
leeway: 0
|
12
|
+
}.freeze
|
13
|
+
end
|
14
|
+
end
|
data/lib/jwtb/encode.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# JWTB::Encode module
|
5
|
+
module JWTB
|
6
|
+
# Encoding logic for JWTB
|
7
|
+
class Encode
|
8
|
+
attr_reader :payload, :key, :algorithm, :header_fields, :segments
|
9
|
+
|
10
|
+
def self.base64url_encode(str)
|
11
|
+
Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(payload, key, algorithm, header_fields)
|
15
|
+
@payload = payload
|
16
|
+
@key = key
|
17
|
+
@algorithm = algorithm
|
18
|
+
@header_fields = header_fields
|
19
|
+
@segments = encode_segments
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def encoded_header(algorithm, header_fields)
|
25
|
+
header = { 'alg' => algorithm }.merge(header_fields)
|
26
|
+
Encode.base64url_encode(JSON.generate(header))
|
27
|
+
end
|
28
|
+
|
29
|
+
def encoded_payload(payload)
|
30
|
+
raise InvalidPayload, 'exp claim must be an integer' if payload['exp'] && payload['exp'].is_a?(Time)
|
31
|
+
Encode.base64url_encode(JSON.generate(payload))
|
32
|
+
end
|
33
|
+
|
34
|
+
def encoded_signature(signing_input, key, algorithm)
|
35
|
+
if algorithm == 'none'
|
36
|
+
''
|
37
|
+
else
|
38
|
+
signature = JWTB::Signature.sign(algorithm, signing_input, key)
|
39
|
+
Encode.base64url_encode(signature)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def encode_segments
|
44
|
+
segments = []
|
45
|
+
segments << encoded_header(@algorithm, @header_fields)
|
46
|
+
segments << encoded_payload(@payload)
|
47
|
+
segments << encoded_signature(segments.join('.'), @key, @algorithm)
|
48
|
+
segments.join('.')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/jwtb/error.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JWTB
|
3
|
+
class EncodeError < StandardError; end
|
4
|
+
class DecodeError < StandardError; end
|
5
|
+
class VerificationError < DecodeError; end
|
6
|
+
class ExpiredSignature < DecodeError; end
|
7
|
+
class IncorrectAlgorithm < DecodeError; end
|
8
|
+
class ImmatureSignature < DecodeError; end
|
9
|
+
class InvalidIssuerError < DecodeError; end
|
10
|
+
class InvalidIatError < DecodeError; end
|
11
|
+
class InvalidAudError < DecodeError; end
|
12
|
+
class InvalidSubError < DecodeError; end
|
13
|
+
class InvalidJtiError < DecodeError; end
|
14
|
+
class InvalidPayload < DecodeError; end
|
15
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'openssl'
|
3
|
+
begin
|
4
|
+
require 'rbnacl'
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
|
8
|
+
# JWTB::Signature module
|
9
|
+
module JWTB
|
10
|
+
# Signature logic for JWTB
|
11
|
+
module Signature
|
12
|
+
extend self
|
13
|
+
|
14
|
+
HMAC_ALGORITHMS = %w(HS256 HS512256 HS384 HS512).freeze
|
15
|
+
RSA_ALGORITHMS = %w(RS256 RS384 RS512).freeze
|
16
|
+
ECDSA_ALGORITHMS = %w(ES256 ES384 ES512 ES256K).freeze
|
17
|
+
|
18
|
+
NAMED_CURVES = {
|
19
|
+
'prime256v1' => 'ES256',
|
20
|
+
'secp384r1' => 'ES384',
|
21
|
+
'secp521r1' => 'ES512',
|
22
|
+
'secp256k1' => 'ES256K'
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
def sign(algorithm, msg, key)
|
26
|
+
if HMAC_ALGORITHMS.include?(algorithm)
|
27
|
+
sign_hmac(algorithm, msg, key)
|
28
|
+
elsif RSA_ALGORITHMS.include?(algorithm)
|
29
|
+
sign_rsa(algorithm, msg, key)
|
30
|
+
elsif ECDSA_ALGORITHMS.include?(algorithm)
|
31
|
+
sign_ecdsa(algorithm, msg, key)
|
32
|
+
else
|
33
|
+
raise NotImplementedError, 'Unsupported signing method'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def verify(algo, key, signing_input, signature)
|
38
|
+
verified = if HMAC_ALGORITHMS.include?(algo)
|
39
|
+
verify_hmac(algo, key, signing_input, signature)
|
40
|
+
elsif RSA_ALGORITHMS.include?(algo)
|
41
|
+
verify_rsa(algo, key, signing_input, signature)
|
42
|
+
elsif ECDSA_ALGORITHMS.include?(algo)
|
43
|
+
verify_ecdsa(algo, key, signing_input, signature)
|
44
|
+
else
|
45
|
+
raise JWTB::VerificationError, 'Algorithm not supported'
|
46
|
+
end
|
47
|
+
|
48
|
+
raise(JWTB::VerificationError, 'Signature verification raised') unless verified
|
49
|
+
rescue OpenSSL::PKey::PKeyError
|
50
|
+
raise JWTB::VerificationError, 'Signature verification raised'
|
51
|
+
ensure
|
52
|
+
OpenSSL.errors.clear
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def sign_rsa(algorithm, msg, private_key)
|
58
|
+
raise EncodeError, "The given key is a #{private_key.class}. It has to be an OpenSSL::PKey::RSA instance." if private_key.class == String
|
59
|
+
private_key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
|
60
|
+
end
|
61
|
+
|
62
|
+
def sign_ecdsa(algorithm, msg, private_key)
|
63
|
+
key_algorithm = NAMED_CURVES[private_key.group.curve_name]
|
64
|
+
if algorithm != key_algorithm
|
65
|
+
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
|
66
|
+
end
|
67
|
+
|
68
|
+
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha').sub('K', ''))
|
69
|
+
asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
|
70
|
+
end
|
71
|
+
|
72
|
+
def sign_hmac(algorithm, msg, key)
|
73
|
+
authenticator, padded_key = rbnacl_fixup(algorithm, key)
|
74
|
+
if authenticator && padded_key
|
75
|
+
authenticator.auth(padded_key, msg.encode('binary'))
|
76
|
+
else
|
77
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def verify_rsa(algorithm, public_key, signing_input, signature)
|
82
|
+
public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
|
83
|
+
end
|
84
|
+
|
85
|
+
def verify_ecdsa(algorithm, public_key, signing_input, signature)
|
86
|
+
key_algorithm = NAMED_CURVES[public_key.group.curve_name]
|
87
|
+
if algorithm != key_algorithm
|
88
|
+
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
|
89
|
+
end
|
90
|
+
|
91
|
+
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha').sub('K', ''))
|
92
|
+
public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
|
93
|
+
end
|
94
|
+
|
95
|
+
def verify_hmac(algorithm, public_key, signing_input, signature)
|
96
|
+
authenticator, padded_key = rbnacl_fixup(algorithm, public_key)
|
97
|
+
if authenticator && padded_key
|
98
|
+
begin
|
99
|
+
authenticator.verify(padded_key, signature.encode('binary'), signing_input.encode('binary'))
|
100
|
+
rescue RbNaCl::BadAuthenticatorError
|
101
|
+
false
|
102
|
+
end
|
103
|
+
else
|
104
|
+
secure_compare(signature, sign_hmac(algorithm, signing_input, public_key))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def asn1_to_raw(signature, public_key)
|
109
|
+
byte_size = (public_key.group.degree + 7) / 8
|
110
|
+
OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
|
111
|
+
end
|
112
|
+
|
113
|
+
def raw_to_asn1(signature, private_key)
|
114
|
+
byte_size = (private_key.group.degree + 7) / 8
|
115
|
+
r = signature[0..(byte_size - 1)]
|
116
|
+
s = signature[byte_size..-1] || ''
|
117
|
+
OpenSSL::ASN1::Sequence.new([r, s].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
|
118
|
+
end
|
119
|
+
|
120
|
+
def rbnacl_fixup(algorithm, key)
|
121
|
+
algorithm = algorithm.sub('HS', 'SHA').to_sym
|
122
|
+
|
123
|
+
return [] unless defined?(RbNaCl) && RbNaCl::HMAC.constants(false).include?(algorithm)
|
124
|
+
|
125
|
+
authenticator = RbNaCl::HMAC.const_get(algorithm)
|
126
|
+
|
127
|
+
# Fall back to OpenSSL for keys larger than 32 bytes.
|
128
|
+
return [] if key.bytesize > authenticator.key_bytes
|
129
|
+
|
130
|
+
[
|
131
|
+
authenticator,
|
132
|
+
key.bytes.fill(0, key.bytesize...authenticator.key_bytes).pack('C*')
|
133
|
+
]
|
134
|
+
end
|
135
|
+
|
136
|
+
# From devise
|
137
|
+
# constant-time comparison algorithm to prevent timing attacks
|
138
|
+
def secure_compare(a, b)
|
139
|
+
return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize
|
140
|
+
l = a.unpack "C#{a.bytesize}"
|
141
|
+
res = 0
|
142
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
143
|
+
res.zero?
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/jwtb/verify.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'jwtb/error'
|
3
|
+
|
4
|
+
module JWTB
|
5
|
+
# JWTB verify methods
|
6
|
+
class Verify
|
7
|
+
class << self
|
8
|
+
%w(verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub).each do |method_name|
|
9
|
+
define_method method_name do |payload, options|
|
10
|
+
new(payload, options).send(method_name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def verify_claims(payload, options)
|
15
|
+
options.each do |key, val|
|
16
|
+
next unless key.to_s =~ /verify/
|
17
|
+
Verify.send(key, payload, options) if val
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(payload, options)
|
23
|
+
@payload = payload
|
24
|
+
@options = options
|
25
|
+
end
|
26
|
+
|
27
|
+
def verify_aud
|
28
|
+
return unless (options_aud = @options[:aud])
|
29
|
+
raise(JWTB::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{@payload['aud'] || '<none>'}") if ([*@payload['aud']] & [*options_aud]).empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def verify_expiration
|
33
|
+
return unless @payload.include?('exp')
|
34
|
+
raise(JWTB::ExpiredSignature, 'Signature has expired') if @payload['exp'].to_i <= (Time.now.to_i - exp_leeway)
|
35
|
+
end
|
36
|
+
|
37
|
+
def verify_iat
|
38
|
+
return unless @payload.include?('iat')
|
39
|
+
raise(JWTB::InvalidIatError, 'Invalid iat') if !@payload['iat'].is_a?(Numeric) || @payload['iat'].to_f > (Time.now.to_f + iat_leeway)
|
40
|
+
end
|
41
|
+
|
42
|
+
def verify_iss
|
43
|
+
return unless (options_iss = @options[:iss])
|
44
|
+
raise(JWTB::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{@payload['iss'] || '<none>'}") if @payload['iss'].to_s != options_iss.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
def verify_jti
|
48
|
+
options_verify_jti = @options[:verify_jti]
|
49
|
+
if options_verify_jti.respond_to?(:call)
|
50
|
+
raise(JWTB::InvalidJtiError, 'Invalid jti') unless options_verify_jti.call(@payload['jti'])
|
51
|
+
elsif @payload['jti'].to_s.strip.empty?
|
52
|
+
raise(JWTB::InvalidJtiError, 'Missing jti')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def verify_not_before
|
57
|
+
return unless @payload.include?('nbf')
|
58
|
+
raise(JWTB::ImmatureSignature, 'Signature nbf has not been reached') if @payload['nbf'].to_i > (Time.now.to_i + nbf_leeway)
|
59
|
+
end
|
60
|
+
|
61
|
+
def verify_sub
|
62
|
+
return unless (options_sub = @options[:sub])
|
63
|
+
raise(JWTB::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{@payload['sub'] || '<none>'}") unless @payload['sub'].to_s == options_sub.to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def global_leeway
|
69
|
+
@options[:leeway]
|
70
|
+
end
|
71
|
+
|
72
|
+
def exp_leeway
|
73
|
+
@options[:exp_leeway] || global_leeway
|
74
|
+
end
|
75
|
+
|
76
|
+
def iat_leeway
|
77
|
+
@options[:iat_leeway] || global_leeway
|
78
|
+
end
|
79
|
+
|
80
|
+
def nbf_leeway
|
81
|
+
@options[:nbf_leeway] || global_leeway
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|