jwt 2.5.0 → 2.8.1

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -23
  3. data/CONTRIBUTING.md +7 -7
  4. data/README.md +125 -47
  5. data/lib/jwt/base64.rb +16 -2
  6. data/lib/jwt/claims_validator.rb +1 -1
  7. data/lib/jwt/configuration/container.rb +14 -3
  8. data/lib/jwt/decode.rb +41 -24
  9. data/lib/jwt/deprecations.rb +29 -0
  10. data/lib/jwt/encode.rb +23 -19
  11. data/lib/jwt/error.rb +1 -0
  12. data/lib/jwt/{algos → jwa}/ecdsa.rb +19 -7
  13. data/lib/jwt/jwa/eddsa.rb +42 -0
  14. data/lib/jwt/jwa/hmac.rb +75 -0
  15. data/lib/jwt/jwa/hmac_rbnacl.rb +50 -0
  16. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +46 -0
  17. data/lib/jwt/{algos → jwa}/none.rb +4 -2
  18. data/lib/jwt/jwa/ps.rb +30 -0
  19. data/lib/jwt/jwa/rsa.rb +25 -0
  20. data/lib/jwt/{algos → jwa}/unsupported.rb +1 -1
  21. data/lib/jwt/jwa/wrapper.rb +26 -0
  22. data/lib/jwt/jwa.rb +62 -0
  23. data/lib/jwt/jwk/ec.rb +168 -116
  24. data/lib/jwt/jwk/hmac.rb +64 -28
  25. data/lib/jwt/jwk/key_base.rb +33 -11
  26. data/lib/jwt/jwk/key_finder.rb +19 -35
  27. data/lib/jwt/jwk/okp_rbnacl.rb +110 -0
  28. data/lib/jwt/jwk/rsa.rb +142 -77
  29. data/lib/jwt/jwk/set.rb +80 -0
  30. data/lib/jwt/jwk.rb +14 -11
  31. data/lib/jwt/verify.rb +8 -4
  32. data/lib/jwt/version.rb +20 -3
  33. data/lib/jwt/x5c_key_finder.rb +0 -3
  34. data/lib/jwt.rb +1 -0
  35. data/ruby-jwt.gemspec +11 -4
  36. metadata +35 -27
  37. data/.codeclimate.yml +0 -8
  38. data/.github/workflows/coverage.yml +0 -27
  39. data/.github/workflows/test.yml +0 -67
  40. data/.gitignore +0 -13
  41. data/.reek.yml +0 -22
  42. data/.rspec +0 -2
  43. data/.rubocop.yml +0 -67
  44. data/.sourcelevel.yml +0 -17
  45. data/Appraisals +0 -13
  46. data/Gemfile +0 -7
  47. data/Rakefile +0 -16
  48. data/lib/jwt/algos/eddsa.rb +0 -35
  49. data/lib/jwt/algos/hmac.rb +0 -36
  50. data/lib/jwt/algos/ps.rb +0 -43
  51. data/lib/jwt/algos/rsa.rb +0 -22
  52. data/lib/jwt/algos.rb +0 -44
  53. data/lib/jwt/security_utils.rb +0 -59
  54. data/lib/jwt/signature.rb +0 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3098671a837e7b291103cde1921277c61ecaa0f0797b955e6adc65328498f0d
4
- data.tar.gz: 3253833ac6d7743e40a5d5157b161cd0daecc9b77f61dfa7687d6b3da1be56ca
3
+ metadata.gz: af3792b982f014801d3ff7ae3410be6bd1e0b27f199c500942eb86267bb2b764
4
+ data.tar.gz: c37fc4b72cf3819210b15164548eff44579de1e8f8c48d9ce35864a84f007d9a
5
5
  SHA512:
6
- metadata.gz: 306c946b1199301a3f1000c8ffba4a77d07fd05dd83f769da86fd29f254827b5af8488a4b6a54b11f1f7f3a028cb88caafb7ed67528e7004c0337f6506e595ea
7
- data.tar.gz: 57d1eba7a06bc9d9f9fcb76b42aa3808415af5020c53969b4cada890b1646e7d348a96ce18010ab0a978e42825febbeb7b3f205b72e8ce60ef90132cf5887599
6
+ metadata.gz: 3f61fd13a1d56c657691abb4bfde3671a7d93b5c853785b804c0d119d27f4641685828eb9c65a8e4856487782530e791ecbce5f1a5f1cb61c883daff97a44367
7
+ data.tar.gz: ef36aa81991e28cb9d3df65f1ebbeb6599eb99dee8e1953aff1a6231e91c6a3f9633e3afd22fbbc6c09f6318c4e516ee7a908428eee303f3952fed890395b267
data/CHANGELOG.md CHANGED
@@ -1,29 +1,98 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.8.1](https://github.com/jwt/ruby-jwt/tree/v2.8.1) (2024-02-29)
3
4
 
4
- ## [v2.5.0](https://github.com/jwt/ruby-jwt/tree/v2.5.0) (NEXT)
5
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.0...v2.8.1)
5
6
 
