jwt 2.2.3 → 2.4.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 +4 -4
- data/.codeclimate.yml +8 -0
- data/.github/workflows/coverage.yml +27 -0
- data/.github/workflows/test.yml +3 -11
- data/.gitignore +2 -0
- data/.rubocop.yml +12 -28
- data/.rubocop_todo.yml +9 -178
- data/AUTHORS +31 -13
- data/Appraisals +3 -0
- data/CHANGELOG.md +85 -2
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +3 -1
- data/README.md +92 -25
- data/Rakefile +2 -0
- data/lib/jwt/algos/ecdsa.rb +23 -5
- data/lib/jwt/algos/eddsa.rb +14 -4
- data/lib/jwt/algos/hmac.rb +2 -0
- data/lib/jwt/algos/none.rb +2 -0
- data/lib/jwt/algos/ps.rb +3 -3
- data/lib/jwt/algos/rsa.rb +4 -1
- data/lib/jwt/algos/unsupported.rb +2 -0
- data/lib/jwt/claims_validator.rb +3 -1
- data/lib/jwt/decode.rb +44 -8
- data/lib/jwt/default_options.rb +4 -1
- data/lib/jwt/encode.rb +6 -6
- data/lib/jwt/error.rb +2 -0
- data/lib/jwt/jwk/ec.rb +7 -7
- data/lib/jwt/jwk/hmac.rb +1 -1
- data/lib/jwt/jwk/key_base.rb +1 -0
- data/lib/jwt/jwk/rsa.rb +4 -3
- data/lib/jwt/jwk.rb +3 -2
- data/lib/jwt/security_utils.rb +2 -0
- data/lib/jwt/signature.rb +3 -7
- data/lib/jwt/verify.rb +18 -3
- data/lib/jwt/version.rb +2 -3
- data/lib/jwt/x5c_key_finder.rb +55 -0
- data/lib/jwt.rb +1 -1
- data/ruby-jwt.gemspec +8 -2
- metadata +11 -6
- data/lib/jwt/base64.rb +0 -19
data/lib/jwt/jwk/ec.rb
CHANGED
@@ -8,7 +8,7 @@ module JWT
|
|
8
8
|
extend Forwardable
|
9
9
|
def_delegators :@keypair, :public_key
|
10
10
|
|
11
|
-
KTY = 'EC'
|
11
|
+
KTY = 'EC'
|
12
12
|
KTYS = [KTY, OpenSSL::PKey::EC].freeze
|
13
13
|
BINARY = 2
|
14
14
|
|
@@ -66,17 +66,17 @@ module JWT
|
|
66
66
|
crv = 'P-521'
|
67
67
|
x_octets, y_octets = encoded_point.unpack('xa66a66')
|
68
68
|
else
|
69
|
-
raise
|
69
|
+
raise JWT::JWKError, "Unsupported curve '#{ec_keypair.group.curve_name}'"
|
70
70
|
end
|
71
71
|
[crv, x_octets, y_octets]
|
72
72
|
end
|
73
73
|
|
74
74
|
def encode_octets(octets)
|
75
|
-
|
75
|
+
Base64.urlsafe_encode64(octets, padding: false)
|
76
76
|
end
|
77
77
|
|
78
78
|
def encode_open_ssl_bn(key_part)
|
79
|
-
|
79
|
+
Base64.urlsafe_encode64(key_part.to_s(BINARY), padding: false)
|
80
80
|
end
|
81
81
|
|
82
82
|
class << self
|
@@ -85,7 +85,7 @@ module JWT
|
|
85
85
|
# explanation of the relevant parameters.
|
86
86
|
|
87
87
|
jwk_crv, jwk_x, jwk_y, jwk_d, jwk_kid = jwk_attrs(jwk_data, %i[crv x y d kid])
|
88
|
-
raise
|
88
|
+
raise JWT::JWKError, 'Key format is invalid for EC' unless jwk_crv && jwk_x && jwk_y
|
89
89
|
|
90
90
|
new(ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d), jwk_kid)
|
91
91
|
end
|
@@ -138,11 +138,11 @@ module JWT
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def decode_octets(jwk_data)
|
141
|
-
|
141
|
+
Base64.urlsafe_decode64(jwk_data)
|
142
142
|
end
|
143
143
|
|
144
144
|
def decode_open_ssl_bn(jwk_data)
|
145
|
-
OpenSSL::BN.new(
|
145
|
+
OpenSSL::BN.new(Base64.urlsafe_decode64(jwk_data), BINARY)
|
146
146
|
end
|
147
147
|
end
|
148
148
|
end
|
data/lib/jwt/jwk/hmac.rb
CHANGED
data/lib/jwt/jwk/key_base.rb
CHANGED
data/lib/jwt/jwk/rsa.rb
CHANGED
@@ -4,12 +4,13 @@ module JWT
|
|
4
4
|
module JWK
|
5
5
|
class RSA < KeyBase
|
6
6
|
BINARY = 2
|
7
|
-
KTY = 'RSA'
|
7
|
+
KTY = 'RSA'
|
8
8
|
KTYS = [KTY, OpenSSL::PKey::RSA].freeze
|
9
9
|
RSA_KEY_ELEMENTS = %i[n e d p q dp dq qi].freeze
|
10
10
|
|
11
11
|
def initialize(keypair, kid = nil)
|
12
12
|
raise ArgumentError, 'keypair must be of type OpenSSL::PKey::RSA' unless keypair.is_a?(OpenSSL::PKey::RSA)
|
13
|
+
|
13
14
|
super(keypair, kid || generate_kid(keypair.public_key))
|
14
15
|
end
|
15
16
|
|
@@ -54,7 +55,7 @@ module JWT
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def encode_open_ssl_bn(key_part)
|
57
|
-
|
58
|
+
Base64.urlsafe_encode64(key_part.to_s(BINARY), padding: false)
|
58
59
|
end
|
59
60
|
|
60
61
|
class << self
|
@@ -107,7 +108,7 @@ module JWT
|
|
107
108
|
def decode_open_ssl_bn(jwk_data)
|
108
109
|
return nil unless jwk_data
|
109
110
|
|
110
|
-
OpenSSL::BN.new(
|
111
|
+
OpenSSL::BN.new(Base64.urlsafe_decode64(jwk_data), BINARY)
|
111
112
|
end
|
112
113
|
end
|
113
114
|
end
|
data/lib/jwt/jwk.rb
CHANGED
@@ -14,10 +14,10 @@ module JWT
|
|
14
14
|
end.import(jwk_data)
|
15
15
|
end
|
16
16
|
|
17
|
-
def create_from(keypair)
|
17
|
+
def create_from(keypair, kid = nil)
|
18
18
|
mappings.fetch(keypair.class) do |klass|
|
19
19
|
raise JWT::JWKError, "Cannot create JWK from a #{klass.name}"
|
20
|
-
end.new(keypair)
|
20
|
+
end.new(keypair, kid)
|
21
21
|
end
|
22
22
|
|
23
23
|
def classes
|
@@ -36,6 +36,7 @@ module JWT
|
|
36
36
|
def generate_mappings
|
37
37
|
classes.each_with_object({}) do |klass, hash|
|
38
38
|
next unless klass.const_defined?('KTYS')
|
39
|
+
|
39
40
|
Array(klass::KTYS).each do |kty|
|
40
41
|
hash[kty] = klass
|
41
42
|
end
|
data/lib/jwt/security_utils.rb
CHANGED
data/lib/jwt/signature.rb
CHANGED
@@ -13,7 +13,8 @@ end
|
|
13
13
|
module JWT
|
14
14
|
# Signature logic for JWT
|
15
15
|
module Signature
|
16
|
-
|
16
|
+
module_function
|
17
|
+
|
17
18
|
ToSign = Struct.new(:algorithm, :msg, :key)
|
18
19
|
ToVerify = Struct.new(:algorithm, :public_key, :signing_input, :signature)
|
19
20
|
|
@@ -23,13 +24,8 @@ module JWT
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def verify(algorithm, key, signing_input, signature)
|
26
|
-
return true if algorithm.casecmp('none').zero?
|
27
|
-
|
28
|
-
raise JWT::DecodeError, 'No verification key available' unless key
|
29
|
-
|
30
27
|
algo, code = Algos.find(algorithm)
|
31
|
-
|
32
|
-
raise(JWT::VerificationError, 'Signature verification raised') unless verified
|
28
|
+
algo.verify(ToVerify.new(code, key, signing_input, signature))
|
33
29
|
rescue OpenSSL::PKey::PKeyError
|
34
30
|
raise JWT::VerificationError, 'Signature verification raised'
|
35
31
|
ensure
|
data/lib/jwt/verify.rb
CHANGED
@@ -10,7 +10,7 @@ module JWT
|
|
10
10
|
}.freeze
|
11
11
|
|
12
12
|
class << self
|
13
|
-
%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|
|
14
14
|
define_method method_name do |payload, options|
|
15
15
|
new(payload, options).send(method_name)
|
16
16
|
end
|
@@ -19,6 +19,7 @@ module JWT
|
|
19
19
|
def verify_claims(payload, options)
|
20
20
|
options.each do |key, val|
|
21
21
|
next unless key.to_s =~ /verify/
|
22
|
+
|
22
23
|
Verify.send(key, payload, options) if val
|
23
24
|
end
|
24
25
|
end
|
@@ -53,9 +54,14 @@ module JWT
|
|
53
54
|
|
54
55
|
iss = @payload['iss']
|
55
56
|
|
56
|
-
|
57
|
+
options_iss = Array(options_iss).map { |item| item.is_a?(Symbol) ? item.to_s : item }
|
57
58
|
|
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
|
59
65
|
end
|
60
66
|
|
61
67
|
def verify_jti
|
@@ -77,10 +83,19 @@ module JWT
|
|
77
83
|
|
78
84
|
def verify_sub
|
79
85
|
return unless (options_sub = @options[:sub])
|
86
|
+
|
80
87
|
sub = @payload['sub']
|
81
88
|
raise(JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{sub || '<none>'}") unless sub.to_s == options_sub.to_s
|
82
89
|
end
|
83
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
|
+
|
84
99
|
private
|
85
100
|
|
86
101
|
def global_leeway
|
data/lib/jwt/version.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
# Moments version builder module
|
@@ -12,9 +11,9 @@ module JWT
|
|
12
11
|
# major version
|
13
12
|
MAJOR = 2
|
14
13
|
# minor version
|
15
|
-
MINOR =
|
14
|
+
MINOR = 4
|
16
15
|
# tiny version
|
17
|
-
TINY =
|
16
|
+
TINY = 0
|
18
17
|
# alpha, beta, etc. tag
|
19
18
|
PRE = nil
|
20
19
|
|
@@ -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(::Base64.strict_decode64(encoded))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/jwt.rb
CHANGED
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
|
|
@@ -13,7 +15,11 @@ Gem::Specification.new do |spec|
|
|
13
15
|
spec.description = 'A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.'
|
14
16
|
spec.homepage = 'https://github.com/jwt/ruby-jwt'
|
15
17
|
spec.license = 'MIT'
|
16
|
-
spec.required_ruby_version = '>= 2.
|
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
|
+
}
|
17
23
|
|
18
24
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|gemfiles|coverage|bin)/}) }
|
19
25
|
spec.executables = []
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jwt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Rudat
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: appraisal
|
@@ -87,6 +87,8 @@ executables: []
|
|
87
87
|
extensions: []
|
88
88
|
extra_rdoc_files: []
|
89
89
|
files:
|
90
|
+
- ".codeclimate.yml"
|
91
|
+
- ".github/workflows/coverage.yml"
|
90
92
|
- ".github/workflows/test.yml"
|
91
93
|
- ".gitignore"
|
92
94
|
- ".rspec"
|
@@ -96,6 +98,7 @@ files:
|
|
96
98
|
- AUTHORS
|
97
99
|
- Appraisals
|
98
100
|
- CHANGELOG.md
|
101
|
+
- CODE_OF_CONDUCT.md
|
99
102
|
- Gemfile
|
100
103
|
- LICENSE
|
101
104
|
- README.md
|
@@ -109,7 +112,6 @@ files:
|
|
109
112
|
- lib/jwt/algos/ps.rb
|
110
113
|
- lib/jwt/algos/rsa.rb
|
111
114
|
- lib/jwt/algos/unsupported.rb
|
112
|
-
- lib/jwt/base64.rb
|
113
115
|
- lib/jwt/claims_validator.rb
|
114
116
|
- lib/jwt/decode.rb
|
115
117
|
- lib/jwt/default_options.rb
|
@@ -126,11 +128,14 @@ files:
|
|
126
128
|
- lib/jwt/signature.rb
|
127
129
|
- lib/jwt/verify.rb
|
128
130
|
- lib/jwt/version.rb
|
131
|
+
- lib/jwt/x5c_key_finder.rb
|
129
132
|
- ruby-jwt.gemspec
|
130
133
|
homepage: https://github.com/jwt/ruby-jwt
|
131
134
|
licenses:
|
132
135
|
- MIT
|
133
|
-
metadata:
|
136
|
+
metadata:
|
137
|
+
bug_tracker_uri: https://github.com/jwt/ruby-jwt/issues
|
138
|
+
changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.4.0/CHANGELOG.md
|
134
139
|
post_install_message:
|
135
140
|
rdoc_options: []
|
136
141
|
require_paths:
|
@@ -139,14 +144,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
144
|
requirements:
|
140
145
|
- - ">="
|
141
146
|
- !ruby/object:Gem::Version
|
142
|
-
version: '2.
|
147
|
+
version: '2.5'
|
143
148
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
149
|
requirements:
|
145
150
|
- - ">="
|
146
151
|
- !ruby/object:Gem::Version
|
147
152
|
version: '0'
|
148
153
|
requirements: []
|
149
|
-
rubygems_version: 3.
|
154
|
+
rubygems_version: 3.3.7
|
150
155
|
signing_key:
|
151
156
|
specification_version: 4
|
152
157
|
summary: JSON Web Token implementation in Ruby
|
data/lib/jwt/base64.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'base64'
|
4
|
-
|
5
|
-
module JWT
|
6
|
-
# Base64 helpers
|
7
|
-
class Base64
|
8
|
-
class << self
|
9
|
-
def url_encode(str)
|
10
|
-
::Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
|
11
|
-
end
|
12
|
-
|
13
|
-
def url_decode(str)
|
14
|
-
str += '=' * (4 - str.length.modulo(4))
|
15
|
-
::Base64.decode64(str.tr('-_', '+/'))
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|