jwt 3.1.2 → 3.3.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 +40 -7
- data/CONTRIBUTING.md +2 -2
- data/README.md +12 -4
- data/UPGRADING.md +54 -0
- data/lib/jwt/claims/decode_verifier.rb +1 -1
- data/lib/jwt/claims/issued_at.rb +12 -1
- data/lib/jwt/claims/verifier.rb +5 -3
- data/lib/jwt/claims.rb +1 -1
- data/lib/jwt/configuration/decode_configuration.rb +8 -3
- data/lib/jwt/decode.rb +12 -6
- data/lib/jwt/encoded_token/claims_context.rb +23 -0
- data/lib/jwt/encoded_token.rb +24 -30
- data/lib/jwt/error.rb +50 -23
- data/lib/jwt/jwa/ecdsa.rb +9 -4
- data/lib/jwt/jwa/hmac.rb +27 -10
- data/lib/jwt/jwa/ps.rb +3 -0
- data/lib/jwt/jwa/rsa.rb +3 -0
- data/lib/jwt/jwa/signer_context.rb +19 -0
- data/lib/jwt/jwa/signing_algorithm.rb +1 -1
- data/lib/jwt/jwa/unsupported.rb +1 -1
- data/lib/jwt/jwa/verifier_context.rb +21 -0
- data/lib/jwt/jwa.rb +4 -32
- data/lib/jwt/jwk/ec.rb +1 -1
- data/lib/jwt/jwk/hmac.rb +1 -1
- data/lib/jwt/jwk/key_finder.rb +4 -4
- data/lib/jwt/jwk/rsa.rb +2 -2
- data/lib/jwt/jwk/set.rb +28 -14
- data/lib/jwt/jwk.rb +1 -1
- data/lib/jwt/token.rb +2 -1
- data/lib/jwt/version.rb +2 -10
- data/lib/jwt/x5c_key_finder.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b0bbac64c8b97deb791cc74f29243c6c04cbc2c3a63904484259c5379a2650f
|
|
4
|
+
data.tar.gz: 0252d7993ad937c9cb961d4f2ff4ce9cef9fc7b503fadb2ba4e85923752ed6b4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cdef3e40a9879182bd028df5dad15676280ba567839a12d3ca31f9569c9b1d44b02f959b256e1d95d1c9d3e882a3ab6d944dbcce0f22d31a0658cd5589e14917
|
|
7
|
+
data.tar.gz: 88f3879e2061b072b15c1a001ff7c62e674fcf61a2edd610b19f95b9aac9dfd7fd07ce2aa416a3859bcc1afb3e59f18cf12e95898027102bd16f13103d04aa26
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v3.3.0](https://github.com/jwt/ruby-jwt/tree/v3.3.0) (2026-09-11)
|
|
4
|
+
|
|
5
|
+
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.2.0...v3.3.0)
|
|
6
|
+
|
|
7
|
+
**Features:**
|
|
8
|
+
|
|
9
|
+
- Allow a leeway to be given for the `iat` claim verification [#747](https://github.com/jwt/ruby-jwt/pull/747) - ([@denis1011101](https://github.com/denis1011101))
|
|
10
|
+
- Revamp the error hierarchy under a new `JWT::Error` base class; signing failures now consistently raise `JWT::EncodeError`, see [UPGRADING.md](UPGRADING.md) [#722](https://github.com/jwt/ruby-jwt/pull/722) ([@anakinj](https://github.com/anakinj))
|
|
11
|
+
|
|
12
|
+
**Fixes and enhancements:**
|
|
13
|
+
|
|
14
|
+
- Refactor `JWT::JWK::Set#initialize` so each construction path is a named method [#758](https://github.com/jwt/ruby-jwt/pull/758) ([@anakinj](https://github.com/anakinj))
|
|
15
|
+
- Fix rejection of unknown algorithms from JWKs for RFC compliance and pquip [#728](https://github.com/jwt/ruby-jwt/pull/728)
|
|
16
|
+
- Fix the `Style/DirectiveScope` RuboCop offense failing the build [#752](https://github.com/jwt/ruby-jwt/pull/752)
|
|
17
|
+
- Fix `JWT::JWK::Set` sharing its key collection with the set it was copied from [#751](https://github.com/jwt/ruby-jwt/pull/751)
|
|
18
|
+
- Reset the decoded payload and verification state in `JWT::EncodedToken#encoded_payload=` [#749](https://github.com/jwt/ruby-jwt/issues/749)
|
|
19
|
+
- Fix `JWT::Token#detach_payload!` not invalidating an already rendered token [#748](https://github.com/jwt/ruby-jwt/issues/748)
|
|
20
|
+
|
|
21
|
+
## [v3.2.0](https://github.com/jwt/ruby-jwt/tree/v3.2.0) (2026-05-13)
|
|
22
|
+
|
|
23
|
+
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.1.2...v3.2.0)
|
|
24
|
+
|
|
25
|
+
**Features:**
|
|
26
|
+
|
|
27
|
+
- Add `enforce_hmac_key_length` configuration option [#716](https://github.com/jwt/ruby-jwt/pull/716) - ([@304](https://github.com/304))
|
|
28
|
+
|
|
29
|
+
**Fixes and enhancements:**
|
|
30
|
+
|
|
31
|
+
- Reject `nil` and empty HMAC keys when signing and verifying ([CVE-2026-45363](https://www.cve.org/CVERecord?id=CVE-2026-45363) / [GHSA-c32j-vqhx-rx3x](https://github.com/jwt/ruby-jwt/security/advisories/GHSA-c32j-vqhx-rx3x))
|
|
32
|
+
- Fix compatibility with the openssl 4.0 gem [#706](https://github.com/jwt/ruby-jwt/pull/706)
|
|
33
|
+
- Test against Ruby 4.0 on CI [#707](https://github.com/jwt/ruby-jwt/pull/707)
|
|
34
|
+
- Fix type error when header is not a JSON object [#715](https://github.com/jwt/ruby-jwt/pull/715) - ([@304](https://github.com/304))
|
|
35
|
+
|
|
3
36
|
## [v3.1.2](https://github.com/jwt/ruby-jwt/tree/v3.1.2) (2025-06-28)
|
|
4
37
|
|
|
5
38
|
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.1.1...v3.1.2)
|
|
@@ -74,7 +107,7 @@ Take a look at the [upgrade guide](UPGRADING.md) for more details.
|
|
|
74
107
|
- JWT::Token and JWT::EncodedToken for signing and verifying tokens [#621](https://github.com/jwt/ruby-jwt/pull/621) ([@anakinj](https://github.com/anakinj))
|
|
75
108
|
- Detached payload support for JWT::Token and JWT::EncodedToken [#630](https://github.com/jwt/ruby-jwt/pull/630) ([@anakinj](https://github.com/anakinj))
|
|
76
109
|
- Skip decoding payload if b64 header is present and false [#631](https://github.com/jwt/ruby-jwt/pull/631) ([@anakinj](https://github.com/anakinj))
|
|
77
|
-
- Remove a few custom
|
|
110
|
+
- Remove a few custom RuboCop configs [#638](https://github.com/jwt/ruby-jwt/pull/638) ([@anakinj](https://github.com/anakinj))
|
|
78
111
|
|
|
79
112
|
**Fixes and enhancements:**
|
|
80
113
|
|
|
@@ -352,7 +385,7 @@ Take a look at the [upgrade guide](UPGRADING.md) for more details.
|
|
|
352
385
|
**Implemented enhancements:**
|
|
353
386
|
|
|
354
387
|
- JWK does not decode. [\#332](https://github.com/jwt/ruby-jwt/issues/332)
|
|
355
|
-
- Inconsistent use of symbol and string keys in args \(exp and
|
|
388
|
+
- Inconsistent use of symbol and string keys in args \(exp and algorithm\). [\#331](https://github.com/jwt/ruby-jwt/issues/331)
|
|
356
389
|
- Pin simplecov to \< 0.18 [\#356](https://github.com/jwt/ruby-jwt/pull/356) ([anakinj](https://github.com/anakinj))
|
|
357
390
|
- verifies algorithm before evaluating keyfinder [\#346](https://github.com/jwt/ruby-jwt/pull/346) ([jb08](https://github.com/jb08))
|
|
358
391
|
- Update Rails 6 appraisal to use actual release version [\#336](https://github.com/jwt/ruby-jwt/pull/336) ([smudge](https://github.com/smudge))
|
|
@@ -467,7 +500,7 @@ Take a look at the [upgrade guide](UPGRADING.md) for more details.
|
|
|
467
500
|
- 'DecodeError'will replace 'ExpiredSignature' [\#260](https://github.com/jwt/ruby-jwt/issues/260)
|
|
468
501
|
- TypeError: no implicit conversion of OpenSSL::PKey::RSA into String [\#259](https://github.com/jwt/ruby-jwt/issues/259)
|
|
469
502
|
- NameError: uninitialized constant JWT::Algos::Eddsa::RbNaCl [\#258](https://github.com/jwt/ruby-jwt/issues/258)
|
|
470
|
-
- Get new token if
|
|
503
|
+
- Get new token if current token expired [\#256](https://github.com/jwt/ruby-jwt/issues/256)
|
|
471
504
|
- Infer algorithm from header [\#254](https://github.com/jwt/ruby-jwt/issues/254)
|
|
472
505
|
- Why is the result of decode is an array? [\#252](https://github.com/jwt/ruby-jwt/issues/252)
|
|
473
506
|
- Add support for headless token [\#251](https://github.com/jwt/ruby-jwt/issues/251)
|
|
@@ -686,8 +719,8 @@ Take a look at the [upgrade guide](UPGRADING.md) for more details.
|
|
|
686
719
|
**Implemented enhancements:**
|
|
687
720
|
|
|
688
721
|
- Refactor obsolete code for ruby 1.8 support [\#120](https://github.com/jwt/ruby-jwt/issues/120)
|
|
689
|
-
- Fix "
|
|
690
|
-
- Fix "
|
|
722
|
+
- Fix "RuboCop/Metrics/CyclomaticComplexity" issue in lib/jwt.rb [\#106](https://github.com/jwt/ruby-jwt/issues/106)
|
|
723
|
+
- Fix "RuboCop/Metrics/CyclomaticComplexity" issue in lib/jwt.rb [\#105](https://github.com/jwt/ruby-jwt/issues/105)
|
|
691
724
|
- Allow a proc to be passed for JTI verification [\#126](https://github.com/jwt/ruby-jwt/pull/126) ([yahooguntu](https://github.com/yahooguntu))
|
|
692
725
|
- Relax restrictions on "jti" claim verification [\#113](https://github.com/jwt/ruby-jwt/pull/113) ([lwe](https://github.com/lwe))
|
|
693
726
|
|
|
@@ -833,13 +866,13 @@ Take a look at the [upgrade guide](UPGRADING.md) for more details.
|
|
|
833
866
|
- Signature Verification to Return Verification Error rather than decode error [\#57](https://github.com/jwt/ruby-jwt/issues/57)
|
|
834
867
|
- Incorrect readme for leeway [\#55](https://github.com/jwt/ruby-jwt/issues/55)
|
|
835
868
|
- What is the reason behind stripping the = in base64 encoding? [\#54](https://github.com/jwt/ruby-jwt/issues/54)
|
|
836
|
-
-
|
|
869
|
+
- Preparations for version 2.x [\#50](https://github.com/jwt/ruby-jwt/issues/50)
|
|
837
870
|
- Release a new version [\#47](https://github.com/jwt/ruby-jwt/issues/47)
|
|
838
871
|
- Catch up for ActiveWhatever 4.1.1 series [\#40](https://github.com/jwt/ruby-jwt/issues/40)
|
|
839
872
|
|
|
840
873
|
**Merged pull requests:**
|
|
841
874
|
|
|
842
|
-
- raise verification error for
|
|
875
|
+
- raise verification error for signature verification [\#58](https://github.com/jwt/ruby-jwt/pull/58) ([punkle](https://github.com/punkle))
|
|
843
876
|
- Added support for not before claim verification [\#56](https://github.com/jwt/ruby-jwt/pull/56) ([punkle](https://github.com/punkle))
|
|
844
877
|
|
|
845
878
|
## [jwt-1.2.1](https://github.com/jwt/ruby-jwt/tree/jwt-1.2.1) (2015-01-22)
|
data/CONTRIBUTING.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Forking the project
|
|
4
4
|
|
|
5
|
-
Fork the project on GitHub and clone your own fork.
|
|
5
|
+
Fork the project on GitHub and clone your own fork. Instructions on forking can be found from the [GitHub Docs](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
git clone git@github.com:you/ruby-jwt.git
|
|
@@ -28,7 +28,7 @@ Before you start with your implementation make sure you are able to get a succes
|
|
|
28
28
|
|
|
29
29
|
The tests are written with rspec and [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
|
|
30
30
|
|
|
31
|
-
[
|
|
31
|
+
[RuboCop](https://github.com/rubocop/rubocop) is used to enforce the Ruby style.
|
|
32
32
|
|
|
33
33
|
To run the complete set of tests and linter run the following
|
|
34
34
|
|
data/README.md
CHANGED
|
@@ -532,7 +532,9 @@ end
|
|
|
532
532
|
|
|
533
533
|
From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.6):
|
|
534
534
|
|
|
535
|
-
> The `iat` (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT.
|
|
535
|
+
> The `iat` (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL.
|
|
536
|
+
|
|
537
|
+
The global `leeway` option does not apply to `iat`. To allow for clock drift, pass `leeway` under `verify_iat`, as shown below. The `iat_leeway` option was removed in version 2.2.0.
|
|
536
538
|
|
|
537
539
|
```ruby
|
|
538
540
|
iat = Time.now.to_i
|
|
@@ -548,6 +550,12 @@ rescue JWT::InvalidIatError
|
|
|
548
550
|
end
|
|
549
551
|
```
|
|
550
552
|
|
|
553
|
+
By default, `iat` verification allows no clock drift. To allow drift between the issuer and verifier clocks, pass a leeway value explicitly:
|
|
554
|
+
|
|
555
|
+
```ruby
|
|
556
|
+
decoded_token = JWT.decode(token, hmac_secret, true, { verify_iat: { leeway: 30 }, algorithm: 'HS256' })
|
|
557
|
+
```
|
|
558
|
+
|
|
551
559
|
### Subject Claim
|
|
552
560
|
|
|
553
561
|
From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.2):
|
|
@@ -632,7 +640,7 @@ end
|
|
|
632
640
|
|
|
633
641
|
begin
|
|
634
642
|
JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls } })
|
|
635
|
-
rescue JWT::
|
|
643
|
+
rescue JWT::TokenError
|
|
636
644
|
# Handle error, e.g. x5c header certificate revoked or expired
|
|
637
645
|
end
|
|
638
646
|
```
|
|
@@ -697,8 +705,8 @@ begin
|
|
|
697
705
|
JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks_loader })
|
|
698
706
|
rescue JWT::JWKError
|
|
699
707
|
# Handle problems with the provided JWKs
|
|
700
|
-
rescue JWT::
|
|
701
|
-
# Handle other
|
|
708
|
+
rescue JWT::TokenError
|
|
709
|
+
# Handle other token related issues e.g. no kid in header, no matching public key found etc.
|
|
702
710
|
end
|
|
703
711
|
```
|
|
704
712
|
|
data/UPGRADING.md
CHANGED
|
@@ -1,3 +1,57 @@
|
|
|
1
|
+
# Upgrading ruby-jwt to >= 3.3.0
|
|
2
|
+
|
|
3
|
+
## Error hierarchy revamp
|
|
4
|
+
|
|
5
|
+
The [error classes were reorganised](https://github.com/jwt/ruby-jwt/pull/722) under a new `JWT::Error` base class, so failures can be rescued by category instead of one class at a time:
|
|
6
|
+
|
|
7
|
+
- `JWT::Error` is the base class for everything the gem raises.
|
|
8
|
+
- `JWT::TokenError` covers every failure in processing a token, and splits into `JWT::MalformedTokenError` (the token is structurally invalid), `JWT::SignatureError` (signature and algorithm problems) and `JWT::ClaimValidationError` (a claim did not verify).
|
|
9
|
+
- `JWT::VerificationKeyError`, a subclass of `JWT::VerificationError`, says the key or algorithm given for verification cannot be used, as opposed to a signature that does not match.
|
|
10
|
+
|
|
11
|
+
### Backwards compatibility
|
|
12
|
+
|
|
13
|
+
This is a backwards compatible change for effectively every application. The new classes were inserted above the existing ones rather than replacing them, so every error class you already rescue keeps its name, its meaning and everything it used to catch. In the ordinary case, upgrading needs no code change at all.
|
|
14
|
+
|
|
15
|
+
There is one exception, and it is narrow enough to be worth stating precisely. It applies only if all three of the following are true:
|
|
16
|
+
|
|
17
|
+
1. You rescue around `JWT.encode`, not around `JWT.decode`.
|
|
18
|
+
2. The class you rescue is `JWT::DecodeError`, `JWT::IncorrectAlgorithm`, `JWT::UnsupportedEcdsaCurve` or `ArgumentError`.
|
|
19
|
+
3. That rescue is reached at all, which takes a key or algorithm that cannot sign in the first place.
|
|
20
|
+
|
|
21
|
+
If any one of the three does not hold, there is nothing to do. If all three do, the fix is a one line change, described in the next section.
|
|
22
|
+
|
|
23
|
+
Why that is a small target in practice:
|
|
24
|
+
|
|
25
|
+
- **Decoding is untouched.** `JWT::DecodeError` is deprecated in favour of the classes above, but it keeps its meaning: every error class except `JWT::EncodeError` still inherits from it. A `rescue JWT::DecodeError` around `JWT.decode` catches everything it caught before, and the specific classes it has always raised, such as `JWT::ExpiredSignature`, are unchanged.
|
|
26
|
+
- **Signing that currently works is untouched.** Every case in the table below is an unusable key or algorithm, a misconfiguration that fails on every call made with that key. None of them can be triggered by a particular payload or token, so an application that signs tokens successfully today does not reach them at all.
|
|
27
|
+
- **Failures you let escape are untouched.** All of these raised before and still raise; only the class changed. Code that does not rescue them behaves exactly as it did.
|
|
28
|
+
|
|
29
|
+
One decode-side behaviour did change, but in the direction of catching more: `RS*` and `PS*` now reject a key of the wrong type with a `JWT::VerificationKeyError`, which is a `JWT::DecodeError`, instead of letting a `NoMethodError` escape.
|
|
30
|
+
|
|
31
|
+
### Signing failures now raise `JWT::EncodeError`
|
|
32
|
+
|
|
33
|
+
Signing failures used to surface as decode errors, and are now consistently `JWT::EncodeError`, which is deliberately not a `JWT::DecodeError`:
|
|
34
|
+
|
|
35
|
+
| Signing with | Used to raise | Now raises |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| a `nil`, empty or too short HMAC key | `JWT::DecodeError` | `JWT::EncodeError` |
|
|
38
|
+
| an ECDSA key whose curve does not match the algorithm | `JWT::IncorrectAlgorithm` | `JWT::EncodeError` |
|
|
39
|
+
| an ECDSA key on an unsupported curve | `JWT::UnsupportedEcdsaCurve` | `JWT::EncodeError` |
|
|
40
|
+
| a JWK whose `alg` does not match the algorithm | `JWT::DecodeError` | `JWT::EncodeError` |
|
|
41
|
+
| an `RS*` or `PS*` public key | `ArgumentError` | `JWT::EncodeError` |
|
|
42
|
+
|
|
43
|
+
If you wrap `JWT.encode` in `rescue JWT::DecodeError`, `rescue JWT::IncorrectAlgorithm` or `rescue JWT::UnsupportedEcdsaCurve`, rescue `JWT::EncodeError` or `JWT::Error` instead.
|
|
44
|
+
|
|
45
|
+
### Why this is a minor release
|
|
46
|
+
|
|
47
|
+
The exception described above is a real incompatibility, and 3.3.0 is still deliberately a minor release rather than a new major.
|
|
48
|
+
|
|
49
|
+
The reason is that nothing here turns a call that used to succeed into one that fails, or the other way round. Every case in the table raised an error before and raises an error now, only under a different class. No token is signed that would previously have been refused, no token verifies that would previously have been rejected, and no signature is produced or accepted on different terms than before. What changed is which `rescue` clause matches on a path that was already failing.
|
|
50
|
+
|
|
51
|
+
Weighed against the cost of a second major migration so soon after 3.0.0, that did not seem to warrant one.
|
|
52
|
+
|
|
53
|
+
If you find a case where this change affects whether a call succeeds, rather than which error it raises when it fails, please [open an issue](https://github.com/jwt/ruby-jwt/issues). That would be a bug rather than an intended consequence of the reorganisation.
|
|
54
|
+
|
|
1
55
|
# Upgrading ruby-jwt to >= 3.0.0
|
|
2
56
|
|
|
3
57
|
## Removal of the indirect [RbNaCl](https://github.com/RubyCrypto/rbnacl) dependency
|
|
@@ -15,7 +15,7 @@ module JWT
|
|
|
15
15
|
verify_expiration: ->(options) { Claims::Expiration.new(leeway: options[:exp_leeway] || options[:leeway]) },
|
|
16
16
|
verify_not_before: ->(options) { Claims::NotBefore.new(leeway: options[:nbf_leeway] || options[:leeway]) },
|
|
17
17
|
verify_iss: ->(options) { options[:iss] && Claims::Issuer.new(issuers: options[:iss]) },
|
|
18
|
-
verify_iat: ->(
|
|
18
|
+
verify_iat: ->(options) { Claims::IssuedAt.new(leeway: options[:verify_iat].is_a?(Hash) ? options[:verify_iat][:leeway] : nil) },
|
|
19
19
|
verify_jti: ->(options) { Claims::JwtId.new(validator: options[:verify_jti]) },
|
|
20
20
|
verify_aud: ->(options) { options[:aud] && Claims::Audience.new(expected_audience: options[:aud]) },
|
|
21
21
|
verify_sub: ->(options) { options[:sub] && Claims::Subject.new(expected_subject: options[:sub]) },
|
data/lib/jwt/claims/issued_at.rb
CHANGED
|
@@ -4,6 +4,13 @@ module JWT
|
|
|
4
4
|
module Claims
|
|
5
5
|
# The IssuedAt class is responsible for validating the issued at claim ('iat') in a JWT token.
|
|
6
6
|
class IssuedAt
|
|
7
|
+
# Initializes a new IssuedAt instance.
|
|
8
|
+
#
|
|
9
|
+
# @param leeway [Integer] the drift (in seconds) to allow between the clock of the issuer and the clock of the verifier. Default: 0.
|
|
10
|
+
def initialize(leeway: 0)
|
|
11
|
+
@leeway = leeway || 0
|
|
12
|
+
end
|
|
13
|
+
|
|
7
14
|
# Verifies the issued at claim ('iat') in the JWT token.
|
|
8
15
|
#
|
|
9
16
|
# @param context [Object] the context containing the JWT payload.
|
|
@@ -15,8 +22,12 @@ module JWT
|
|
|
15
22
|
return unless context.payload.key?('iat')
|
|
16
23
|
|
|
17
24
|
iat = context.payload['iat']
|
|
18
|
-
raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(::Numeric) || iat.to_f > Time.now.to_f
|
|
25
|
+
raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(::Numeric) || iat.to_f > (Time.now.to_f + leeway)
|
|
19
26
|
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :leeway
|
|
20
31
|
end
|
|
21
32
|
end
|
|
22
33
|
end
|
data/lib/jwt/claims/verifier.rb
CHANGED
|
@@ -8,7 +8,7 @@ module JWT
|
|
|
8
8
|
exp: ->(options) { Claims::Expiration.new(leeway: options.dig(:exp, :leeway)) },
|
|
9
9
|
nbf: ->(options) { Claims::NotBefore.new(leeway: options.dig(:nbf, :leeway)) },
|
|
10
10
|
iss: ->(options) { Claims::Issuer.new(issuers: options[:iss]) },
|
|
11
|
-
iat: ->(
|
|
11
|
+
iat: ->(options) { Claims::IssuedAt.new(leeway: options.dig(:iat, :leeway)) },
|
|
12
12
|
jti: ->(options) { Claims::JwtId.new(validator: options[:jti]) },
|
|
13
13
|
aud: ->(options) { Claims::Audience.new(expected_audience: options[:aud]) },
|
|
14
14
|
sub: ->(options) { Claims::Subject.new(expected_subject: options[:sub]) },
|
|
@@ -33,8 +33,10 @@ module JWT
|
|
|
33
33
|
errors = []
|
|
34
34
|
iterate_verifiers(*options) do |verifier, verifier_options|
|
|
35
35
|
verify_one!(context, verifier, verifier_options)
|
|
36
|
-
rescue ::JWT::
|
|
37
|
-
|
|
36
|
+
rescue ::JWT::ClaimValidationError, ::JWT::MalformedTokenError => e
|
|
37
|
+
# A payload that cannot be decoded has no valid claims either, so the
|
|
38
|
+
# predicate API reports it instead of raising.
|
|
39
|
+
errors << JWT::Claims::Error.new(message: e.message)
|
|
38
40
|
end
|
|
39
41
|
errors
|
|
40
42
|
end
|
data/lib/jwt/claims.rb
CHANGED
|
@@ -41,7 +41,7 @@ module JWT
|
|
|
41
41
|
# @param payload [Hash] the JWT payload.
|
|
42
42
|
# @param options [Array] the options for verifying the claims.
|
|
43
43
|
# @return [void]
|
|
44
|
-
# @raise [JWT::
|
|
44
|
+
# @raise [JWT::ClaimValidationError] if any claim is invalid.
|
|
45
45
|
def verify_payload!(payload, *options)
|
|
46
46
|
Verifier.verify!(VerificationContext.new(payload: payload), *options)
|
|
47
47
|
end
|
|
@@ -11,7 +11,7 @@ module JWT
|
|
|
11
11
|
# @!attribute [rw] verify_iss
|
|
12
12
|
# @return [Boolean] whether to verify the issuer claim.
|
|
13
13
|
# @!attribute [rw] verify_iat
|
|
14
|
-
# @return [Boolean] whether to verify the issued at claim.
|
|
14
|
+
# @return [Boolean, Hash] whether to verify the issued at claim. A hash can be given to configure the claim, currently only `leeway` is supported.
|
|
15
15
|
# @!attribute [rw] verify_jti
|
|
16
16
|
# @return [Boolean] whether to verify the JWT ID claim.
|
|
17
17
|
# @!attribute [rw] verify_aud
|
|
@@ -24,6 +24,8 @@ module JWT
|
|
|
24
24
|
# @return [Array<String>] the list of acceptable algorithms.
|
|
25
25
|
# @!attribute [rw] required_claims
|
|
26
26
|
# @return [Array<String>] the list of required claims.
|
|
27
|
+
# @!attribute [rw] enforce_hmac_key_length
|
|
28
|
+
# @return [Boolean] whether to enforce minimum HMAC key lengths. false disables validation (default).
|
|
27
29
|
|
|
28
30
|
attr_accessor :verify_expiration,
|
|
29
31
|
:verify_not_before,
|
|
@@ -34,7 +36,8 @@ module JWT
|
|
|
34
36
|
:verify_sub,
|
|
35
37
|
:leeway,
|
|
36
38
|
:algorithms,
|
|
37
|
-
:required_claims
|
|
39
|
+
:required_claims,
|
|
40
|
+
:enforce_hmac_key_length
|
|
38
41
|
|
|
39
42
|
# Initializes a new DecodeConfiguration instance with default settings.
|
|
40
43
|
def initialize
|
|
@@ -48,6 +51,7 @@ module JWT
|
|
|
48
51
|
@leeway = 0
|
|
49
52
|
@algorithms = ['HS256']
|
|
50
53
|
@required_claims = []
|
|
54
|
+
@enforce_hmac_key_length = false
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
# @api private
|
|
@@ -62,7 +66,8 @@ module JWT
|
|
|
62
66
|
verify_sub: verify_sub,
|
|
63
67
|
leeway: leeway,
|
|
64
68
|
algorithms: algorithms,
|
|
65
|
-
required_claims: required_claims
|
|
69
|
+
required_claims: required_claims,
|
|
70
|
+
enforce_hmac_key_length: enforce_hmac_key_length
|
|
66
71
|
}
|
|
67
72
|
end
|
|
68
73
|
end
|
data/lib/jwt/decode.rb
CHANGED
|
@@ -18,9 +18,9 @@ module JWT
|
|
|
18
18
|
# @param verify [Boolean] whether to verify the token's signature.
|
|
19
19
|
# @param options [Hash] additional options for decoding and verification.
|
|
20
20
|
# @param keyfinder [Proc] an optional key finder block to dynamically find the key for verification.
|
|
21
|
-
# @raise [JWT::
|
|
21
|
+
# @raise [JWT::Error] if decoding or verification fails.
|
|
22
22
|
def initialize(jwt, key, verify, options, &keyfinder)
|
|
23
|
-
raise JWT::
|
|
23
|
+
raise JWT::MalformedTokenError, 'Nil JSON web token' unless jwt
|
|
24
24
|
|
|
25
25
|
@token = EncodedToken.new(jwt)
|
|
26
26
|
@key = key
|
|
@@ -51,14 +51,14 @@ module JWT
|
|
|
51
51
|
def verify_signature
|
|
52
52
|
return if none_algorithm?
|
|
53
53
|
|
|
54
|
-
raise JWT::
|
|
54
|
+
raise JWT::SignatureError, 'No verification key available' unless @key
|
|
55
55
|
|
|
56
56
|
token.verify_signature!(algorithm: allowed_and_valid_algorithms, key: @key)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def verify_algo
|
|
60
60
|
raise JWT::IncorrectAlgorithm, 'An algorithm must be specified' if allowed_algorithms.empty?
|
|
61
|
-
raise JWT::
|
|
61
|
+
raise JWT::MalformedTokenError, 'Token header not a JSON object' unless valid_token_header?
|
|
62
62
|
raise JWT::IncorrectAlgorithm, 'Token is missing alg header' unless alg_in_header
|
|
63
63
|
raise JWT::IncorrectAlgorithm, 'Expected a different algorithm' if allowed_and_valid_algorithms.empty?
|
|
64
64
|
end
|
|
@@ -100,7 +100,7 @@ module JWT
|
|
|
100
100
|
# key can be of type [string, nil, OpenSSL::PKey, Array]
|
|
101
101
|
return key if key && !Array(key).empty?
|
|
102
102
|
|
|
103
|
-
raise JWT::
|
|
103
|
+
raise JWT::SignatureError, 'No verification key available'
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
def validate_segment_count!
|
|
@@ -109,13 +109,19 @@ module JWT
|
|
|
109
109
|
return if !@verify && segment_count == 2 # If no verifying required, the signature is not needed
|
|
110
110
|
return if segment_count == 2 && none_algorithm?
|
|
111
111
|
|
|
112
|
-
raise JWT::
|
|
112
|
+
raise JWT::MalformedTokenError, 'Not enough or too many segments'
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
def none_algorithm?
|
|
116
|
+
return false unless valid_token_header?
|
|
117
|
+
|
|
116
118
|
alg_in_header == 'none'
|
|
117
119
|
end
|
|
118
120
|
|
|
121
|
+
def valid_token_header?
|
|
122
|
+
token.header.is_a?(Hash)
|
|
123
|
+
end
|
|
124
|
+
|
|
119
125
|
def alg_in_header
|
|
120
126
|
token.header['alg']
|
|
121
127
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
|
|
5
|
+
module JWT
|
|
6
|
+
# @private
|
|
7
|
+
class EncodedToken
|
|
8
|
+
# Allow access to the unverified payload for claim verification.
|
|
9
|
+
class ClaimsContext
|
|
10
|
+
extend Forwardable
|
|
11
|
+
|
|
12
|
+
def_delegators :@token, :header, :unverified_payload
|
|
13
|
+
|
|
14
|
+
def initialize(token)
|
|
15
|
+
@token = token
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def payload
|
|
19
|
+
unverified_payload
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/jwt/encoded_token.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'encoded_token/claims_context'
|
|
4
|
+
|
|
3
5
|
module JWT
|
|
4
6
|
# Represents an encoded JWT token
|
|
5
7
|
#
|
|
@@ -12,22 +14,6 @@ module JWT
|
|
|
12
14
|
# encoded_token.verify_signature!(algorithm: 'HS256', key: 'secret')
|
|
13
15
|
# encoded_token.payload # => {'pay' => 'load'}
|
|
14
16
|
class EncodedToken
|
|
15
|
-
# @private
|
|
16
|
-
# Allow access to the unverified payload for claim verification.
|
|
17
|
-
class ClaimsContext
|
|
18
|
-
extend Forwardable
|
|
19
|
-
|
|
20
|
-
def_delegators :@token, :header, :unverified_payload
|
|
21
|
-
|
|
22
|
-
def initialize(token)
|
|
23
|
-
@token = token
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def payload
|
|
27
|
-
unverified_payload
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
17
|
DEFAULT_CLAIMS = [:exp].freeze
|
|
32
18
|
|
|
33
19
|
private_constant(:DEFAULT_CLAIMS)
|
|
@@ -77,10 +63,10 @@ module JWT
|
|
|
77
63
|
# Returns the payload of the JWT token. Access requires the signature and claims to have been verified.
|
|
78
64
|
#
|
|
79
65
|
# @return [Hash] the payload.
|
|
80
|
-
# @raise [JWT::
|
|
66
|
+
# @raise [JWT::TokenError] if the signature has not been verified.
|
|
81
67
|
def payload
|
|
82
|
-
raise JWT::
|
|
83
|
-
raise JWT::
|
|
68
|
+
raise JWT::TokenError, 'Verify the token signature before accessing the payload' unless @signature_verified
|
|
69
|
+
raise JWT::TokenError, 'Verify the token claims before accessing the payload' unless @claims_verified
|
|
84
70
|
|
|
85
71
|
decoded_payload
|
|
86
72
|
end
|
|
@@ -91,10 +77,21 @@ module JWT
|
|
|
91
77
|
decoded_payload
|
|
92
78
|
end
|
|
93
79
|
|
|
94
|
-
#
|
|
80
|
+
# Returns the encoded payload of the JWT token.
|
|
95
81
|
#
|
|
96
82
|
# @return [String] the encoded payload.
|
|
97
|
-
|
|
83
|
+
attr_reader :encoded_payload
|
|
84
|
+
|
|
85
|
+
# Sets the encoded payload of the JWT token.
|
|
86
|
+
#
|
|
87
|
+
# Resets the verification state, requiring the token to be verified again.
|
|
88
|
+
#
|
|
89
|
+
# @param encoded_payload [String] the encoded payload.
|
|
90
|
+
def encoded_payload=(encoded_payload)
|
|
91
|
+
@encoded_payload = encoded_payload
|
|
92
|
+
@decoded_payload = nil
|
|
93
|
+
@signature_verified = @claims_verified = false
|
|
94
|
+
end
|
|
98
95
|
|
|
99
96
|
# Returns the signing input of the JWT token.
|
|
100
97
|
#
|
|
@@ -112,7 +109,7 @@ module JWT
|
|
|
112
109
|
# @param signature [Hash] the parameters for signature verification (see {#verify_signature!}).
|
|
113
110
|
# @param claims [Array<Symbol>, Hash] the claims to verify (see {#verify_claims!}).
|
|
114
111
|
# @return [nil]
|
|
115
|
-
# @raise [JWT::
|
|
112
|
+
# @raise [JWT::Error] if the signature or claim verification fails.
|
|
116
113
|
def verify!(signature:, claims: nil)
|
|
117
114
|
verify_signature!(**signature)
|
|
118
115
|
claims.is_a?(Array) ? verify_claims!(*claims) : verify_claims!(claims)
|
|
@@ -166,7 +163,8 @@ module JWT
|
|
|
166
163
|
|
|
167
164
|
# Verifies the claims of the token.
|
|
168
165
|
# @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
|
|
169
|
-
# @raise [JWT::
|
|
166
|
+
# @raise [JWT::ClaimValidationError] if the claims are invalid.
|
|
167
|
+
# @raise [JWT::MalformedTokenError] if the payload cannot be decoded, which happens before any claim is validated.
|
|
170
168
|
def verify_claims!(*options)
|
|
171
169
|
Claims::Verifier.verify!(ClaimsContext.new(self), *claims_options(options)).tap do
|
|
172
170
|
@claims_verified = true
|
|
@@ -201,11 +199,11 @@ module JWT
|
|
|
201
199
|
end
|
|
202
200
|
|
|
203
201
|
def decode_payload
|
|
204
|
-
raise JWT::
|
|
202
|
+
raise JWT::MalformedTokenError, 'Encoded payload is empty' if encoded_payload == ''
|
|
205
203
|
|
|
206
204
|
if unencoded_payload?
|
|
207
205
|
verify_claims!(crit: ['b64'])
|
|
208
|
-
return
|
|
206
|
+
return parse(encoded_payload)
|
|
209
207
|
end
|
|
210
208
|
|
|
211
209
|
parse_and_decode(encoded_payload)
|
|
@@ -219,14 +217,10 @@ module JWT
|
|
|
219
217
|
parse(::JWT::Base64.url_decode(segment || ''))
|
|
220
218
|
end
|
|
221
219
|
|
|
222
|
-
def parse_unencoded(segment)
|
|
223
|
-
parse(segment)
|
|
224
|
-
end
|
|
225
|
-
|
|
226
220
|
def parse(segment)
|
|
227
221
|
JWT::JSON.parse(segment)
|
|
228
222
|
rescue ::JSON::ParserError
|
|
229
|
-
raise JWT::
|
|
223
|
+
raise JWT::MalformedTokenError, 'Invalid segment encoding'
|
|
230
224
|
end
|
|
231
225
|
|
|
232
226
|
def decoded_payload
|
data/lib/jwt/error.rb
CHANGED
|
@@ -1,54 +1,81 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module JWT
|
|
4
|
+
# The base error class for all JWT errors.
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
4
7
|
# The EncodeError class is raised when there is an error encoding a JWT.
|
|
5
|
-
class EncodeError <
|
|
8
|
+
class EncodeError < Error; end
|
|
6
9
|
|
|
7
|
-
# The
|
|
8
|
-
class DecodeError
|
|
10
|
+
# The historical grouping of every error that is not an encoding error. Every
|
|
11
|
+
# error class below is a descendant, so `rescue JWT::DecodeError` keeps its
|
|
12
|
+
# original meaning.
|
|
13
|
+
#
|
|
14
|
+
# @deprecated Use {JWT::Error}, {JWT::TokenError} or a more specific error class instead.
|
|
15
|
+
class DecodeError < Error; end
|
|
9
16
|
|
|
10
|
-
# The
|
|
11
|
-
class
|
|
17
|
+
# The TokenError class is the base class for all errors related to token processing.
|
|
18
|
+
class TokenError < DecodeError; end
|
|
12
19
|
|
|
13
|
-
# The
|
|
14
|
-
class
|
|
20
|
+
# The MalformedTokenError class is raised when the token is structurally invalid.
|
|
21
|
+
class MalformedTokenError < TokenError; end
|
|
15
22
|
|
|
16
|
-
# The
|
|
17
|
-
class
|
|
23
|
+
# The Base64DecodeError class is raised when there is an error decoding a Base64-encoded string.
|
|
24
|
+
class Base64DecodeError < MalformedTokenError; end
|
|
18
25
|
|
|
19
|
-
# The
|
|
20
|
-
class
|
|
26
|
+
# The SignatureError class is the base class for signature and algorithm related errors.
|
|
27
|
+
class SignatureError < TokenError; end
|
|
21
28
|
|
|
22
|
-
# The
|
|
23
|
-
|
|
29
|
+
# The VerificationError class is raised when the signature of a token does not
|
|
30
|
+
# match the one calculated from the signing input.
|
|
31
|
+
class VerificationError < SignatureError; end
|
|
32
|
+
|
|
33
|
+
# The VerificationKeyError class is raised when the key or algorithm given for
|
|
34
|
+
# verification cannot be used, as opposed to a signature that does not match.
|
|
35
|
+
class VerificationKeyError < VerificationError; end
|
|
36
|
+
|
|
37
|
+
# The IncorrectAlgorithm class is raised when the JWT algorithm is incorrect.
|
|
38
|
+
class IncorrectAlgorithm < SignatureError; end
|
|
24
39
|
|
|
25
40
|
# The UnsupportedEcdsaCurve class is raised when the ECDSA curve is unsupported.
|
|
26
41
|
class UnsupportedEcdsaCurve < IncorrectAlgorithm; end
|
|
27
42
|
|
|
43
|
+
# The ClaimValidationError class is the base class for all claim validation errors.
|
|
44
|
+
class ClaimValidationError < TokenError; end
|
|
45
|
+
|
|
46
|
+
# The ExpiredSignature class is raised when the JWT token has expired.
|
|
47
|
+
class ExpiredSignature < ClaimValidationError; end
|
|
48
|
+
|
|
49
|
+
# The ImmatureSignature class is raised when the JWT token is not yet valid (nbf).
|
|
50
|
+
class ImmatureSignature < ClaimValidationError; end
|
|
51
|
+
|
|
52
|
+
# The InvalidIssuerError class is raised when the JWT issuer is invalid.
|
|
53
|
+
class InvalidIssuerError < ClaimValidationError; end
|
|
54
|
+
|
|
28
55
|
# The InvalidIatError class is raised when the JWT issued at (iat) claim is invalid.
|
|
29
|
-
class InvalidIatError <
|
|
56
|
+
class InvalidIatError < ClaimValidationError; end
|
|
30
57
|
|
|
31
58
|
# The InvalidAudError class is raised when the JWT audience (aud) claim is invalid.
|
|
32
|
-
class InvalidAudError <
|
|
59
|
+
class InvalidAudError < ClaimValidationError; end
|
|
33
60
|
|
|
34
61
|
# The InvalidSubError class is raised when the JWT subject (sub) claim is invalid.
|
|
35
|
-
class InvalidSubError <
|
|
62
|
+
class InvalidSubError < ClaimValidationError; end
|
|
36
63
|
|
|
37
64
|
# The InvalidCritError class is raised when the JWT crit header is invalid.
|
|
38
|
-
class InvalidCritError <
|
|
65
|
+
class InvalidCritError < ClaimValidationError; end
|
|
39
66
|
|
|
40
67
|
# The InvalidJtiError class is raised when the JWT ID (jti) claim is invalid.
|
|
41
|
-
class InvalidJtiError <
|
|
68
|
+
class InvalidJtiError < ClaimValidationError; end
|
|
42
69
|
|
|
43
70
|
# The InvalidPayload class is raised when the JWT payload is invalid.
|
|
44
|
-
class InvalidPayload <
|
|
71
|
+
class InvalidPayload < ClaimValidationError; end
|
|
45
72
|
|
|
46
73
|
# The MissingRequiredClaim class is raised when a required claim is missing from the JWT.
|
|
47
|
-
class MissingRequiredClaim <
|
|
48
|
-
|
|
49
|
-
# The Base64DecodeError class is raised when there is an error decoding a Base64-encoded string.
|
|
50
|
-
class Base64DecodeError < DecodeError; end
|
|
74
|
+
class MissingRequiredClaim < ClaimValidationError; end
|
|
51
75
|
|
|
52
76
|
# The JWKError class is raised when there is an error with the JSON Web Key (JWK).
|
|
53
77
|
class JWKError < DecodeError; end
|
|
78
|
+
|
|
79
|
+
# Raised when a JWK uses a key type (kty) that this library does not support.
|
|
80
|
+
class UnsupportedKeyType < JWKError; end
|
|
54
81
|
end
|
data/lib/jwt/jwa/ecdsa.rb
CHANGED
|
@@ -15,10 +15,8 @@ module JWT
|
|
|
15
15
|
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::EC instance") unless signing_key.is_a?(::OpenSSL::PKey::EC)
|
|
16
16
|
raise_sign_error!('The given key is not a private key') unless signing_key.private?
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
key_algorithm
|
|
20
|
-
|
|
21
|
-
raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided" if alg != key_algorithm
|
|
18
|
+
key_algorithm = signing_key_algorithm(signing_key)
|
|
19
|
+
raise_sign_error!("payload algorithm is #{alg} but #{key_algorithm} signing key was provided") if alg != key_algorithm
|
|
22
20
|
|
|
23
21
|
asn1_to_raw(signing_key.dsa_sign_asn1(OpenSSL::Digest.new(digest).digest(data)), signing_key)
|
|
24
22
|
end
|
|
@@ -95,6 +93,13 @@ module JWT
|
|
|
95
93
|
self.class.curve_by_name(name)
|
|
96
94
|
end
|
|
97
95
|
|
|
96
|
+
# Signing-side counterpart of {.curve_by_name}. An unsupported curve on the
|
|
97
|
+
# signing key is an encoding problem, so it raises a JWT::EncodeError.
|
|
98
|
+
def signing_key_algorithm(signing_key)
|
|
99
|
+
curve_name = signing_key.group.curve_name
|
|
100
|
+
NAMED_CURVES.fetch(curve_name) { raise_sign_error!("The ECDSA curve '#{curve_name}' is not supported") }[:algorithm]
|
|
101
|
+
end
|
|
102
|
+
|
|
98
103
|
def raw_to_asn1(signature, private_key)
|
|
99
104
|
byte_size = (private_key.group.degree + 7) / 8
|
|
100
105
|
sig_bytes = signature[0..(byte_size - 1)]
|
data/lib/jwt/jwa/hmac.rb
CHANGED
|
@@ -6,24 +6,30 @@ module JWT
|
|
|
6
6
|
class Hmac
|
|
7
7
|
include JWT::JWA::SigningAlgorithm
|
|
8
8
|
|
|
9
|
+
# Minimum key lengths for HMAC algorithms based on RFC 7518 Section 3.2.
|
|
10
|
+
# Keys must be at least the size of the hash output to ensure sufficient
|
|
11
|
+
# entropy for the algorithm's security level.
|
|
12
|
+
MIN_KEY_LENGTHS = {
|
|
13
|
+
'HS256' => 32,
|
|
14
|
+
'HS384' => 48,
|
|
15
|
+
'HS512' => 64
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
9
18
|
def initialize(alg, digest)
|
|
10
19
|
@alg = alg
|
|
11
20
|
@digest = digest
|
|
12
21
|
end
|
|
13
22
|
|
|
14
23
|
def sign(data:, signing_key:)
|
|
15
|
-
signing_key
|
|
16
|
-
raise_verify_error!('HMAC key expected to be a String') unless signing_key.is_a?(String)
|
|
24
|
+
validate_key!(signing_key) { |message| raise_sign_error!(message) }
|
|
17
25
|
|
|
18
26
|
OpenSSL::HMAC.digest(digest.new, signing_key, data)
|
|
19
|
-
rescue OpenSSL::HMACError => e
|
|
20
|
-
raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret') if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure'
|
|
21
|
-
|
|
22
|
-
raise e
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
def verify(data:, signature:, verification_key:)
|
|
26
|
-
|
|
30
|
+
validate_key!(verification_key) { |message| raise_verify_error!(message) }
|
|
31
|
+
|
|
32
|
+
SecurityUtils.secure_compare(signature, OpenSSL::HMAC.digest(digest.new, verification_key, data))
|
|
27
33
|
end
|
|
28
34
|
|
|
29
35
|
register_algorithm(new('HS256', OpenSSL::Digest::SHA256))
|
|
@@ -34,8 +40,20 @@ module JWT
|
|
|
34
40
|
|
|
35
41
|
attr_reader :digest
|
|
36
42
|
|
|
43
|
+
# Yields a message for the first problem found with the key. The caller
|
|
44
|
+
# raises it, so signing and verification failures keep their own error class.
|
|
45
|
+
def validate_key!(key)
|
|
46
|
+
yield 'HMAC key expected to be a String' unless key.is_a?(String)
|
|
47
|
+
yield 'HMAC key cannot be empty' if key.empty?
|
|
48
|
+
|
|
49
|
+
return unless JWT.configuration.decode.enforce_hmac_key_length
|
|
50
|
+
|
|
51
|
+
min_length = MIN_KEY_LENGTHS[alg]
|
|
52
|
+
yield "HMAC key must be at least #{min_length} bytes for #{alg} algorithm" if key.bytesize < min_length
|
|
53
|
+
end
|
|
54
|
+
|
|
37
55
|
# Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb
|
|
38
|
-
# rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
|
|
56
|
+
# rubocop:disable-next Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
|
|
39
57
|
module SecurityUtils
|
|
40
58
|
# Constant time string comparison, for fixed length strings.
|
|
41
59
|
#
|
|
@@ -51,7 +69,7 @@ module JWT
|
|
|
51
69
|
def fixed_length_secure_compare(a, b)
|
|
52
70
|
raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
|
|
53
71
|
|
|
54
|
-
l = a.unpack
|
|
72
|
+
l = a.unpack("C#{a.bytesize}")
|
|
55
73
|
|
|
56
74
|
res = 0
|
|
57
75
|
b.each_byte { |byte| res |= byte ^ l.shift }
|
|
@@ -72,7 +90,6 @@ module JWT
|
|
|
72
90
|
end
|
|
73
91
|
module_function :secure_compare
|
|
74
92
|
end
|
|
75
|
-
# rubocop:enable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
|
|
76
93
|
end
|
|
77
94
|
end
|
|
78
95
|
end
|
data/lib/jwt/jwa/ps.rb
CHANGED
|
@@ -13,12 +13,15 @@ module JWT
|
|
|
13
13
|
|
|
14
14
|
def sign(data:, signing_key:)
|
|
15
15
|
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance.") unless signing_key.is_a?(::OpenSSL::PKey::RSA)
|
|
16
|
+
raise_sign_error!('The given key is not a private key') unless signing_key.private?
|
|
16
17
|
raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048
|
|
17
18
|
|
|
18
19
|
signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm)
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def verify(data:, signature:, verification_key:)
|
|
23
|
+
raise_verify_error!("The given key is a #{verification_key.class}. It has to be an OpenSSL::PKey::RSA instance") unless verification_key.is_a?(::OpenSSL::PKey::RSA)
|
|
24
|
+
|
|
22
25
|
verification_key.verify_pss(digest_algorithm, signature, data, salt_length: :auto, mgf1_hash: digest_algorithm)
|
|
23
26
|
rescue OpenSSL::PKey::PKeyError
|
|
24
27
|
raise JWT::VerificationError, 'Signature verification raised'
|
data/lib/jwt/jwa/rsa.rb
CHANGED
|
@@ -13,12 +13,15 @@ module JWT
|
|
|
13
13
|
|
|
14
14
|
def sign(data:, signing_key:)
|
|
15
15
|
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance") unless signing_key.is_a?(OpenSSL::PKey::RSA)
|
|
16
|
+
raise_sign_error!('The given key is not a private key') unless signing_key.private?
|
|
16
17
|
raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048
|
|
17
18
|
|
|
18
19
|
signing_key.sign(OpenSSL::Digest.new(digest), data)
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def verify(data:, signature:, verification_key:)
|
|
23
|
+
raise_verify_error!("The given key is a #{verification_key.class}. It has to be an OpenSSL::PKey::RSA instance") unless verification_key.is_a?(::OpenSSL::PKey::RSA)
|
|
24
|
+
|
|
22
25
|
verification_key.verify(OpenSSL::Digest.new(digest), signature, data)
|
|
23
26
|
rescue OpenSSL::PKey::PKeyError
|
|
24
27
|
raise JWT::VerificationError, 'Signature verification raised'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JWT
|
|
4
|
+
module JWA
|
|
5
|
+
# @api private
|
|
6
|
+
class SignerContext
|
|
7
|
+
attr_reader :jwa
|
|
8
|
+
|
|
9
|
+
def initialize(jwa:, key:)
|
|
10
|
+
@jwa = jwa
|
|
11
|
+
@key = key
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def sign(*args, **kwargs)
|
|
15
|
+
@jwa.sign(*args, **kwargs, signing_key: @key)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/jwt/jwa/unsupported.rb
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JWT
|
|
4
|
+
module JWA
|
|
5
|
+
# @api private
|
|
6
|
+
class VerifierContext
|
|
7
|
+
attr_reader :jwa
|
|
8
|
+
|
|
9
|
+
def initialize(jwa:, keys:)
|
|
10
|
+
@jwa = jwa
|
|
11
|
+
@keys = Array(keys)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def verify(*args, **kwargs)
|
|
15
|
+
@keys.any? do |key|
|
|
16
|
+
@jwa.verify(*args, **kwargs, verification_key: key)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/jwt/jwa.rb
CHANGED
|
@@ -9,40 +9,12 @@ require_relative 'jwa/none'
|
|
|
9
9
|
require_relative 'jwa/ps'
|
|
10
10
|
require_relative 'jwa/rsa'
|
|
11
11
|
require_relative 'jwa/unsupported'
|
|
12
|
+
require_relative 'jwa/verifier_context'
|
|
13
|
+
require_relative 'jwa/signer_context'
|
|
12
14
|
|
|
13
15
|
module JWT
|
|
14
16
|
# The JWA module contains all supported algorithms.
|
|
15
17
|
module JWA
|
|
16
|
-
# @api private
|
|
17
|
-
class VerifierContext
|
|
18
|
-
attr_reader :jwa
|
|
19
|
-
|
|
20
|
-
def initialize(jwa:, keys:)
|
|
21
|
-
@jwa = jwa
|
|
22
|
-
@keys = Array(keys)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def verify(*args, **kwargs)
|
|
26
|
-
@keys.any? do |key|
|
|
27
|
-
@jwa.verify(*args, **kwargs, verification_key: key)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# @api private
|
|
33
|
-
class SignerContext
|
|
34
|
-
attr_reader :jwa
|
|
35
|
-
|
|
36
|
-
def initialize(jwa:, key:)
|
|
37
|
-
@jwa = jwa
|
|
38
|
-
@key = key
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def sign(*args, **kwargs)
|
|
42
|
-
@jwa.sign(*args, **kwargs, signing_key: @key)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
18
|
class << self
|
|
47
19
|
# @api private
|
|
48
20
|
def resolve(algorithm)
|
|
@@ -65,7 +37,7 @@ module JWT
|
|
|
65
37
|
# @api private
|
|
66
38
|
def create_signer(algorithm:, key:)
|
|
67
39
|
if key.is_a?(JWK::KeyBase)
|
|
68
|
-
validate_jwk_algorithms!(key, algorithm,
|
|
40
|
+
validate_jwk_algorithms!(key, algorithm, EncodeError)
|
|
69
41
|
|
|
70
42
|
return key
|
|
71
43
|
end
|
|
@@ -77,7 +49,7 @@ module JWT
|
|
|
77
49
|
def create_verifiers(algorithms:, keys:, preferred_algorithm:)
|
|
78
50
|
jwks, other_keys = keys.partition { |key| key.is_a?(JWK::KeyBase) }
|
|
79
51
|
|
|
80
|
-
validate_jwk_algorithms!(jwks, algorithms,
|
|
52
|
+
validate_jwk_algorithms!(jwks, algorithms, VerificationKeyError)
|
|
81
53
|
|
|
82
54
|
jwks + resolve_and_sort(algorithms: algorithms,
|
|
83
55
|
preferred_algorithm: preferred_algorithm)
|
data/lib/jwt/jwk/ec.rb
CHANGED
|
@@ -54,7 +54,7 @@ module JWT
|
|
|
54
54
|
|
|
55
55
|
def export(options = {})
|
|
56
56
|
exported = parameters.clone
|
|
57
|
-
exported.reject! { |k, _| EC_PRIVATE_KEY_ELEMENTS.include?
|
|
57
|
+
exported.reject! { |k, _| EC_PRIVATE_KEY_ELEMENTS.include?(k) } unless private? && options[:include_private] == true
|
|
58
58
|
exported
|
|
59
59
|
end
|
|
60
60
|
|
data/lib/jwt/jwk/hmac.rb
CHANGED
|
@@ -47,7 +47,7 @@ module JWT
|
|
|
47
47
|
# See https://tools.ietf.org/html/rfc7517#appendix-A.3
|
|
48
48
|
def export(options = {})
|
|
49
49
|
exported = parameters.clone
|
|
50
|
-
exported.reject! { |k, _| HMAC_PRIVATE_KEY_ELEMENTS.include?
|
|
50
|
+
exported.reject! { |k, _| HMAC_PRIVATE_KEY_ELEMENTS.include?(k) } unless private? && options[:include_private] == true
|
|
51
51
|
exported
|
|
52
52
|
end
|
|
53
53
|
|
data/lib/jwt/jwk/key_finder.rb
CHANGED
|
@@ -28,12 +28,12 @@ module JWT
|
|
|
28
28
|
# Returns the verification key for the given kid
|
|
29
29
|
# @param [String] kid the key id
|
|
30
30
|
def key_for(kid, key_field = :kid)
|
|
31
|
-
raise ::JWT::
|
|
31
|
+
raise ::JWT::MalformedTokenError, "Invalid type for #{key_field} header parameter" unless kid.nil? || kid.is_a?(String)
|
|
32
32
|
|
|
33
33
|
jwk = resolve_key(kid, key_field)
|
|
34
34
|
|
|
35
|
-
raise ::JWT::
|
|
36
|
-
raise ::JWT::
|
|
35
|
+
raise ::JWT::SignatureError, 'No keys found in jwks' unless @jwks.any?
|
|
36
|
+
raise ::JWT::SignatureError, "Could not find public key for kid #{kid}" unless jwk
|
|
37
37
|
|
|
38
38
|
jwk.verify_key
|
|
39
39
|
end
|
|
@@ -47,7 +47,7 @@ module JWT
|
|
|
47
47
|
return key_for(field_value, key_field) if field_value
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
raise ::JWT::
|
|
50
|
+
raise ::JWT::SignatureError, 'No key id (kid) or x5t found from token headers' unless @allow_nil_kid
|
|
51
51
|
|
|
52
52
|
kid = token.header['kid']
|
|
53
53
|
key_for(kid)
|
data/lib/jwt/jwk/rsa.rb
CHANGED
|
@@ -50,7 +50,7 @@ module JWT
|
|
|
50
50
|
|
|
51
51
|
def export(options = {})
|
|
52
52
|
exported = parameters.clone
|
|
53
|
-
exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include?
|
|
53
|
+
exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include?(k) } unless private? && options[:include_private] == true
|
|
54
54
|
|
|
55
55
|
exported
|
|
56
56
|
end
|
|
@@ -195,7 +195,7 @@ module JWT
|
|
|
195
195
|
|
|
196
196
|
if ::JWT.openssl_3?
|
|
197
197
|
alias create_rsa_key create_rsa_key_using_der
|
|
198
|
-
elsif OpenSSL::PKey::RSA.
|
|
198
|
+
elsif OpenSSL::PKey::RSA.method_defined?(:set_key)
|
|
199
199
|
alias create_rsa_key create_rsa_key_using_sets
|
|
200
200
|
else
|
|
201
201
|
alias create_rsa_key create_rsa_key_using_accessors
|
data/lib/jwt/jwk/set.rb
CHANGED
|
@@ -12,24 +12,24 @@ module JWT
|
|
|
12
12
|
|
|
13
13
|
attr_reader :keys
|
|
14
14
|
|
|
15
|
-
def initialize(jwks = nil, options = {})
|
|
16
|
-
jwks ||= {}
|
|
17
|
-
|
|
15
|
+
def initialize(jwks = nil, options = {})
|
|
18
16
|
@keys = case jwks
|
|
19
|
-
when
|
|
20
|
-
|
|
21
|
-
when JWT::JWK::KeyBase
|
|
22
|
-
|
|
23
|
-
when
|
|
24
|
-
|
|
25
|
-
[*jwks[:keys]].map { |k| JWT::JWK.new(k, nil, options) }
|
|
26
|
-
when Array
|
|
27
|
-
jwks.map { |k| JWT::JWK.new(k, nil, options) }
|
|
28
|
-
else
|
|
29
|
-
raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK'
|
|
17
|
+
when nil then []
|
|
18
|
+
when JWT::JWK::Set then jwks.keys.dup
|
|
19
|
+
when JWT::JWK::KeyBase then [jwks]
|
|
20
|
+
when Hash then build_supported_keys(jwks.transform_keys(&:to_sym)[:keys], options)
|
|
21
|
+
when Array then build_keys(jwks, options)
|
|
22
|
+
else raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK'
|
|
30
23
|
end
|
|
31
24
|
end
|
|
32
25
|
|
|
26
|
+
# Ensures a duplicated set owns its key collection. The keys themselves are
|
|
27
|
+
# intentionally shared; only the collection is copied.
|
|
28
|
+
def initialize_copy(other)
|
|
29
|
+
super
|
|
30
|
+
@keys = @keys.dup
|
|
31
|
+
end
|
|
32
|
+
|
|
33
33
|
def export(options = {})
|
|
34
34
|
{ keys: @keys.map { |k| k.export(options) } }
|
|
35
35
|
end
|
|
@@ -77,6 +77,20 @@ module JWT
|
|
|
77
77
|
alias | union
|
|
78
78
|
alias + union
|
|
79
79
|
alias << add
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def build_keys(keys, options)
|
|
84
|
+
[*keys].map { |key| JWT::JWK.new(key, nil, options) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def build_supported_keys(keys, options)
|
|
88
|
+
[*keys].each_with_object([]) do |key, arr|
|
|
89
|
+
arr << JWT::JWK.new(key, nil, options)
|
|
90
|
+
rescue JWT::UnsupportedKeyType
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
end
|
|
80
94
|
end
|
|
81
95
|
end
|
|
82
96
|
end
|
data/lib/jwt/jwk.rb
CHANGED
|
@@ -13,7 +13,7 @@ module JWT
|
|
|
13
13
|
raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty
|
|
14
14
|
|
|
15
15
|
return mappings.fetch(jwk_kty.to_s) do |kty|
|
|
16
|
-
raise JWT::
|
|
16
|
+
raise JWT::UnsupportedKeyType, "Key type #{kty} not supported"
|
|
17
17
|
end.new(key, params, options)
|
|
18
18
|
end
|
|
19
19
|
|
data/lib/jwt/token.rb
CHANGED
|
@@ -81,6 +81,7 @@ module JWT
|
|
|
81
81
|
#
|
|
82
82
|
def detach_payload!
|
|
83
83
|
@detached_payload = true
|
|
84
|
+
@jwt = nil
|
|
84
85
|
|
|
85
86
|
nil
|
|
86
87
|
end
|
|
@@ -104,7 +105,7 @@ module JWT
|
|
|
104
105
|
|
|
105
106
|
# Verifies the claims of the token.
|
|
106
107
|
# @param options [Array<Symbol>, Hash] the claims to verify.
|
|
107
|
-
# @raise [JWT::
|
|
108
|
+
# @raise [JWT::ClaimValidationError] if the claims are invalid.
|
|
108
109
|
def verify_claims!(*options)
|
|
109
110
|
Claims::Verifier.verify!(self, *options)
|
|
110
111
|
end
|
data/lib/jwt/version.rb
CHANGED
|
@@ -15,8 +15,8 @@ module JWT
|
|
|
15
15
|
# Version constants
|
|
16
16
|
module VERSION
|
|
17
17
|
MAJOR = 3
|
|
18
|
-
MINOR =
|
|
19
|
-
TINY =
|
|
18
|
+
MINOR = 3
|
|
19
|
+
TINY = 0
|
|
20
20
|
PRE = nil
|
|
21
21
|
|
|
22
22
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
|
@@ -32,14 +32,6 @@ module JWT
|
|
|
32
32
|
true if 3 * 0x10000000 <= OpenSSL::OPENSSL_VERSION_NUMBER
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# Checks if there is an OpenSSL 3 HMAC empty key regression.
|
|
36
|
-
#
|
|
37
|
-
# @return [Boolean] true if there is an OpenSSL 3 HMAC empty key regression, false otherwise.
|
|
38
|
-
# @api private
|
|
39
|
-
def self.openssl_3_hmac_empty_key_regression?
|
|
40
|
-
openssl_3? && openssl_version <= ::Gem::Version.new('3.0.0')
|
|
41
|
-
end
|
|
42
|
-
|
|
43
35
|
# Returns the OpenSSL version.
|
|
44
36
|
#
|
|
45
37
|
# @return [Gem::Version] the OpenSSL version.
|
data/lib/jwt/x5c_key_finder.rb
CHANGED
|
@@ -40,7 +40,7 @@ module JWT
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def parse_certificates(x5c_header_or_certificates)
|
|
43
|
-
if x5c_header_or_certificates.all?
|
|
43
|
+
if x5c_header_or_certificates.all?(OpenSSL::X509::Certificate)
|
|
44
44
|
x5c_header_or_certificates
|
|
45
45
|
else
|
|
46
46
|
x5c_header_or_certificates.map do |encoded|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jwt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tim Rudat
|
|
@@ -171,6 +171,7 @@ files:
|
|
|
171
171
|
- lib/jwt/decode.rb
|
|
172
172
|
- lib/jwt/encode.rb
|
|
173
173
|
- lib/jwt/encoded_token.rb
|
|
174
|
+
- lib/jwt/encoded_token/claims_context.rb
|
|
174
175
|
- lib/jwt/error.rb
|
|
175
176
|
- lib/jwt/json.rb
|
|
176
177
|
- lib/jwt/jwa.rb
|
|
@@ -179,8 +180,10 @@ files:
|
|
|
179
180
|
- lib/jwt/jwa/none.rb
|
|
180
181
|
- lib/jwt/jwa/ps.rb
|
|
181
182
|
- lib/jwt/jwa/rsa.rb
|
|
183
|
+
- lib/jwt/jwa/signer_context.rb
|
|
182
184
|
- lib/jwt/jwa/signing_algorithm.rb
|
|
183
185
|
- lib/jwt/jwa/unsupported.rb
|
|
186
|
+
- lib/jwt/jwa/verifier_context.rb
|
|
184
187
|
- lib/jwt/jwk.rb
|
|
185
188
|
- lib/jwt/jwk/ec.rb
|
|
186
189
|
- lib/jwt/jwk/hmac.rb
|
|
@@ -199,7 +202,7 @@ licenses:
|
|
|
199
202
|
- MIT
|
|
200
203
|
metadata:
|
|
201
204
|
bug_tracker_uri: https://github.com/jwt/ruby-jwt/issues
|
|
202
|
-
changelog_uri: https://github.com/jwt/ruby-jwt/blob/v3.
|
|
205
|
+
changelog_uri: https://github.com/jwt/ruby-jwt/blob/v3.3.0/CHANGELOG.md
|
|
203
206
|
rubygems_mfa_required: 'true'
|
|
204
207
|
rdoc_options: []
|
|
205
208
|
require_paths:
|
|
@@ -215,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
215
218
|
- !ruby/object:Gem::Version
|
|
216
219
|
version: '0'
|
|
217
220
|
requirements: []
|
|
218
|
-
rubygems_version:
|
|
221
|
+
rubygems_version: 4.0.16
|
|
219
222
|
specification_version: 4
|
|
220
223
|
summary: JSON Web Token implementation in Ruby
|
|
221
224
|
test_files: []
|