jwt 2.8.1 → 2.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +19 -11
- data/lib/jwt/base64.rb +1 -1
- data/lib/jwt/claims/audience.rb +20 -0
- data/lib/jwt/claims/expiration.rb +22 -0
- data/lib/jwt/claims/issued_at.rb +15 -0
- data/lib/jwt/claims/issuer.rb +24 -0
- data/lib/jwt/claims/jwt_id.rb +25 -0
- data/lib/jwt/claims/not_before.rb +22 -0
- data/lib/jwt/claims/numeric.rb +43 -0
- data/lib/jwt/claims/required.rb +23 -0
- data/lib/jwt/claims/subject.rb +20 -0
- data/lib/jwt/claims.rb +38 -0
- data/lib/jwt/configuration/jwk_configuration.rb +1 -1
- data/lib/jwt/decode.rb +12 -15
- data/lib/jwt/deprecations.rb +24 -5
- data/lib/jwt/encode.rb +3 -7
- data/lib/jwt/jwa/ecdsa.rb +38 -25
- data/lib/jwt/jwa/eddsa.rb +19 -27
- data/lib/jwt/jwa/hmac.rb +22 -18
- data/lib/jwt/jwa/hmac_rbnacl.rb +38 -43
- data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +35 -39
- data/lib/jwt/jwa/none.rb +7 -3
- data/lib/jwt/jwa/ps.rb +20 -14
- data/lib/jwt/jwa/rsa.rb +20 -9
- data/lib/jwt/jwa/signing_algorithm.rb +59 -0
- data/lib/jwt/jwa/unsupported.rb +8 -8
- data/lib/jwt/jwa/wrapper.rb +26 -9
- data/lib/jwt/jwa.rb +21 -38
- data/lib/jwt/jwk/ec.rb +20 -20
- data/lib/jwt/jwk/key_finder.rb +4 -4
- data/lib/jwt/jwk/set.rb +1 -1
- data/lib/jwt/version.rb +2 -2
- data/lib/jwt/x5c_key_finder.rb +2 -2
- data/lib/jwt.rb +4 -1
- metadata +18 -9
- data/lib/jwt/claims_validator.rb +0 -37
- data/lib/jwt/verify.rb +0 -117
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f3d3ea752a6b4dee8f1b020a3e0af95e003f74a8c40f730faab73b616fc9e98
|
4
|
+
data.tar.gz: 8dcdb470771f56931485824c32739bfcec1ce310b7096d6cd28c656d0f8c21fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c68cccb66154a824751df6e7cd4d0b9d31372c0bada498097770feb8992ac3c7a462cd11eb759171d096e8d957b2a67b088fb35dc79fe7b4dd6eac6b25140d63
|
7
|
+
data.tar.gz: 03fe234584cce6458fb1a0f64cb399f81bc3c7aee40e25c011f0c27484ede3c6c6a6d950a9c5f3f13e4d66db5a76de98f2801942762df514097b2f663bf79247
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v2.9.0](https://github.com/jwt/ruby-jwt/tree/v2.9.0) (NEXT)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.2...v2.9.0)
|
6
|
+
|
7
|
+
**Features:**
|
8
|
+
|
9
|
+
- Build and push gem using a GH action [#612](https://github.com/jwt/ruby-jwt/pull/612) ([@anakinj](https://github.com/anakinj))
|
10
|
+
|
11
|
+
**Fixes and enhancements:**
|
12
|
+
|
13
|
+
- Refactor claim validators into their own classes [#605](https://github.com/jwt/ruby-jwt/pull/605) ([@anakinj](https://github.com/anakinj), [@MatteoPierro](https://github.com/MatteoPierro))
|
14
|
+
- Allow extending available algorithms [#607](https://github.com/jwt/ruby-jwt/pull/607) ([@anakinj](https://github.com/anakinj))
|
15
|
+
- Do not include the EdDSA algorithm if rbnacl not available [#613](https://github.com/jwt/ruby-jwt/pull/613) ([@anakinj](https://github.com/anakinj))
|
16
|
+
|
17
|
+
## [v2.8.2](https://github.com/jwt/ruby-jwt/tree/v2.8.2) (2024-06-18)
|
18
|
+
|
19
|
+
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.1...v2.8.2)
|
20
|
+
|
21
|
+
**Fixes and enhancements:**
|
22
|
+
|
23
|
+
- Print deprecation warnings only on when token decoding succeeds [#600](https://github.com/jwt/ruby-jwt/pull/600) ([@anakinj](https://github.com/anakinj))
|
24
|
+
- Unify code style [#602](https://github.com/jwt/ruby-jwt/pull/602) ([@anakinj](https://github.com/anakinj))
|
25
|
+
|
3
26
|
## [v2.8.1](https://github.com/jwt/ruby-jwt/tree/v2.8.1) (2024-02-29)
|
4
27
|
|
5
28
|
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.0...v2.8.1)
|
data/README.md
CHANGED
@@ -223,18 +223,22 @@ puts decoded_token
|
|
223
223
|
|
224
224
|
### **Custom algorithms**
|
225
225
|
|
226
|
-
|
226
|
+
When encoding or decoding a token, you can pass in a custom object through the `algorithm` option to handle signing or verification. This custom object must include or extend the `JWT::JWA::SigningAlgorithm` module and implement certain methods:
|
227
|
+
|
228
|
+
- For decoding/verifying: The object must implement the methods `alg` and `verify`.
|
229
|
+
- For encoding/signing: The object must implement the methods `alg` and `sign`.
|
230
|
+
|
231
|
+
For customization options check the details from `JWT::JWA::SigningAlgorithm`.
|
232
|
+
|
227
233
|
|
228
234
|
```ruby
|
229
235
|
module CustomHS512Algorithm
|
236
|
+
extend JWT::JWA::SigningAlgorithm
|
237
|
+
|
230
238
|
def self.alg
|
231
239
|
'HS512'
|
232
240
|
end
|
233
241
|
|
234
|
-
def self.valid_alg?(alg_to_validate)
|
235
|
-
alg_to_validate == alg
|
236
|
-
end
|
237
|
-
|
238
242
|
def self.sign(data:, signing_key:)
|
239
243
|
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), data, signing_key)
|
240
244
|
end
|
@@ -690,21 +694,25 @@ jwk_hash = jwk.export
|
|
690
694
|
thumbprint_as_the_kid = jwk_hash[:kid]
|
691
695
|
```
|
692
696
|
|
693
|
-
# Development and
|
697
|
+
# Development and testing
|
694
698
|
|
695
|
-
|
699
|
+
The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
|
696
700
|
|
697
701
|
```bash
|
698
|
-
|
702
|
+
bundle install
|
703
|
+
bundle exec appraisal rake test
|
699
704
|
```
|
700
705
|
|
701
|
-
|
706
|
+
# Releasing
|
707
|
+
|
708
|
+
To cut a new release adjust the [version.rb](lib/jwt/version.rb) and [CHANGELOG](CHANGELOG.md) with desired version numbers and dates and commit the changes. Tag the release with the version number using the following command:
|
702
709
|
|
703
710
|
```bash
|
704
|
-
|
705
|
-
bundle exec appraisal rake test
|
711
|
+
rake release:source_control_push
|
706
712
|
```
|
707
713
|
|
714
|
+
This will tag a new version an trigger a [GitHub action](.github/workflows/push_gem.yml) that eventually will push the gem to rubygems.org.
|
715
|
+
|
708
716
|
## How to contribute
|
709
717
|
See [CONTRIBUTING](CONTRIBUTING.md).
|
710
718
|
|
data/lib/jwt/base64.rb
CHANGED
@@ -20,7 +20,7 @@ module JWT
|
|
20
20
|
raise Base64DecodeError, 'Invalid base64 encoding' if JWT.configuration.strict_base64_decoding
|
21
21
|
|
22
22
|
loose_urlsafe_decode64(str).tap do
|
23
|
-
Deprecations.warning('Invalid base64 input detected, could be because of invalid padding, trailing whitespaces or newline chars. Graceful handling of invalid input will be dropped in the next major version of ruby-jwt')
|
23
|
+
Deprecations.warning('Invalid base64 input detected, could be because of invalid padding, trailing whitespaces or newline chars. Graceful handling of invalid input will be dropped in the next major version of ruby-jwt', only_if_valid: true)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Claims
|
5
|
+
class Audience
|
6
|
+
def initialize(expected_audience:)
|
7
|
+
@expected_audience = expected_audience
|
8
|
+
end
|
9
|
+
|
10
|
+
def verify!(context:, **_args)
|
11
|
+
aud = context.payload['aud']
|
12
|
+
raise JWT::InvalidAudError, "Invalid audience. Expected #{expected_audience}, received #{aud || '<none>'}" if ([*aud] & [*expected_audience]).empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :expected_audience
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Claims
|
5
|
+
class Expiration
|
6
|
+
def initialize(leeway:)
|
7
|
+
@leeway = leeway || 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def verify!(context:, **_args)
|
11
|
+
return unless context.payload.is_a?(Hash)
|
12
|
+
return unless context.payload.key?('exp')
|
13
|
+
|
14
|
+
raise JWT::ExpiredSignature, 'Signature has expired' if context.payload['exp'].to_i <= (Time.now.to_i - leeway)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :leeway
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Claims
|
5
|
+
class IssuedAt
|
6
|
+
def verify!(context:, **_args)
|
7
|
+
return unless context.payload.is_a?(Hash)
|
8
|
+
return unless context.payload.key?('iat')
|
9
|
+
|
10
|
+
iat = context.payload['iat']
|
11
|
+
raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(::Numeric) || iat.to_f > Time.now.to_f
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Claims
|
5
|
+
class Issuer
|
6
|
+
def initialize(issuers:)
|
7
|
+
@issuers = Array(issuers).map { |item| item.is_a?(Symbol) ? item.to_s : item }
|
8
|
+
end
|
9
|
+
|
10
|
+
def verify!(context:, **_args)
|
11
|
+
case (iss = context.payload['iss'])
|
12
|
+
when *issuers
|
13
|
+
nil
|
14
|
+
else
|
15
|
+
raise JWT::InvalidIssuerError, "Invalid issuer. Expected #{issuers}, received #{iss || '<none>'}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :issuers
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Claims
|
5
|
+
class JwtId
|
6
|
+
def initialize(validator:)
|
7
|
+
@validator = validator
|
8
|
+
end
|
9
|
+
|
10
|
+
def verify!(context:, **_args)
|
11
|
+
jti = context.payload['jti']
|
12
|
+
if validator.respond_to?(:call)
|
13
|
+
verified = validator.arity == 2 ? validator.call(jti, context.payload) : validator.call(jti)
|
14
|
+
raise(JWT::InvalidJtiError, 'Invalid jti') unless verified
|
15
|
+
elsif jti.to_s.strip.empty?
|
16
|
+
raise(JWT::InvalidJtiError, 'Missing jti')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :validator
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Claims
|
5
|
+
class NotBefore
|
6
|
+
def initialize(leeway:)
|
7
|
+
@leeway = leeway || 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def verify!(context:, **_args)
|
11
|
+
return unless context.payload.is_a?(Hash)
|
12
|
+
return unless context.payload.key?('nbf')
|
13
|
+
|
14
|
+
raise JWT::ImmatureSignature, 'Signature nbf has not been reached' if context.payload['nbf'].to_i > (Time.now.to_i + leeway)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :leeway
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Claims
|
5
|
+
class Numeric
|
6
|
+
def self.verify!(payload:, **_args)
|
7
|
+
return unless payload.is_a?(Hash)
|
8
|
+
|
9
|
+
new(payload).verify!
|
10
|
+
end
|
11
|
+
|
12
|
+
NUMERIC_CLAIMS = %i[
|
13
|
+
exp
|
14
|
+
iat
|
15
|
+
nbf
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
def initialize(payload)
|
19
|
+
@payload = payload.transform_keys(&:to_sym)
|
20
|
+
end
|
21
|
+
|
22
|
+
def verify!
|
23
|
+
validate_numeric_claims
|
24
|
+
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def validate_numeric_claims
|
31
|
+
NUMERIC_CLAIMS.each do |claim|
|
32
|
+
validate_is_numeric(claim) if @payload.key?(claim)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_is_numeric(claim)
|
37
|
+
return if @payload[claim].is_a?(::Numeric)
|
38
|
+
|
39
|
+
raise InvalidPayload, "#{claim} claim must be a Numeric value but it is a #{@payload[claim].class}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Claims
|
5
|
+
class Required
|
6
|
+
def initialize(required_claims:)
|
7
|
+
@required_claims = required_claims
|
8
|
+
end
|
9
|
+
|
10
|
+
def verify!(context:, **_args)
|
11
|
+
required_claims.each do |required_claim|
|
12
|
+
next if context.payload.is_a?(Hash) && context.payload.key?(required_claim)
|
13
|
+
|
14
|
+
raise JWT::MissingRequiredClaim, "Missing required claim #{required_claim}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :required_claims
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Claims
|
5
|
+
class Subject
|
6
|
+
def initialize(expected_subject:)
|
7
|
+
@expected_subject = expected_subject.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def verify!(context:, **_args)
|
11
|
+
sub = context.payload['sub']
|
12
|
+
raise(JWT::InvalidSubError, "Invalid subject. Expected #{expected_subject}, received #{sub || '<none>'}") unless sub.to_s == expected_subject
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :expected_subject
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/jwt/claims.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'claims/audience'
|
4
|
+
require_relative 'claims/expiration'
|
5
|
+
require_relative 'claims/issued_at'
|
6
|
+
require_relative 'claims/issuer'
|
7
|
+
require_relative 'claims/jwt_id'
|
8
|
+
require_relative 'claims/not_before'
|
9
|
+
require_relative 'claims/numeric'
|
10
|
+
require_relative 'claims/required'
|
11
|
+
require_relative 'claims/subject'
|
12
|
+
|
13
|
+
module JWT
|
14
|
+
module Claims
|
15
|
+
VerificationContext = Struct.new(:payload, keyword_init: true)
|
16
|
+
|
17
|
+
VERIFIERS = {
|
18
|
+
verify_expiration: ->(options) { Claims::Expiration.new(leeway: options[:exp_leeway] || options[:leeway]) },
|
19
|
+
verify_not_before: ->(options) { Claims::NotBefore.new(leeway: options[:nbf_leeway] || options[:leeway]) },
|
20
|
+
verify_iss: ->(options) { Claims::Issuer.new(issuers: options[:iss]) },
|
21
|
+
verify_iat: ->(*) { Claims::IssuedAt.new },
|
22
|
+
verify_jti: ->(options) { Claims::JwtId.new(validator: options[:verify_jti]) },
|
23
|
+
verify_aud: ->(options) { Claims::Audience.new(expected_audience: options[:aud]) },
|
24
|
+
verify_sub: ->(options) { options[:sub] && Claims::Subject.new(expected_subject: options[:sub]) },
|
25
|
+
required_claims: ->(options) { Claims::Required.new(required_claims: options[:required_claims]) }
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def verify!(payload, options)
|
30
|
+
VERIFIERS.each do |key, verifier_builder|
|
31
|
+
next unless options[key]
|
32
|
+
|
33
|
+
verifier_builder&.call(options)&.verify!(context: VerificationContext.new(payload: payload))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/jwt/decode.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
|
-
|
5
|
-
require 'jwt/verify'
|
6
4
|
require 'jwt/x5c_key_finder'
|
7
5
|
|
8
6
|
# JWT::Decode module
|
@@ -10,7 +8,7 @@ module JWT
|
|
10
8
|
# Decoding logic for JWT
|
11
9
|
class Decode
|
12
10
|
def initialize(jwt, key, verify, options, &keyfinder)
|
13
|
-
raise
|
11
|
+
raise JWT::DecodeError, 'Nil JSON web token' unless jwt
|
14
12
|
|
15
13
|
@jwt = jwt
|
16
14
|
@key = key
|
@@ -30,7 +28,7 @@ module JWT
|
|
30
28
|
verify_signature
|
31
29
|
verify_claims
|
32
30
|
end
|
33
|
-
raise
|
31
|
+
raise JWT::DecodeError, 'Not enough or too many segments' unless header && payload
|
34
32
|
|
35
33
|
[payload, header]
|
36
34
|
end
|
@@ -46,21 +44,21 @@ module JWT
|
|
46
44
|
|
47
45
|
return if Array(@key).any? { |key| verify_signature_for?(key) }
|
48
46
|
|
49
|
-
raise
|
47
|
+
raise JWT::VerificationError, 'Signature verification failed'
|
50
48
|
end
|
51
49
|
|
52
50
|
def verify_algo
|
53
|
-
raise
|
54
|
-
raise
|
55
|
-
raise
|
51
|
+
raise JWT::IncorrectAlgorithm, 'An algorithm must be specified' if allowed_algorithms.empty?
|
52
|
+
raise JWT::IncorrectAlgorithm, 'Token is missing alg header' unless alg_in_header
|
53
|
+
raise JWT::IncorrectAlgorithm, 'Expected a different algorithm' if allowed_and_valid_algorithms.empty?
|
56
54
|
end
|
57
55
|
|
58
56
|
def set_key
|
59
57
|
@key = find_key(&@keyfinder) if @keyfinder
|
60
58
|
@key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(header['kid']) if @options[:jwks]
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
return unless (x5c_options = @options[:x5c])
|
60
|
+
|
61
|
+
@key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
|
64
62
|
end
|
65
63
|
|
66
64
|
def verify_signature_for?(key)
|
@@ -92,7 +90,7 @@ module JWT
|
|
92
90
|
end
|
93
91
|
|
94
92
|
def resolve_allowed_algorithms
|
95
|
-
algs = given_algorithms.map { |alg| JWA.
|
93
|
+
algs = given_algorithms.map { |alg| JWA.resolve(alg) }
|
96
94
|
|
97
95
|
sort_by_alg_header(algs)
|
98
96
|
end
|
@@ -113,8 +111,7 @@ module JWT
|
|
113
111
|
end
|
114
112
|
|
115
113
|
def verify_claims
|
116
|
-
|
117
|
-
Verify.verify_required_claims(payload, @options)
|
114
|
+
Claims.verify!(payload, @options)
|
118
115
|
end
|
119
116
|
|
120
117
|
def validate_segment_count!
|
@@ -122,7 +119,7 @@ module JWT
|
|
122
119
|
return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
|
123
120
|
return if segment_length == 2 && none_algorithm?
|
124
121
|
|
125
|
-
raise
|
122
|
+
raise JWT::DecodeError, 'Not enough or too many segments'
|
126
123
|
end
|
127
124
|
|
128
125
|
def segment_length
|
data/lib/jwt/deprecations.rb
CHANGED
@@ -4,15 +4,34 @@ module JWT
|
|
4
4
|
# Deprecations module to handle deprecation warnings in the gem
|
5
5
|
module Deprecations
|
6
6
|
class << self
|
7
|
-
def
|
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
|
8
15
|
case JWT.configuration.deprecation_warnings
|
9
|
-
when :warn
|
10
|
-
warn("[DEPRECATION WARNING] #{message}")
|
11
16
|
when :once
|
12
17
|
return if record_warned(message)
|
13
|
-
|
14
|
-
|
18
|
+
when :warn
|
19
|
+
# noop
|
20
|
+
else
|
21
|
+
return
|
15
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) }
|
16
35
|
end
|
17
36
|
|
18
37
|
private
|
data/lib/jwt/encode.rb
CHANGED
@@ -1,20 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'jwa'
|
4
|
-
require_relative 'claims_validator'
|
5
4
|
|
6
5
|
# JWT::Encode module
|
7
6
|
module JWT
|
8
7
|
# Encoding logic for JWT
|
9
8
|
class Encode
|
10
|
-
ALG_KEY = 'alg'
|
11
|
-
|
12
9
|
def initialize(options)
|
13
10
|
@payload = options[:payload]
|
14
11
|
@key = options[:key]
|
15
|
-
@algorithm = JWA.
|
12
|
+
@algorithm = JWA.resolve(options[:algorithm])
|
16
13
|
@headers = options[:headers].transform_keys(&:to_s)
|
17
|
-
@headers[ALG_KEY] = @algorithm.alg
|
18
14
|
end
|
19
15
|
|
20
16
|
def segments
|
@@ -41,7 +37,7 @@ module JWT
|
|
41
37
|
end
|
42
38
|
|
43
39
|
def encode_header
|
44
|
-
encode_data(@headers)
|
40
|
+
encode_data(@headers.merge(@algorithm.header(signing_key: @key)))
|
45
41
|
end
|
46
42
|
|
47
43
|
def encode_payload
|
@@ -55,7 +51,7 @@ module JWT
|
|
55
51
|
def validate_claims!
|
56
52
|
return unless @payload.is_a?(Hash)
|
57
53
|
|
58
|
-
|
54
|
+
Claims::Numeric.new(@payload).verify!
|
59
55
|
end
|
60
56
|
|
61
57
|
def encode_signature
|
data/lib/jwt/jwa/ecdsa.rb
CHANGED
@@ -2,8 +2,35 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module JWA
|
5
|
-
|
6
|
-
|
5
|
+
class Ecdsa
|
6
|
+
include JWT::JWA::SigningAlgorithm
|
7
|
+
|
8
|
+
def initialize(alg, digest)
|
9
|
+
@alg = alg
|
10
|
+
@digest = OpenSSL::Digest.new(digest)
|
11
|
+
end
|
12
|
+
|
13
|
+
def sign(data:, signing_key:)
|
14
|
+
curve_definition = curve_by_name(signing_key.group.curve_name)
|
15
|
+
key_algorithm = curve_definition[:algorithm]
|
16
|
+
if alg != key_algorithm
|
17
|
+
raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided"
|
18
|
+
end
|
19
|
+
|
20
|
+
asn1_to_raw(signing_key.dsa_sign_asn1(digest.digest(data)), signing_key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify(data:, signature:, verification_key:)
|
24
|
+
curve_definition = curve_by_name(verification_key.group.curve_name)
|
25
|
+
key_algorithm = curve_definition[:algorithm]
|
26
|
+
if alg != key_algorithm
|
27
|
+
raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided"
|
28
|
+
end
|
29
|
+
|
30
|
+
verification_key.dsa_verify_asn1(digest.digest(data), raw_to_asn1(signature, verification_key))
|
31
|
+
rescue OpenSSL::PKey::PKeyError
|
32
|
+
raise JWT::VerificationError, 'Signature verification raised'
|
33
|
+
end
|
7
34
|
|
8
35
|
NAMED_CURVES = {
|
9
36
|
'prime256v1' => {
|
@@ -28,36 +55,22 @@ module JWT
|
|
28
55
|
}
|
29
56
|
}.freeze
|
30
57
|
|
31
|
-
|
58
|
+
NAMED_CURVES.each_value do |v|
|
59
|
+
register_algorithm(new(v[:algorithm], v[:digest]))
|
60
|
+
end
|
32
61
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
if algorithm != key_algorithm
|
37
|
-
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
|
62
|
+
def self.curve_by_name(name)
|
63
|
+
NAMED_CURVES.fetch(name) do
|
64
|
+
raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
|
38
65
|
end
|
39
|
-
|
40
|
-
digest = OpenSSL::Digest.new(curve_definition[:digest])
|
41
|
-
asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
|
42
66
|
end
|
43
67
|
|
44
|
-
|
45
|
-
curve_definition = curve_by_name(public_key.group.curve_name)
|
46
|
-
key_algorithm = curve_definition[:algorithm]
|
47
|
-
if algorithm != key_algorithm
|
48
|
-
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
|
49
|
-
end
|
68
|
+
private
|
50
69
|
|
51
|
-
|
52
|
-
public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
|
53
|
-
rescue OpenSSL::PKey::PKeyError
|
54
|
-
raise JWT::VerificationError, 'Signature verification raised'
|
55
|
-
end
|
70
|
+
attr_reader :digest
|
56
71
|
|
57
72
|
def curve_by_name(name)
|
58
|
-
|
59
|
-
raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
|
60
|
-
end
|
73
|
+
self.class.curve_by_name(name)
|
61
74
|
end
|
62
75
|
|
63
76
|
def raw_to_asn1(signature, private_key)
|