jwt 2.3.0 → 2.5.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/.codeclimate.yml +8 -0
- data/.github/workflows/coverage.yml +27 -0
- data/.github/workflows/test.yml +15 -22
- data/.gitignore +2 -0
- data/.reek.yml +22 -0
- data/.rubocop.yml +17 -47
- data/.sourcelevel.yml +3 -4
- data/AUTHORS +60 -53
- data/Appraisals +3 -0
- data/CHANGELOG.md +47 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +99 -0
- data/Gemfile +3 -1
- data/README.md +114 -34
- data/Rakefile +2 -0
- data/lib/jwt/algos/ecdsa.rb +37 -8
- data/lib/jwt/algos/eddsa.rb +5 -0
- data/lib/jwt/algos/hmac.rb +2 -0
- data/lib/jwt/algos/none.rb +2 -0
- data/lib/jwt/algos/ps.rb +3 -3
- data/lib/jwt/algos/rsa.rb +4 -1
- data/lib/jwt/algos/unsupported.rb +2 -0
- data/lib/jwt/claims_validator.rb +3 -1
- data/lib/jwt/configuration/container.rb +21 -0
- data/lib/jwt/configuration/decode_configuration.rb +46 -0
- data/lib/jwt/configuration/jwk_configuration.rb +27 -0
- data/lib/jwt/configuration.rb +15 -0
- data/lib/jwt/decode.rb +42 -8
- data/lib/jwt/encode.rb +6 -6
- data/lib/jwt/error.rb +1 -0
- data/lib/jwt/jwk/ec.rb +92 -43
- data/lib/jwt/jwk/hmac.rb +19 -10
- data/lib/jwt/jwk/key_base.rb +23 -6
- data/lib/jwt/jwk/key_finder.rb +1 -1
- data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
- data/lib/jwt/jwk/rsa.rb +54 -31
- data/lib/jwt/jwk/thumbprint.rb +26 -0
- data/lib/jwt/jwk.rb +1 -0
- data/lib/jwt/security_utils.rb +2 -0
- data/lib/jwt/signature.rb +3 -7
- data/lib/jwt/verify.rb +10 -2
- data/lib/jwt/version.rb +6 -2
- data/lib/jwt/x5c_key_finder.rb +55 -0
- data/lib/jwt.rb +5 -4
- data/ruby-jwt.gemspec +6 -3
- metadata +31 -7
- data/.rubocop_todo.yml +0 -185
- data/lib/jwt/default_options.rb +0 -16
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# Contributing to [ruby-jwt](https://github.com/jwt/ruby-jwt)
|
2
|
+
|
3
|
+
## Forking the project
|
4
|
+
|
5
|
+
Fork the project on GitHub and clone your own fork. Instuctions on forking can be found from the [GitHub Docs](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
|
6
|
+
|
7
|
+
```
|
8
|
+
git clone git@github.com:you/ruby-jwt.git
|
9
|
+
cd ruby-jwt
|
10
|
+
git remote add upstream https://github.com/jwt/ruby-jwt
|
11
|
+
```
|
12
|
+
|
13
|
+
## Create a branch for your implementation
|
14
|
+
|
15
|
+
Make sure you have the latest upstream master branch of the project.
|
16
|
+
|
17
|
+
```
|
18
|
+
git fetch --all
|
19
|
+
git checkout master
|
20
|
+
git rebase upstream/master
|
21
|
+
git push origin master
|
22
|
+
git checkout -b fix-a-little-problem
|
23
|
+
```
|
24
|
+
|
25
|
+
## Running the tests and linter
|
26
|
+
|
27
|
+
Before you start with your implementation make sure you are able to get a succesful test run with the current revision.
|
28
|
+
|
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
|
+
|
31
|
+
[Rubocop](https://github.com/rubocop/rubocop) is used to enforce the Ruby style.
|
32
|
+
|
33
|
+
To run the complete set of tests and linter run the following
|
34
|
+
|
35
|
+
```bash
|
36
|
+
bundle install
|
37
|
+
bundle exec appraisal rake test
|
38
|
+
bundle exec rubocop
|
39
|
+
```
|
40
|
+
|
41
|
+
## Implement your feature
|
42
|
+
|
43
|
+
Implement tests and your change. Don't be shy adding a little something in the [README](README.md).
|
44
|
+
Add a short description of the change in either the `Features` or `Fixes` section in the [CHANGELOG](CHANGELOG.md) file.
|
45
|
+
|
46
|
+
The form of the row (You need to return to the row when you know the pull request id)
|
47
|
+
```
|
48
|
+
- Fix a little problem [#123](https://github.com/jwt/ruby-jwt/pull/123) - [@you](https://github.com/you).
|
49
|
+
```
|
50
|
+
|
51
|
+
## Push your branch and create a pull request
|
52
|
+
|
53
|
+
Before pushing make sure the tests pass and RuboCop is happy.
|
54
|
+
|
55
|
+
```
|
56
|
+
bundle exec appraisal rake test
|
57
|
+
bundle exec rubocop
|
58
|
+
git push origin fix-a-little-problem
|
59
|
+
```
|
60
|
+
|
61
|
+
Make a new pull request on the [ruby-jwt project](https://github.com/jwt/ruby-jwt/pulls) with a description what the change is about.
|
62
|
+
|
63
|
+
## Update the CHANGELOG, again
|
64
|
+
|
65
|
+
Update the [CHANGELOG](CHANGELOG.md) with the pull request id from the previous step.
|
66
|
+
|
67
|
+
You can ammend the previous commit with the updated changelog change and force push your branch. The PR will get automatically updated.
|
68
|
+
|
69
|
+
```
|
70
|
+
git add CHANGELOG.md
|
71
|
+
git commit --amend --no-edit
|
72
|
+
git push origin fix-a-little-problem -f
|
73
|
+
```
|
74
|
+
|
75
|
+
## Keep an eye on your pull request
|
76
|
+
|
77
|
+
A maintainer will review and probably merge you changes when time allows, be patient.
|
78
|
+
|
79
|
+
## Keeping your branch up-to-date
|
80
|
+
|
81
|
+
It's recommended that you keep your branch up-to-date by rebasing to the upstream master.
|
82
|
+
|
83
|
+
```
|
84
|
+
git fetch upstream
|
85
|
+
git checkout fix-a-little-problem
|
86
|
+
git rebase upstream/master
|
87
|
+
git push origin fix-a-little-problem -f
|
88
|
+
```
|
89
|
+
|
90
|
+
# Releasing a new version
|
91
|
+
|
92
|
+
The version is using the [Semantic Versioning](http://semver.org/) and the version is located in the [version.rb](lib/jwt/version.rb) file.
|
93
|
+
Also update the [CHANGELOG](CHANGELOG.md) to reflect the upcoming version release.
|
94
|
+
|
95
|
+
```bash
|
96
|
+
rake release
|
97
|
+
```
|
98
|
+
|
99
|
+
**If you want a release cut with your PR, please include a version bump according to **
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -12,10 +12,12 @@ A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools
|
|
12
12
|
If you have further questions related to development or usage, join us: [ruby-jwt google group](https://groups.google.com/forum/#!forum/ruby-jwt).
|
13
13
|
|
14
14
|
## Announcements
|
15
|
-
|
15
|
+
* Ruby 2.4 support was dropped in version 2.4.0
|
16
16
|
* Ruby 1.9.3 support was dropped at December 31st, 2016.
|
17
17
|
* Version 1.5.3 yanked. See: [#132](https://github.com/jwt/ruby-jwt/issues/132) and [#133](https://github.com/jwt/ruby-jwt/issues/133)
|
18
18
|
|
19
|
+
See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes.
|
20
|
+
|
19
21
|
## Sponsors
|
20
22
|
|
21
23
|
|Logo|Message|
|
@@ -42,7 +44,7 @@ The JWT spec supports NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms for cr
|
|
42
44
|
|
43
45
|
See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
|
44
46
|
|
45
|
-
**NONE**
|
47
|
+
### **NONE**
|
46
48
|
|
47
49
|
* none - unsigned token
|
48
50
|
|
@@ -68,7 +70,7 @@ decoded_token = JWT.decode token, nil, false
|
|
68
70
|
puts decoded_token
|
69
71
|
```
|
70
72
|
|
71
|
-
**HMAC**
|
73
|
+
### **HMAC**
|
72
74
|
|
73
75
|
* HS256 - HMAC using SHA-256 hash algorithm
|
74
76
|
* HS512256 - HMAC using SHA-512-256 hash algorithm (only available with RbNaCl; see note below)
|
@@ -100,7 +102,7 @@ Note: If [RbNaCl](https://github.com/cryptosphere/rbnacl) is loadable, ruby-jwt
|
|
100
102
|
[libsodium](https://github.com/jedisct1/libsodium), it can be installed
|
101
103
|
on MacOS with `brew install libsodium`.
|
102
104
|
|
103
|
-
**RSA**
|
105
|
+
### **RSA**
|
104
106
|
|
105
107
|
* RS256 - RSA using SHA-256 hash algorithm
|
106
108
|
* RS384 - RSA using SHA-384 hash algorithm
|
@@ -125,24 +127,22 @@ decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' }
|
|
125
127
|
puts decoded_token
|
126
128
|
```
|
127
129
|
|
128
|
-
**ECDSA**
|
130
|
+
### **ECDSA**
|
129
131
|
|
130
132
|
* ES256 - ECDSA using P-256 and SHA-256
|
131
133
|
* ES384 - ECDSA using P-384 and SHA-384
|
132
134
|
* ES512 - ECDSA using P-521 and SHA-512
|
135
|
+
* ES256K - ECDSA using P-256K and SHA-256
|
133
136
|
|
134
137
|
```ruby
|
135
|
-
ecdsa_key = OpenSSL::PKey::EC.
|
136
|
-
ecdsa_key.generate_key
|
137
|
-
ecdsa_public = OpenSSL::PKey::EC.new ecdsa_key
|
138
|
-
ecdsa_public.private_key = nil
|
138
|
+
ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
|
139
139
|
|
140
140
|
token = JWT.encode payload, ecdsa_key, 'ES256'
|
141
141
|
|
142
142
|
# eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
|
143
143
|
puts token
|
144
144
|
|
145
|
-
decoded_token = JWT.decode token,
|
145
|
+
decoded_token = JWT.decode token, ecdsa_key, true, { algorithm: 'ES256' }
|
146
146
|
|
147
147
|
# Array
|
148
148
|
# [
|
@@ -152,7 +152,7 @@ decoded_token = JWT.decode token, ecdsa_public, true, { algorithm: 'ES256' }
|
|
152
152
|
puts decoded_token
|
153
153
|
```
|
154
154
|
|
155
|
-
**EDDSA**
|
155
|
+
### **EDDSA**
|
156
156
|
|
157
157
|
In order to use this algorithm you need to add the `RbNaCl` gem to you `Gemfile`.
|
158
158
|
|
@@ -181,9 +181,9 @@ decoded_token = JWT.decode token, public_key, true, { algorithm: 'ED25519' }
|
|
181
181
|
|
182
182
|
```
|
183
183
|
|
184
|
-
**RSASSA-PSS**
|
184
|
+
### **RSASSA-PSS**
|
185
185
|
|
186
|
-
In order to use this algorithm you need to add the `openssl` gem to
|
186
|
+
In order to use this algorithm you need to add the `openssl` gem to your `Gemfile` with a version greater or equal to `2.1`.
|
187
187
|
|
188
188
|
```ruby
|
189
189
|
gem 'openssl', '~> 2.1'
|
@@ -370,6 +370,36 @@ rescue JWT::InvalidIssuerError
|
|
370
370
|
end
|
371
371
|
```
|
372
372
|
|
373
|
+
You can also pass a Regexp or Proc (with arity 1), verification will pass if the regexp matches or the proc returns truthy.
|
374
|
+
On supported ruby versions (>= 2.5) you can also delegate to methods, on older versions you will have
|
375
|
+
to convert them to proc (using `to_proc`)
|
376
|
+
|
377
|
+
```ruby
|
378
|
+
JWT.decode token, hmac_secret, true,
|
379
|
+
iss: %r'https://my.awesome.website/',
|
380
|
+
verify_iss: true,
|
381
|
+
algorithm: 'HS256'
|
382
|
+
```
|
383
|
+
|
384
|
+
```ruby
|
385
|
+
JWT.decode token, hmac_secret, true,
|
386
|
+
iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') },
|
387
|
+
verify_iss: true,
|
388
|
+
algorithm: 'HS256'
|
389
|
+
```
|
390
|
+
|
391
|
+
```ruby
|
392
|
+
JWT.decode token, hmac_secret, true,
|
393
|
+
iss: method(:valid_issuer?),
|
394
|
+
verify_iss: true,
|
395
|
+
algorithm: 'HS256'
|
396
|
+
|
397
|
+
# somewhere in the same class:
|
398
|
+
def valid_issuer?(issuer)
|
399
|
+
# custom validation
|
400
|
+
end
|
401
|
+
```
|
402
|
+
|
373
403
|
### Audience Claim
|
374
404
|
|
375
405
|
From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3):
|
@@ -486,36 +516,68 @@ end
|
|
486
516
|
|
487
517
|
You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing
|
488
518
|
```ruby
|
489
|
-
# Will raise a JWT::
|
519
|
+
# Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
|
490
520
|
JWT.decode token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' }
|
491
521
|
```
|
492
522
|
|
493
|
-
###
|
523
|
+
### X.509 certificates in x5c header
|
494
524
|
|
495
|
-
|
525
|
+
A JWT signature can be verified using certificate(s) given in the `x5c` header. Before doing that, the trustworthiness of these certificate(s) must be established. This is done in accordance with RFC 5280 which (among other things) verifies the certificate(s) are issued by a trusted root certificate, the timestamps are valid, and none of the certificate(s) are revoked (i.e. being present in the root certificate's Certificate Revocation List).
|
496
526
|
|
497
527
|
```ruby
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
@cached_keys = nil if options[:invalidate] # need to reload the keys
|
506
|
-
@cached_keys ||= { keys: [jwk.export] }
|
528
|
+
root_certificates = [] # trusted `OpenSSL::X509::Certificate` objects
|
529
|
+
crl_uris = root_certificates.map(&:crl_uris)
|
530
|
+
crls = crl_uris.map do |uri|
|
531
|
+
# look up cached CRL by `uri` and return it if found, otherwise continue
|
532
|
+
crl = Net::HTTP.get(uri)
|
533
|
+
crl = OpenSSL::X509::CRL.new(crl)
|
534
|
+
# cache `crl` using `uri` as the key, expiry set to `crl.next_update` timestamp
|
507
535
|
end
|
508
536
|
|
509
537
|
begin
|
510
|
-
JWT.decode(token, nil, true, {
|
511
|
-
rescue JWT::JWKError
|
512
|
-
# Handle problems with the provided JWKs
|
538
|
+
JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls })
|
513
539
|
rescue JWT::DecodeError
|
514
|
-
# Handle
|
540
|
+
# Handle error, e.g. x5c header certificate revoked or expired
|
515
541
|
end
|
516
542
|
```
|
517
543
|
|
518
|
-
|
544
|
+
### JSON Web Key (JWK)
|
545
|
+
|
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.
|
547
|
+
|
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.
|
549
|
+
|
550
|
+
```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)
|
556
|
+
|
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
|
570
|
+
|
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
|
578
|
+
```
|
579
|
+
|
580
|
+
or by passing the JWKs as a simple Hash
|
519
581
|
|
520
582
|
```
|
521
583
|
jwks = { keys: [{ ... }] } # keys accepts both of string and symbol
|
@@ -524,7 +586,7 @@ JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks})
|
|
524
586
|
|
525
587
|
### Importing and exporting JSON Web Keys
|
526
588
|
|
527
|
-
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
|
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.
|
528
590
|
|
529
591
|
```ruby
|
530
592
|
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
|
@@ -533,6 +595,23 @@ jwk_hash = jwk.export
|
|
533
595
|
jwk_hash_with_private_key = jwk.export(include_private: true)
|
534
596
|
```
|
535
597
|
|
598
|
+
### Key ID (kid) and JWKs
|
599
|
+
|
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.
|
601
|
+
|
602
|
+
```ruby
|
603
|
+
JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint
|
604
|
+
# OR
|
605
|
+
JWT.configuration.jwk.kid_generator = ::JWT::JWK::Thumbprint
|
606
|
+
# OR
|
607
|
+
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), kid_generator: ::JWT::JWK::Thumbprint)
|
608
|
+
|
609
|
+
jwk_hash = jwk.export
|
610
|
+
|
611
|
+
thumbprint_as_the_kid = jwk_hash[:kid]
|
612
|
+
|
613
|
+
```
|
614
|
+
|
536
615
|
# Development and Tests
|
537
616
|
|
538
617
|
We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec and performing releases to rubygems.org, which can be done with
|
@@ -548,12 +627,13 @@ bundle install
|
|
548
627
|
bundle exec appraisal rake test
|
549
628
|
```
|
550
629
|
|
551
|
-
|
630
|
+
## How to contribute
|
631
|
+
See [CONTRIBUTING](CONTRIBUTING.md).
|
552
632
|
|
553
633
|
## Contributors
|
554
634
|
|
555
|
-
See
|
635
|
+
See [AUTHORS](AUTHORS).
|
556
636
|
|
557
637
|
## License
|
558
638
|
|
559
|
-
See
|
639
|
+
See [LICENSE](LICENSE).
|
data/Rakefile
CHANGED
data/lib/jwt/algos/ecdsa.rb
CHANGED
@@ -1,35 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JWT
|
2
4
|
module Algos
|
3
5
|
module Ecdsa
|
4
6
|
module_function
|
5
7
|
|
6
|
-
SUPPORTED = %w[ES256 ES384 ES512].freeze
|
7
8
|
NAMED_CURVES = {
|
8
|
-
'prime256v1' =>
|
9
|
-
|
10
|
-
|
9
|
+
'prime256v1' => {
|
10
|
+
algorithm: 'ES256',
|
11
|
+
digest: 'sha256'
|
12
|
+
},
|
13
|
+
'secp256r1' => { # alias for prime256v1
|
14
|
+
algorithm: 'ES256',
|
15
|
+
digest: 'sha256'
|
16
|
+
},
|
17
|
+
'secp384r1' => {
|
18
|
+
algorithm: 'ES384',
|
19
|
+
digest: 'sha384'
|
20
|
+
},
|
21
|
+
'secp521r1' => {
|
22
|
+
algorithm: 'ES512',
|
23
|
+
digest: 'sha512'
|
24
|
+
},
|
25
|
+
'secp256k1' => {
|
26
|
+
algorithm: 'ES256K',
|
27
|
+
digest: 'sha256'
|
28
|
+
}
|
11
29
|
}.freeze
|
12
30
|
|
31
|
+
SUPPORTED = NAMED_CURVES.map { |_, c| c[:algorithm] }.uniq.freeze
|
32
|
+
|
13
33
|
def sign(to_sign)
|
14
34
|
algorithm, msg, key = to_sign.values
|
15
|
-
|
35
|
+
curve_definition = curve_by_name(key.group.curve_name)
|
36
|
+
key_algorithm = curve_definition[:algorithm]
|
16
37
|
if algorithm != key_algorithm
|
17
38
|
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
|
18
39
|
end
|
19
40
|
|
20
|
-
digest = OpenSSL::Digest.new(
|
41
|
+
digest = OpenSSL::Digest.new(curve_definition[:digest])
|
21
42
|
SecurityUtils.asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
|
22
43
|
end
|
23
44
|
|
24
45
|
def verify(to_verify)
|
25
46
|
algorithm, public_key, signing_input, signature = to_verify.values
|
26
|
-
|
47
|
+
curve_definition = curve_by_name(public_key.group.curve_name)
|
48
|
+
key_algorithm = curve_definition[:algorithm]
|
27
49
|
if algorithm != key_algorithm
|
28
50
|
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
|
29
51
|
end
|
30
|
-
|
52
|
+
|
53
|
+
digest = OpenSSL::Digest.new(curve_definition[:digest])
|
31
54
|
public_key.dsa_verify_asn1(digest.digest(signing_input), SecurityUtils.raw_to_asn1(signature, public_key))
|
32
55
|
end
|
56
|
+
|
57
|
+
def curve_by_name(name)
|
58
|
+
NAMED_CURVES.fetch(name) do
|
59
|
+
raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
|
60
|
+
end
|
61
|
+
end
|
33
62
|
end
|
34
63
|
end
|
35
64
|
end
|
data/lib/jwt/algos/eddsa.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JWT
|
2
4
|
module Algos
|
3
5
|
module Eddsa
|
@@ -23,7 +25,10 @@ module JWT
|
|
23
25
|
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided"
|
24
26
|
end
|
25
27
|
raise DecodeError, "key given is a #{public_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey" if public_key.class != RbNaCl::Signatures::Ed25519::VerifyKey
|
28
|
+
|
26
29
|
public_key.verify(signature, signing_input)
|
30
|
+
rescue RbNaCl::CryptoError
|
31
|
+
false
|
27
32
|
end
|
28
33
|
end
|
29
34
|
end
|
data/lib/jwt/algos/hmac.rb
CHANGED
data/lib/jwt/algos/none.rb
CHANGED
data/lib/jwt/algos/ps.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JWT
|
2
4
|
module Algos
|
3
5
|
module Ps
|
@@ -29,9 +31,7 @@ module JWT
|
|
29
31
|
|
30
32
|
def require_openssl!
|
31
33
|
if Object.const_defined?('OpenSSL')
|
32
|
-
|
33
|
-
|
34
|
-
unless major.to_i >= 2 && minor.to_i >= 1
|
34
|
+
if ::Gem::Version.new(OpenSSL::VERSION) < ::Gem::Version.new('2.1')
|
35
35
|
raise JWT::RequiredDependencyError, "You currently have OpenSSL #{OpenSSL::VERSION}. PS support requires >= 2.1"
|
36
36
|
end
|
37
37
|
else
|
data/lib/jwt/algos/rsa.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JWT
|
2
4
|
module Algos
|
3
5
|
module Rsa
|
@@ -7,7 +9,8 @@ module JWT
|
|
7
9
|
|
8
10
|
def sign(to_sign)
|
9
11
|
algorithm, msg, key = to_sign.values
|
10
|
-
raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance." if key.
|
12
|
+
raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance." if key.instance_of?(String)
|
13
|
+
|
11
14
|
key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
|
12
15
|
end
|
13
16
|
|
data/lib/jwt/claims_validator.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative './error'
|
2
4
|
|
3
5
|
module JWT
|
@@ -9,7 +11,7 @@ module JWT
|
|
9
11
|
].freeze
|
10
12
|
|
11
13
|
def initialize(payload)
|
12
|
-
@payload = payload.
|
14
|
+
@payload = payload.transform_keys(&:to_sym)
|
13
15
|
end
|
14
16
|
|
15
17
|
def validate!
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'decode_configuration'
|
4
|
+
require_relative 'jwk_configuration'
|
5
|
+
|
6
|
+
module JWT
|
7
|
+
module Configuration
|
8
|
+
class Container
|
9
|
+
attr_accessor :decode, :jwk
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
reset!
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset!
|
16
|
+
@decode = DecodeConfiguration.new
|
17
|
+
@jwk = JwkConfiguration.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module Configuration
|
5
|
+
class DecodeConfiguration
|
6
|
+
attr_accessor :verify_expiration,
|
7
|
+
:verify_not_before,
|
8
|
+
:verify_iss,
|
9
|
+
:verify_iat,
|
10
|
+
:verify_jti,
|
11
|
+
:verify_aud,
|
12
|
+
:verify_sub,
|
13
|
+
:leeway,
|
14
|
+
:algorithms,
|
15
|
+
:required_claims
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@verify_expiration = true
|
19
|
+
@verify_not_before = true
|
20
|
+
@verify_iss = false
|
21
|
+
@verify_iat = false
|
22
|
+
@verify_jti = false
|
23
|
+
@verify_aud = false
|
24
|
+
@verify_sub = false
|
25
|
+
@leeway = 0
|
26
|
+
@algorithms = ['HS256']
|
27
|
+
@required_claims = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_h
|
31
|
+
{
|
32
|
+
verify_expiration: verify_expiration,
|
33
|
+
verify_not_before: verify_not_before,
|
34
|
+
verify_iss: verify_iss,
|
35
|
+
verify_iat: verify_iat,
|
36
|
+
verify_jti: verify_jti,
|
37
|
+
verify_aud: verify_aud,
|
38
|
+
verify_sub: verify_sub,
|
39
|
+
leeway: leeway,
|
40
|
+
algorithms: algorithms,
|
41
|
+
required_claims: required_claims
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../jwk/kid_as_key_digest'
|
4
|
+
require_relative '../jwk/thumbprint'
|
5
|
+
|
6
|
+
module JWT
|
7
|
+
module Configuration
|
8
|
+
class JwkConfiguration
|
9
|
+
def initialize
|
10
|
+
self.kid_generator_type = :key_digest
|
11
|
+
end
|
12
|
+
|
13
|
+
def kid_generator_type=(value)
|
14
|
+
self.kid_generator = case value
|
15
|
+
when :key_digest
|
16
|
+
JWT::JWK::KidAsKeyDigest
|
17
|
+
when :rfc7638_thumbprint
|
18
|
+
JWT::JWK::Thumbprint
|
19
|
+
else
|
20
|
+
raise ArgumentError, "#{value} is not a valid kid generator type."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_accessor :kid_generator
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'configuration/container'
|
4
|
+
|
5
|
+
module JWT
|
6
|
+
module Configuration
|
7
|
+
def configure
|
8
|
+
yield(configuration)
|
9
|
+
end
|
10
|
+
|
11
|
+
def configuration
|
12
|
+
@configuration ||= ::JWT::Configuration::Container.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|