jwt 2.8.1 → 2.9.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/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)
|