jwt 2.5.0 → 2.8.1

Sign up to get free protection for your applications and to get access to all the features.
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