jwt 2.8.2 → 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 +14 -0
- data/README.md +19 -11
- 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/decode.rb +2 -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/version.rb +2 -2
- data/lib/jwt.rb +1 -0
- 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,19 @@
|
|
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
|
+
|
3
17
|
## [v2.8.2](https://github.com/jwt/ruby-jwt/tree/v2.8.2) (2024-06-18)
|
4
18
|
|
5
19
|
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.1...v2.8.2)
|
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
|
|
@@ -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
|
@@ -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!
|
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)
|
data/lib/jwt/jwa/eddsa.rb
CHANGED
@@ -2,41 +2,33 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module JWA
|
5
|
-
|
6
|
-
|
7
|
-
SUPPORTED_DOWNCASED = SUPPORTED.map(&:downcase).freeze
|
5
|
+
class Eddsa
|
6
|
+
include JWT::JWA::SigningAlgorithm
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey"
|
13
|
-
end
|
14
|
-
|
15
|
-
validate_algorithm!(algorithm)
|
8
|
+
def initialize(alg)
|
9
|
+
@alg = alg
|
10
|
+
end
|
16
11
|
|
17
|
-
|
12
|
+
def sign(data:, signing_key:)
|
13
|
+
unless signing_key.is_a?(RbNaCl::Signatures::Ed25519::SigningKey)
|
14
|
+
raise_encode_error!("Key given is a #{signing_key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey")
|
18
15
|
end
|
19
16
|
|
20
|
-
|
21
|
-
|
22
|
-
raise DecodeError, "key given is a #{public_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey"
|
23
|
-
end
|
24
|
-
|
25
|
-
validate_algorithm!(algorithm)
|
17
|
+
signing_key.sign(data)
|
18
|
+
end
|
26
19
|
|
27
|
-
|
28
|
-
|
29
|
-
|
20
|
+
def verify(data:, signature:, verification_key:)
|
21
|
+
unless verification_key.is_a?(RbNaCl::Signatures::Ed25519::VerifyKey)
|
22
|
+
raise_decode_error!("key given is a #{verification_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey")
|
30
23
|
end
|
31
24
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
return if SUPPORTED_DOWNCASED.include?(algorithm.downcase)
|
36
|
-
|
37
|
-
raise IncorrectAlgorithm, "Algorithm #{algorithm} not supported. Supported algoritms are #{SUPPORTED.join(', ')}"
|
38
|
-
end
|
25
|
+
verification_key.verify(signature, data)
|
26
|
+
rescue RbNaCl::CryptoError
|
27
|
+
false
|
39
28
|
end
|
29
|
+
|
30
|
+
register_algorithm(new('ED25519'))
|
31
|
+
register_algorithm(new('EdDSA'))
|
40
32
|
end
|
41
33
|
end
|
42
34
|
end
|
data/lib/jwt/jwa/hmac.rb
CHANGED
@@ -2,35 +2,39 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module JWA
|
5
|
-
|
6
|
-
|
5
|
+
class Hmac
|
6
|
+
include JWT::JWA::SigningAlgorithm
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
}.freeze
|
13
|
-
|
14
|
-
SUPPORTED = MAPPING.keys
|
15
|
-
|
16
|
-
def sign(algorithm, msg, key)
|
17
|
-
key ||= ''
|
8
|
+
def initialize(alg, digest)
|
9
|
+
@alg = alg
|
10
|
+
@digest = digest
|
11
|
+
end
|
18
12
|
|
19
|
-
|
13
|
+
def sign(data:, signing_key:)
|
14
|
+
signing_key ||= ''
|
15
|
+
raise_verify_error!('HMAC key expected to be a String') unless signing_key.is_a?(String)
|
20
16
|
|
21
|
-
OpenSSL::HMAC.digest(
|
17
|
+
OpenSSL::HMAC.digest(digest.new, signing_key, data)
|
22
18
|
rescue OpenSSL::HMACError => e
|
23
|
-
if
|
24
|
-
|
19
|
+
if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure'
|
20
|
+
raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret')
|
25
21
|
end
|
26
22
|
|
27
23
|
raise e
|
28
24
|
end
|
29
25
|
|
30
|
-
def verify(
|
31
|
-
SecurityUtils.secure_compare(signature, sign(
|
26
|
+
def verify(data:, signature:, verification_key:)
|
27
|
+
SecurityUtils.secure_compare(signature, sign(data: data, signing_key: verification_key))
|
32
28
|
end
|
33
29
|
|
30
|
+
register_algorithm(new('HS256', OpenSSL::Digest::SHA256))
|
31
|
+
register_algorithm(new('HS384', OpenSSL::Digest::SHA384))
|
32
|
+
register_algorithm(new('HS512', OpenSSL::Digest::SHA512))
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :digest
|
37
|
+
|
34
38
|
# Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb
|
35
39
|
# rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
|
36
40
|
module SecurityUtils
|