6
- [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.4.1...master)
7
+ **Features:**
8
+
9
+ - Configurable base64 decode behaviour [#589](https://github.com/jwt/ruby-jwt/pull/589) ([@anakinj](https://github.com/anakinj))
10
+
11
+ **Fixes and enhancements:**
12
+
13
+ - Output deprecation warnings once [#589](https://github.com/jwt/ruby-jwt/pull/589) ([@anakinj](https://github.com/anakinj))
14
+
15
+ ## [v2.8.0](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2024-02-17)
16
+
17
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.7.1...v2.8.0)
18
+
19
+ **Features:**
20
+
21
+ - Updated rubocop to 1.56 [#573](https://github.com/jwt/ruby-jwt/pull/573) ([@anakinj](https://github.com/anakinj))
22
+ - Run CI on Ruby 3.3 [#577](https://github.com/jwt/ruby-jwt/pull/577) ([@anakinj](https://github.com/anakinj))
23
+ - Deprecation warning added for the HMAC algorithm HS512256 (HMAC-SHA-512 truncated to 256-bits) [#575](https://github.com/jwt/ruby-jwt/pull/575) ([@anakinj](https://github.com/anakinj))
24
+ - Stop using RbNaCl for standard HMAC algorithms [#575](https://github.com/jwt/ruby-jwt/pull/575) ([@anakinj](https://github.com/anakinj))
25
+
26
+ **Fixes and enhancements:**
27
+
28
+ - Fix signature has expired error if payload is a string [#555](https://github.com/jwt/ruby-jwt/pull/555) ([@GobinathAL](https://github.com/GobinathAL))
29
+ - Fix key base equality and spaceship operators [#569](https://github.com/jwt/ruby-jwt/pull/569) ([@magneland](https://github.com/magneland))
30
+ - Remove explicit base64 require from x5c_key_finder [#580](https://github.com/jwt/ruby-jwt/pull/580) ([@anakinj](https://github.com/anakinj))
31
+ - Performance improvements and cleanup of tests [#581](https://github.com/jwt/ruby-jwt/pull/581) ([@anakinj](https://github.com/anakinj))
32
+ - Repair EC x/y coordinates when importing JWK [#585](https://github.com/jwt/ruby-jwt/pull/585) ([@julik](https://github.com/julik))
33
+ - Explicit dependency to the base64 gem [#582](https://github.com/jwt/ruby-jwt/pull/582) ([@anakinj](https://github.com/anakinj))
34
+ - Deprecation warning for decoding content not compliant with RFC 4648 [#582](https://github.com/jwt/ruby-jwt/pull/582) ([@anakinj](https://github.com/anakinj))
35
+ - Algorithms moved under the `::JWT::JWA` module ([@anakinj](https://github.com/anakinj))
36
+
37
+ ## [v2.7.1](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2023-06-09)
38
+
39
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.7.0...v2.7.1)
40
+
41
+ **Fixes and enhancements:**
42
+
43
+ - Handle invalid algorithm when decoding JWT [#559](https://github.com/jwt/ruby-jwt/pull/559) ([@nataliastanko](https://github.com/nataliastanko))
44
+ - Do not raise error when verifying bad HMAC signature [#563](https://github.com/jwt/ruby-jwt/pull/563) ([@hieuk09](https://github.com/hieuk09))
45
+
46
+ ## [v2.7.0](https://github.com/jwt/ruby-jwt/tree/v2.7.0) (2023-02-01)
47
+
48
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.6.0...v2.7.0)
49
+
50
+ **Features:**
51
+
52
+ - Support OKP (Ed25519) keys for JWKs [#540](https://github.com/jwt/ruby-jwt/pull/540) ([@anakinj](https://github.com/anakinj))
53
+ - JWK Sets can now be used for tokens with nil kid [#543](https://github.com/jwt/ruby-jwt/pull/543) ([@bellebaum](https://github.com/bellebaum))
54
+
55
+ **Fixes and enhancements:**
56
+
57
+ - Fix issue with multiple keys returned by keyfinder and multiple allowed algorithms [#545](https://github.com/jwt/ruby-jwt/pull/545) ([@mpospelov](https://github.com/mpospelov))
58
+ - Non-string `kid` header values are now rejected [#543](https://github.com/jwt/ruby-jwt/pull/543) ([@bellebaum](https://github.com/bellebaum))
59
+
60
+ ## [v2.6.0](https://github.com/jwt/ruby-jwt/tree/v2.6.0) (2022-12-22)
61
+
62
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.5.0...v2.6.0)
63
+
64
+ **Features:**
65
+
66
+ - Support custom algorithms by passing algorithm objects [#512](https://github.com/jwt/ruby-jwt/pull/512) ([@anakinj](https://github.com/anakinj))
67
+ - Support descriptive (not key related) JWK parameters [#520](https://github.com/jwt/ruby-jwt/pull/520) ([@bellebaum](https://github.com/bellebaum))
68
+ - Support for JSON Web Key Sets [#525](https://github.com/jwt/ruby-jwt/pull/525) ([@bellebaum](https://github.com/bellebaum))
69
+ - Support HMAC keys over 32 chars when using RbNaCl [#521](https://github.com/jwt/ruby-jwt/pull/521) ([@anakinj](https://github.com/anakinj))
70
+
71
+ **Fixes and enhancements:**
72
+
73
+ - Raise descriptive error on empty hmac_secret and OpenSSL 3.0/openssl gem <3.0.1 [#530](https://github.com/jwt/ruby-jwt/pull/530) ([@jonmchan](https://github.com/jonmchan))
74
+
75
+ ## [v2.5.0](https://github.com/jwt/ruby-jwt/tree/v2.5.0) (2022-08-25)
76
+
77
+ [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.4.1...v2.5.0)
7
78
 
8
79
  **Features:**
9
80
 
10
- - Support JWK thumbprints as key ids [#481](https://github.com/jwt/ruby-jwt/pull/481) ([@anakinj](https://github.com/anakinj)).
11
- - Your contribution here
81
+ - Support JWK thumbprints as key ids [#481](https://github.com/jwt/ruby-jwt/pull/481) ([@anakinj](https://github.com/anakinj))
82
+ - Support OpenSSL >= 3.0 [#496](https://github.com/jwt/ruby-jwt/pull/496) ([@anakinj](https://github.com/anakinj))
12
83
 
13
84
  **Fixes and enhancements:**
14
- - Bring back the old Base64 (RFC2045) deocode mechanisms [#488](https://github.com/jwt/ruby-jwt/pull/488) ([@anakinj](https://github.com/anakinj)).
15
- - Rescue RbNaCl exception for EdDSA wrong key [#491](https://github.com/jwt/ruby-jwt/pull/491) ([@n-studio](https://github.com/n-studio)).
16
- - New parameter name for cases when kid is not found using JWK key loader proc [#501](https://github.com/jwt/ruby-jwt/pull/501) ([@anakinj](https://github.com/anakinj)).
17
- - Fix NoMethodError when a 2 segment token is missing 'alg' header [#502](https://github.com/jwt/ruby-jwt/pull/502) ([@cmrd-senya](https://github.com/cmrd-senya)).
18
- - Support OpenSSL >= 3.0 [#496](https://github.com/jwt/ruby-jwt/pull/496) ([@anakinj](https://github.com/anakinj)).
19
- - Your contribution here
85
+ - Bring back the old Base64 (RFC2045) deocode mechanisms [#488](https://github.com/jwt/ruby-jwt/pull/488) ([@anakinj](https://github.com/anakinj))
86
+ - Rescue RbNaCl exception for EdDSA wrong key [#491](https://github.com/jwt/ruby-jwt/pull/491) ([@n-studio](https://github.com/n-studio))
87
+ - New parameter name for cases when kid is not found using JWK key loader proc [#501](https://github.com/jwt/ruby-jwt/pull/501) ([@anakinj](https://github.com/anakinj))
88
+ - Fix NoMethodError when a 2 segment token is missing 'alg' header [#502](https://github.com/jwt/ruby-jwt/pull/502) ([@cmrd-senya](https://github.com/cmrd-senya))
20
89
 
21
90
  ## [v2.4.1](https://github.com/jwt/ruby-jwt/tree/v2.4.1) (2022-06-07)
22
91
 
23
92
  [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.4.0...v2.4.1)
24
93
 
25
94
  **Fixes and enhancements:**
26
- - Raise JWT::DecodeError on invalid signature [\#484](https://github.com/jwt/ruby-jwt/pull/484) ([@freakyfelt!](https://github.com/freakyfelt!)).
95
+ - Raise JWT::DecodeError on invalid signature [\#484](https://github.com/jwt/ruby-jwt/pull/484) ([@freakyfelt!](https://github.com/freakyfelt!))
27
96
 
28
97
  ## [v2.4.0](https://github.com/jwt/ruby-jwt/tree/v2.4.0) (2022-06-06)
29
98
 
@@ -31,20 +100,20 @@
31
100
 
32
101
  **Features:**
33
102
 
34
- - Dropped support for Ruby 2.5 and older [#453](https://github.com/jwt/ruby-jwt/pull/453) - [@anakinj](https://github.com/anakinj).
35
- - Use Ruby built-in url-safe base64 methods [#454](https://github.com/jwt/ruby-jwt/pull/454) - [@bdewater](https://github.com/bdewater).
36
- - Updated rubocop to 1.23.0 [#457](https://github.com/jwt/ruby-jwt/pull/457) - [@anakinj](https://github.com/anakinj).
37
- - Add x5c header key finder [#338](https://github.com/jwt/ruby-jwt/pull/338) - [@bdewater](https://github.com/bdewater).
38
- - Author driven changelog process [#463](https://github.com/jwt/ruby-jwt/pull/463) - [@anakinj](https://github.com/anakinj).
39
- - Allow regular expressions and procs to verify issuer [\#437](https://github.com/jwt/ruby-jwt/pull/437) ([rewritten](https://github.com/rewritten)).
40
- - Add Support to be able to verify from multiple keys [\#425](https://github.com/jwt/ruby-jwt/pull/425) ([ritikesh](https://github.com/ritikesh)).
103
+ - Dropped support for Ruby 2.5 and older [#453](https://github.com/jwt/ruby-jwt/pull/453) - ([@anakinj](https://github.com/anakinj))
104
+ - Use Ruby built-in url-safe base64 methods [#454](https://github.com/jwt/ruby-jwt/pull/454) - ([@bdewater](https://github.com/bdewater))
105
+ - Updated rubocop to 1.23.0 [#457](https://github.com/jwt/ruby-jwt/pull/457) - ([@anakinj](https://github.com/anakinj))
106
+ - Add x5c header key finder [#338](https://github.com/jwt/ruby-jwt/pull/338) - ([@bdewater](https://github.com/bdewater))
107
+ - Author driven changelog process [#463](https://github.com/jwt/ruby-jwt/pull/463) - ([@anakinj](https://github.com/anakinj))
108
+ - Allow regular expressions and procs to verify issuer [\#437](https://github.com/jwt/ruby-jwt/pull/437) ([rewritten](https://github.com/rewritten))
109
+ - Add Support to be able to verify from multiple keys [\#425](https://github.com/jwt/ruby-jwt/pull/425) ([ritikesh](https://github.com/ritikesh))
41
110
 
42
111
  **Fixes and enhancements:**
43
- - Readme: Typo fix re MissingRequiredClaim [\#451](https://github.com/jwt/ruby-jwt/pull/451) ([antonmorant](https://github.com/antonmorant)).
44
- - Fix RuboCop TODOs [\#476](https://github.com/jwt/ruby-jwt/pull/476) ([typhoon2099](https://github.com/typhoon2099)).
45
- - Make specific algorithms in README linkable [\#472](https://github.com/jwt/ruby-jwt/pull/472) ([milieu](https://github.com/milieu)).
46
- - Update note about supported JWK types [\#475](https://github.com/jwt/ruby-jwt/pull/475) ([dpashkevich](https://github.com/dpashkevich)).
47
- - Create CODE\_OF\_CONDUCT.md [\#449](https://github.com/jwt/ruby-jwt/pull/449) ([loic5](https://github.com/loic5)).
112
+ - Readme: Typo fix re MissingRequiredClaim [\#451](https://github.com/jwt/ruby-jwt/pull/451) ([antonmorant](https://github.com/antonmorant))
113
+ - Fix RuboCop TODOs [\#476](https://github.com/jwt/ruby-jwt/pull/476) ([typhoon2099](https://github.com/typhoon2099))
114
+ - Make specific algorithms in README linkable [\#472](https://github.com/jwt/ruby-jwt/pull/472) ([milieu](https://github.com/milieu))
115
+ - Update note about supported JWK types [\#475](https://github.com/jwt/ruby-jwt/pull/475) ([dpashkevich](https://github.com/dpashkevich))
116
+ - Create CODE\_OF\_CONDUCT.md [\#449](https://github.com/jwt/ruby-jwt/pull/449) ([loic5](https://github.com/loic5))
48
117
 
49
118
  ## [v2.3.0](https://github.com/jwt/ruby-jwt/tree/v2.3.0) (2021-10-03)
50
119
 
data/CONTRIBUTING.md CHANGED
@@ -12,19 +12,19 @@ git remote add upstream https://github.com/jwt/ruby-jwt
12
12
 
13
13
  ## Create a branch for your implementation
14
14
 
15
- Make sure you have the latest upstream master branch of the project.
15
+ Make sure you have the latest upstream main branch of the project.
16
16
 
17
17
  ```
18
18
  git fetch --all
19
- git checkout master
20
- git rebase upstream/master
21
- git push origin master
19
+ git checkout main
20
+ git rebase upstream/main
21
+ git push origin main
22
22
  git checkout -b fix-a-little-problem
23
23
  ```
24
24
 
25
25
  ## Running the tests and linter
26
26
 
27
- Before you start with your implementation make sure you are able to get a succesful test run with the current revision.
27
+ Before you start with your implementation make sure you are able to get a successful test run with the current revision.
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
 
@@ -78,12 +78,12 @@ A maintainer will review and probably merge you changes when time allows, be pat
78
78
 
79
79
  ## Keeping your branch up-to-date
80
80
 
81
- It's recommended that you keep your branch up-to-date by rebasing to the upstream master.
81
+ It's recommended that you keep your branch up-to-date by rebasing to the upstream main.
82
82
 
83
83
  ```
84
84
  git fetch upstream
85
85
  git checkout fix-a-little-problem
86
- git rebase upstream/master
86
+ git rebase upstream/main
87
87
  git push origin fix-a-little-problem -f
88
88
  ```
89
89
 
data/README.md CHANGED
@@ -1,11 +1,10 @@
1
1
  # JWT
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/jwt.svg)](https://badge.fury.io/rb/jwt)
4
- [![Build Status](https://github.com/jwt/ruby-jwt/workflows/test/badge.svg?branch=master)](https://github.com/jwt/ruby-jwt/actions)
4
+ [![Build Status](https://github.com/jwt/ruby-jwt/workflows/test/badge.svg?branch=main)](https://github.com/jwt/ruby-jwt/actions)
5
5
  [![Code Climate](https://codeclimate.com/github/jwt/ruby-jwt/badges/gpa.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
6
6
  [![Test Coverage](https://codeclimate.com/github/jwt/ruby-jwt/badges/coverage.svg)](https://codeclimate.com/github/jwt/ruby-jwt/coverage)
7
7
  [![Issue Count](https://codeclimate.com/github/jwt/ruby-jwt/badges/issue_count.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
8
- [![SourceLevel](https://app.sourcelevel.io/github/jwt/-/ruby-jwt.svg)](https://app.sourcelevel.io/github/jwt/-/ruby-jwt)
9
8
 
10
9
  A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard.
11
10
 
@@ -44,6 +43,23 @@ The JWT spec supports NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms for cr
44
43
 
45
44
  See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
46
45
 
46
+ ### Deprecation warnings
47
+
48
+ Deprecation warnings are logged once (`:once` option) by default to avoid spam in logs. Other options are `:silent` to completely silence warnings and `:warn` to log every time a deprecated path is executed.
49
+
50
+ ```ruby
51
+ JWT.configuration.deprecation_warnings = :warn # default is :once
52
+ ```
53
+
54
+ ### Base64 decoding
55
+
56
+ In the past the gem has been supporting the Base64 decoding specified in [RFC2045](https://www.rfc-editor.org/rfc/rfc2045) allowing newlines and blanks in the base64 encoded payload. In future versions base64 decoding will be stricter and only comply to [RFC4648](https://www.rfc-editor.org/rfc/rfc4648).
57
+
58
+ The stricter base64 decoding when processing tokens can be done via the `strict_base64_decoding` configuration accessor.
59
+ ```ruby
60
+ JWT.configuration.strict_base64_decoding = true # default is false
61
+ ```
62
+
47
63
  ### **NONE**
48
64
 
49
65
  * none - unsigned token
@@ -73,12 +89,11 @@ puts decoded_token
73
89
  ### **HMAC**
74
90
 
75
91
  * HS256 - HMAC using SHA-256 hash algorithm
76
- * HS512256 - HMAC using SHA-512-256 hash algorithm (only available with RbNaCl; see note below)
77
92
  * HS384 - HMAC using SHA-384 hash algorithm
78
93
  * HS512 - HMAC using SHA-512 hash algorithm
79
94
 
80
95
  ```ruby
81
- # The secret must be a string. A JWT::DecodeError will be raised if it isn't provided.
96
+ # The secret must be a string. With OpenSSL 3.0/openssl gem `<3.0.1`, JWT::DecodeError will be raised if it isn't provided.
82
97
  hmac_secret = 'my$ecretK3y'
83
98
 
84
99
  token = JWT.encode payload, hmac_secret, 'HS256'
@@ -96,12 +111,6 @@ decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
96
111
  puts decoded_token
97
112
  ```
98
113
 
99
- Note: If [RbNaCl](https://github.com/cryptosphere/rbnacl) is loadable, ruby-jwt will use it for HMAC-SHA256, HMAC-SHA512-256, and HMAC-SHA512. RbNaCl enforces a maximum key size of 32 bytes for these algorithms.
100
-
101
- [RbNaCl](https://github.com/cryptosphere/rbnacl) requires
102
- [libsodium](https://github.com/jedisct1/libsodium), it can be installed
103
- on MacOS with `brew install libsodium`.
104
-
105
114
  ### **RSA**
106
115
 
107
116
  * RS256 - RSA using SHA-256 hash algorithm
@@ -160,7 +169,7 @@ In order to use this algorithm you need to add the `RbNaCl` gem to you `Gemfile`
160
169
  gem 'rbnacl'
161
170
  ```
162
171
 
163
- For more detailed installation instruction check the official [repository](https://github.com/cryptosphere/rbnacl) on GitHub.
172
+ For more detailed installation instruction check the official [repository](https://github.com/RubyCrypto/rbnacl) on GitHub.
164
173
 
165
174
  * ED25519
166
175
 
@@ -212,6 +221,33 @@ decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'PS256' }
212
221
  puts decoded_token
213
222
  ```
214
223
 
224
+ ### **Custom algorithms**
225
+
226
+ An object implementing custom signing or verification behaviour can be passed in the `algorithm` option when encoding and decoding. The given object needs to implement the method `valid_alg?` and `verify` and/or `alg` and `sign`, depending if object is used for encoding or decoding.
227
+
228
+ ```ruby
229
+ module CustomHS512Algorithm
230
+ def self.alg
231
+ 'HS512'
232
+ end
233
+
234
+ def self.valid_alg?(alg_to_validate)
235
+ alg_to_validate == alg
236
+ end
237
+
238
+ def self.sign(data:, signing_key:)
239
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), data, signing_key)
240
+ end
241
+
242
+ def self.verify(data:, signature:, verification_key:)
243
+ ::OpenSSL.secure_compare(sign(data: data, signing_key: verification_key), signature)
244
+ end
245
+ end
246
+
247
+ token = ::JWT.encode({'pay' => 'load'}, 'secret', CustomHS512Algorithm)
248
+ payload, header = ::JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
249
+ ```
250
+
215
251
  ## Support for reserved claim names
216
252
  JSON Web Token defines some reserved claim names and defines how they should be
217
253
  used. JWT supports these reserved claim names:
@@ -535,7 +571,7 @@ crls = crl_uris.map do |uri|
535
571
  end
536
572
 
537
573
  begin
538
- JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls })
574
+ JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls } })
539
575
  rescue JWT::DecodeError
540
576
  # Handle error, e.g. x5c header certificate revoked or expired
541
577
  end
@@ -543,73 +579,115 @@ end
543
579
 
544
580
  ### JSON Web Key (JWK)
545
581
 
546
- JWK is a JSON structure representing a cryptographic key. Currently only supports RSA, EC and HMAC keys. The `jwks` option can be given as a lambda that evaluates every time a kid is resolved.
582
+ JWK is a JSON structure representing a cryptographic key. This gem currently supports RSA, EC, OKP and HMAC keys. OKP support requires [RbNaCl](https://github.com/RubyCrypto/rbnacl) and currently only supports the Ed25519 curve.
547
583
 
548
- If the kid is not found from the given set the loader will be called a second time with the `kid_not_found` option set to `true`. The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
584
+ To encode a JWT using your JWK:
549
585
 
550
586
  ```ruby
551
- jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), 'optional-kid')
552
- payload = { data: 'data' }
553
- headers = { kid: jwk.kid }
554
-
555
- token = JWT.encode(payload, jwk.keypair, 'RS512', headers)
587
+ optional_parameters = { kid: 'my-kid', use: 'sig', alg: 'RS512' }
588
+ jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters)
556
589
 
557
- # The jwk loader would fetch the set of JWKs from a trusted source,
558
- # to avoid malicious requests triggering cache invalidations there needs to be some kind of grace time or other logic for determining the validity of the invalidation.
559
- # This example only allows cache invalidations every 5 minutes.
560
- jwk_loader = ->(options) do
561
- if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300
562
- logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache")
563
- @cached_keys = nil
564
- end
565
- @cached_keys ||= begin
566
- @cache_last_update = Time.now.to_i
567
- { keys: [jwk.export] }
568
- end
569
- end
590
+ # Encoding
591
+ payload = { data: 'data' }
592
+ token = JWT.encode(payload, jwk.signing_key, jwk[:alg], kid: jwk[:kid])
570
593
 
571
- begin
572
- JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwk_loader })
573
- rescue JWT::JWKError
574
- # Handle problems with the provided JWKs
575
- rescue JWT::DecodeError
576
- # Handle other decode related issues e.g. no kid in header, no matching public key found etc.
577
- end
594
+ # JSON Web Key Set for advertising your signing keys
595
+ jwks_hash = JWT::JWK::Set.new(jwk).export
578
596
  ```
579
597
 
580
- or by passing the JWKs as a simple Hash
598
+ To decode a JWT using a trusted entity's JSON Web Key Set (JWKS):
581
599
 
600
+ ```ruby
601
+ jwks = JWT::JWK::Set.new(jwks_hash)
602
+ jwks.filter! {|key| key[:use] == 'sig' } # Signing keys only!
603
+ algorithms = jwks.map { |key| key[:alg] }.compact.uniq
604
+ JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
582
605
  ```
583
- jwks = { keys: [{ ... }] } # keys accepts both of string and symbol
584
- JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks})
606
+
607
+
608
+ The `jwks` option can also be given as a lambda that evaluates every time a kid is resolved.
609
+ This can be used to implement caching of remotely fetched JWK Sets.
610
+
611
+ If the requested `kid` is not found from the given set the loader will be called a second time with the `kid_not_found` option set to `true`.
612
+ The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases.
613
+
614
+ Tokens without a specified `kid` are rejected by default.
615
+ This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`.
616
+
617
+ ```ruby
618
+ jwks_loader = ->(options) do
619
+ # The jwk loader would fetch the set of JWKs from a trusted source.
620
+ # To avoid malicious requests triggering cache invalidations there needs to be
621
+ # some kind of grace time or other logic for determining the validity of the invalidation.
622
+ # This example only allows cache invalidations every 5 minutes.
623
+ if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300
624
+ logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache")
625
+ @cached_keys = nil
626
+ end
627
+ @cached_keys ||= begin
628
+ @cache_last_update = Time.now.to_i
629
+ # Replace with your own JWKS fetching routine
630
+ jwks = JWT::JWK::Set.new(jwks_hash)
631
+ jwks.select! { |key| key[:use] == 'sig' } # Signing Keys only
632
+ jwks
633
+ end
634
+ end
635
+
636
+ begin
637
+ JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks_loader })
638
+ rescue JWT::JWKError
639
+ # Handle problems with the provided JWKs
640
+ rescue JWT::DecodeError
641
+ # Handle other decode related issues e.g. no kid in header, no matching public key found etc.
642
+ end
585
643
  ```
586
644
 
587
645
  ### Importing and exporting JSON Web Keys
588
646
 
589
- The ::JWT::JWK class can be used to import and export both the public key (default behaviour) and the private key. To include the private key in the export pass the `include_private` parameter to the export method.
647
+ The ::JWT::JWK class can be used to import both JSON Web Keys and OpenSSL keys
648
+ and export to either format with and without the private key included.
649
+
650
+ To include the private key in the export pass the `include_private` parameter to the export method.
590
651
 
591
652
  ```ruby
592
- jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
653
+ # Import a JWK Hash (showing an HMAC example)
654
+ jwk = JWT::JWK.new({ kty: 'oct', k: 'my-secret', kid: 'my-kid' })
655
+
656
+ # Import an OpenSSL key
657
+ # You can optionally add descriptive parameters to the JWK
658
+ desc_params = { kid: 'my-kid', use: 'sig' }
659
+ jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), desc_params)
593
660
 
661
+ # Export as JWK Hash (public key only by default)
594
662
  jwk_hash = jwk.export
595
663
  jwk_hash_with_private_key = jwk.export(include_private: true)
664
+
665
+ # Export as OpenSSL key
666
+ public_key = jwk.verify_key
667
+ private_key = jwk.signing_key if jwk.private?
668
+
669
+ # You can also import and export entire JSON Web Key Sets
670
+ jwks_hash = { keys: [{ kty: 'oct', k: 'my-secret', kid: 'my-kid' }] }
671
+ jwks = JWT::JWK::Set.new(jwks_hash)
672
+ jwks_hash = jwks.export
596
673
  ```
597
674
 
598
675
  ### Key ID (kid) and JWKs
599
676
 
600
- The key id (kid) generation in the gem is a custom algorithm and not based on any standards. To use a standardized JWK thumbprint (RFC 7638) as the kid for JWKs a generator type can be specified in the global configuration or can be given to the JWK instance on initialization.
677
+ The key id (kid) generation in the gem is a custom algorithm and not based on any standards.
678
+ To use a standardized JWK thumbprint (RFC 7638) as the kid for JWKs a generator type can be specified in the global configuration
679
+ or can be given to the JWK instance on initialization.
601
680
 
602
681
  ```ruby
603
682
  JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint
604
683
  # OR
605
684
  JWT.configuration.jwk.kid_generator = ::JWT::JWK::Thumbprint
606
685
  # OR
607
- jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), kid_generator: ::JWT::JWK::Thumbprint)
686
+ jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), nil, kid_generator: ::JWT::JWK::Thumbprint)
608
687
 
609
688
  jwk_hash = jwk.export
610
689
 
611
690
  thumbprint_as_the_kid = jwk_hash[:kid]
612
-
613
691
  ```
614
692
 
615
693
  # Development and Tests
data/lib/jwt/base64.rb CHANGED
@@ -3,14 +3,28 @@
3
3
  require 'base64'
4
4
 
5
5
  module JWT
6
- # Base64 helpers
6
+ # Base64 encoding and decoding
7
7
  class Base64
8
8
  class << self
9
+ # Encode a string with URL-safe Base64 complying with RFC 4648 (not padded).
9
10
  def url_encode(str)
10
- ::Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
11
+ ::Base64.urlsafe_encode64(str, padding: false)
11
12
  end
12
13
 
14
+ # Decode a string with URL-safe Base64 complying with RFC 4648.
15
+ # Deprecated support for RFC 2045 remains for now. ("All line breaks or other characters not found in Table 1 must be ignored by decoding software")
13
16
  def url_decode(str)
17
+ ::Base64.urlsafe_decode64(str)
18
+ rescue ArgumentError => e
19
+ raise unless e.message == 'invalid base64'
20
+ raise Base64DecodeError, 'Invalid base64 encoding' if JWT.configuration.strict_base64_decoding
21
+
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')
24
+ end
25
+ end
26
+
27
+ def loose_urlsafe_decode64(str)
14
28
  str += '=' * (4 - str.length.modulo(4))
15
29
  ::Base64.decode64(str.tr('-_', '+/'))
16
30
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './error'
3
+ require_relative 'error'
4
4
 
5
5
  module JWT
6
6
  class ClaimsValidator
@@ -6,15 +6,26 @@ require_relative 'jwk_configuration'
6
6
  module JWT
7
7
  module Configuration
8
8
  class Container
9
- attr_accessor :decode, :jwk
9
+ attr_accessor :decode, :jwk, :strict_base64_decoding
10
+ attr_reader :deprecation_warnings
10
11
 
11
12
  def initialize
12
13
  reset!
13
14
  end
14
15
 
15
16
  def reset!
16
- @decode = DecodeConfiguration.new
17
- @jwk = JwkConfiguration.new
17
+ @decode = DecodeConfiguration.new
18
+ @jwk = JwkConfiguration.new
19
+ @strict_base64_decoding = false
20
+
21
+ self.deprecation_warnings = :once
22
+ end
23
+
24
+ DEPRECATION_WARNINGS_VALUES = %i[once warn silent].freeze
25
+ def deprecation_warnings=(value)
26
+ raise ArgumentError, "Invalid deprecation_warnings value #{value}. Supported values: #{DEPRECATION_WARNINGS_VALUES}" unless DEPRECATION_WARNINGS_VALUES.include?(value)
27
+
28
+ @deprecation_warnings = value
18
29
  end
19
30
  end
20
31
  end
data/lib/jwt/decode.rb CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  require 'json'
4
4
 
5
- require 'jwt/signature'
6
5
  require 'jwt/verify'
7
6
  require 'jwt/x5c_key_finder'
7
+
8
8
  # JWT::Decode module
9
9
  module JWT
10
10
  # Decoding logic for JWT
@@ -24,7 +24,7 @@ module JWT
24
24
  def decode_segments
25
25
  validate_segment_count!
26
26
  if @verify
27
- decode_crypto
27
+ decode_signature
28
28
  verify_algo
29
29
  set_key
30
30
  verify_signature
@@ -51,40 +51,57 @@ module JWT
51
51
 
52
52
  def verify_algo
53
53
  raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms.empty?
54
- raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless algorithm
55
- raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless options_includes_algo_in_header?
54
+ raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless alg_in_header
55
+ raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') if allowed_and_valid_algorithms.empty?
56
56
  end
57
57
 
58
58
  def set_key
59
59
  @key = find_key(&@keyfinder) if @keyfinder
60
- @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks]).key_for(header['kid']) if @options[:jwks]
60
+ @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(header['kid']) if @options[:jwks]
61
61
  if (x5c_options = @options[:x5c])
62
62
  @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
63
63
  end
64
64
  end
65
65
 
66
66
  def verify_signature_for?(key)
67
- Signature.verify(algorithm, key, signing_input, @signature)
67
+ allowed_and_valid_algorithms.any? do |alg|
68
+ alg.verify(data: signing_input, signature: @signature, verification_key: key)
69
+ end
68
70
  end
69
71
 
70
- def options_includes_algo_in_header?
71
- allowed_algorithms.any? { |alg| alg.casecmp(algorithm).zero? }
72
+ def allowed_and_valid_algorithms
73
+ @allowed_and_valid_algorithms ||= allowed_algorithms.select { |alg| alg.valid_alg?(alg_in_header) }
72
74
  end
73
75
 
74
- def allowed_algorithms
75
- # Order is very important - first check for string keys, next for symbols
76
- algos = if @options.key?('algorithm')
77
- @options['algorithm']
78
- elsif @options.key?(:algorithm)
79
- @options[:algorithm]
80
- elsif @options.key?('algorithms')
81
- @options['algorithms']
82
- elsif @options.key?(:algorithms)
83
- @options[:algorithms]
84
- else
85
- []
76
+ # Order is very important - first check for string keys, next for symbols
77
+ ALGORITHM_KEYS = ['algorithm',
78
+ :algorithm,
79
+ 'algorithms',
80
+ :algorithms].freeze
81
+
82
+ def given_algorithms
83
+ ALGORITHM_KEYS.each do |alg_key|
84
+ alg = @options[alg_key]
85
+ return Array(alg) if alg
86
86
  end
87
- Array(algos)
87
+ []
88
+ end
89
+
90
+ def allowed_algorithms
91
+ @allowed_algorithms ||= resolve_allowed_algorithms
92
+ end
93
+
94
+ def resolve_allowed_algorithms
95
+ algs = given_algorithms.map { |alg| JWA.create(alg) }
96
+
97
+ sort_by_alg_header(algs)
98
+ end
99
+
100
+ # Move algorithms matching the JWT alg header to the beginning of the list
101
+ def sort_by_alg_header(algs)
102
+ return algs if algs.size <= 1
103
+
104
+ algs.partition { |alg| alg.valid_alg?(alg_in_header) }.flatten
88
105
  end
89
106
 
90
107
  def find_key(&keyfinder)
@@ -113,14 +130,14 @@ module JWT
113
130
  end
114
131
 
115
132
  def none_algorithm?
116
- algorithm == 'none'
133
+ alg_in_header == 'none'
117
134
  end
118
135
 
119
- def decode_crypto
136
+ def decode_signature
120
137
  @signature = ::JWT::Base64.url_decode(@segments[2] || '')
121
138
  end
122
139
 
123
- def algorithm
140
+ def alg_in_header
124
141
  header['alg']
125
142
  end
126
143