jwt 2.3.0 → 2.4.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +8 -0
- data/.github/workflows/coverage.yml +27 -0
- data/.github/workflows/test.yml +2 -10
- data/.gitignore +2 -0
- data/.rubocop.yml +12 -28
- data/.rubocop_todo.yml +9 -172
- data/AUTHORS +60 -53
- data/Appraisals +3 -0
- data/CHANGELOG.md +48 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +3 -1
- data/README.md +59 -8
- data/Rakefile +2 -0
- data/lib/jwt/algos/ecdsa.rb +23 -5
- data/lib/jwt/algos/eddsa.rb +3 -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/decode.rb +43 -9
- data/lib/jwt/default_options.rb +2 -0
- data/lib/jwt/encode.rb +6 -6
- data/lib/jwt/error.rb +1 -0
- data/lib/jwt/jwk/ec.rb +5 -5
- data/lib/jwt/jwk/hmac.rb +1 -1
- data/lib/jwt/jwk/key_base.rb +1 -0
- data/lib/jwt/jwk/rsa.rb +4 -3
- 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 +2 -3
- data/lib/jwt/x5c_key_finder.rb +55 -0
- data/lib/jwt.rb +1 -1
- data/ruby-jwt.gemspec +4 -2
- metadata +10 -7
- data/lib/jwt/base64.rb +0 -19
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
8
|
+
|
9
|
+
## Our Standards
|
10
|
+
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
12
|
+
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
18
|
+
|
19
|
+
Examples of unacceptable behavior include:
|
20
|
+
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
22
|
+
advances of any kind
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
* Public or private harassment
|
25
|
+
* Publishing others' private information, such as a physical or email
|
26
|
+
address, without their explicit permission
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
28
|
+
professional setting
|
29
|
+
|
30
|
+
## Enforcement Responsibilities
|
31
|
+
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
33
|
+
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
35
|
+
|
36
|
+
## Scope
|
37
|
+
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
39
|
+
|
40
|
+
## Enforcement
|
41
|
+
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at antmanj@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
|
43
|
+
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
|
+
|
46
|
+
## Enforcement Guidelines
|
47
|
+
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
49
|
+
|
50
|
+
### 1. Correction
|
51
|
+
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
53
|
+
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
55
|
+
|
56
|
+
### 2. Warning
|
57
|
+
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
59
|
+
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
61
|
+
|
62
|
+
### 3. Temporary Ban
|
63
|
+
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
65
|
+
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
67
|
+
|
68
|
+
### 4. Permanent Ban
|
69
|
+
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
|
+
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
|
+
|
74
|
+
## Attribution
|
75
|
+
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
78
|
+
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
80
|
+
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
82
|
+
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -42,7 +42,7 @@ The JWT spec supports NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms for cr
|
|
42
42
|
|
43
43
|
See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
|
44
44
|
|
45
|
-
**NONE**
|
45
|
+
### **NONE**
|
46
46
|
|
47
47
|
* none - unsigned token
|
48
48
|
|
@@ -68,7 +68,7 @@ decoded_token = JWT.decode token, nil, false
|
|
68
68
|
puts decoded_token
|
69
69
|
```
|
70
70
|
|
71
|
-
**HMAC**
|
71
|
+
### **HMAC**
|
72
72
|
|
73
73
|
* HS256 - HMAC using SHA-256 hash algorithm
|
74
74
|
* HS512256 - HMAC using SHA-512-256 hash algorithm (only available with RbNaCl; see note below)
|
@@ -100,7 +100,7 @@ Note: If [RbNaCl](https://github.com/cryptosphere/rbnacl) is loadable, ruby-jwt
|
|
100
100
|
[libsodium](https://github.com/jedisct1/libsodium), it can be installed
|
101
101
|
on MacOS with `brew install libsodium`.
|
102
102
|
|
103
|
-
**RSA**
|
103
|
+
### **RSA**
|
104
104
|
|
105
105
|
* RS256 - RSA using SHA-256 hash algorithm
|
106
106
|
* RS384 - RSA using SHA-384 hash algorithm
|
@@ -125,7 +125,7 @@ decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' }
|
|
125
125
|
puts decoded_token
|
126
126
|
```
|
127
127
|
|
128
|
-
**ECDSA**
|
128
|
+
### **ECDSA**
|
129
129
|
|
130
130
|
* ES256 - ECDSA using P-256 and SHA-256
|
131
131
|
* ES384 - ECDSA using P-384 and SHA-384
|
@@ -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,7 +181,7 @@ 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
186
|
In order to use this algorithm you need to add the `openssl` gem to you `Gemfile` with a version greater or equal to `2.1`.
|
187
187
|
|
@@ -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,13 +516,34 @@ 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
|
|
523
|
+
### X.509 certificates in x5c header
|
524
|
+
|
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).
|
526
|
+
|
527
|
+
```ruby
|
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
|
535
|
+
end
|
536
|
+
|
537
|
+
begin
|
538
|
+
JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls })
|
539
|
+
rescue JWT::DecodeError
|
540
|
+
# Handle error, e.g. x5c header certificate revoked or expired
|
541
|
+
end
|
542
|
+
```
|
543
|
+
|
493
544
|
### JSON Web Key (JWK)
|
494
545
|
|
495
|
-
JWK is a JSON structure representing a cryptographic key. Currently only supports RSA
|
546
|
+
JWK is a JSON structure representing a cryptographic key. Currently only supports RSA, EC and HMAC keys.
|
496
547
|
|
497
548
|
```ruby
|
498
549
|
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), "optional-kid")
|
data/Rakefile
CHANGED
data/lib/jwt/algos/ecdsa.rb
CHANGED
@@ -1,35 +1,53 @@
|
|
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
9
|
'prime256v1' => 'ES256',
|
10
|
+
'secp256r1' => 'ES256', # alias for prime256v1
|
9
11
|
'secp384r1' => 'ES384',
|
10
12
|
'secp521r1' => 'ES512'
|
11
13
|
}.freeze
|
12
14
|
|
15
|
+
SUPPORTED = NAMED_CURVES.values.uniq.freeze
|
16
|
+
|
13
17
|
def sign(to_sign)
|
14
18
|
algorithm, msg, key = to_sign.values
|
15
|
-
|
19
|
+
curve_definition = curve_by_name(key.group.curve_name)
|
20
|
+
key_algorithm = curve_definition[:algorithm]
|
16
21
|
if algorithm != key_algorithm
|
17
22
|
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
|
18
23
|
end
|
19
24
|
|
20
|
-
digest = OpenSSL::Digest.new(
|
25
|
+
digest = OpenSSL::Digest.new(curve_definition[:digest])
|
21
26
|
SecurityUtils.asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
|
22
27
|
end
|
23
28
|
|
24
29
|
def verify(to_verify)
|
25
30
|
algorithm, public_key, signing_input, signature = to_verify.values
|
26
|
-
|
31
|
+
curve_definition = curve_by_name(public_key.group.curve_name)
|
32
|
+
key_algorithm = curve_definition[:algorithm]
|
27
33
|
if algorithm != key_algorithm
|
28
34
|
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
|
29
35
|
end
|
30
|
-
|
36
|
+
|
37
|
+
digest = OpenSSL::Digest.new(curve_definition[:digest])
|
31
38
|
public_key.dsa_verify_asn1(digest.digest(signing_input), SecurityUtils.raw_to_asn1(signature, public_key))
|
32
39
|
end
|
40
|
+
|
41
|
+
def curve_by_name(name)
|
42
|
+
algorithm = NAMED_CURVES.fetch(name) do
|
43
|
+
raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
|
44
|
+
end
|
45
|
+
|
46
|
+
{
|
47
|
+
algorithm: algorithm,
|
48
|
+
digest: algorithm.sub('ES', 'sha')
|
49
|
+
}
|
50
|
+
end
|
33
51
|
end
|
34
52
|
end
|
35
53
|
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,6 +25,7 @@ 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)
|
27
30
|
end
|
28
31
|
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!
|
data/lib/jwt/decode.rb
CHANGED
@@ -4,12 +4,14 @@ require 'json'
|
|
4
4
|
|
5
5
|
require 'jwt/signature'
|
6
6
|
require 'jwt/verify'
|
7
|
+
require 'jwt/x5c_key_finder'
|
7
8
|
# JWT::Decode module
|
8
9
|
module JWT
|
9
10
|
# Decoding logic for JWT
|
10
11
|
class Decode
|
11
12
|
def initialize(jwt, key, verify, options, &keyfinder)
|
12
13
|
raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
|
14
|
+
|
13
15
|
@jwt = jwt
|
14
16
|
@key = key
|
15
17
|
@options = options
|
@@ -23,28 +25,50 @@ module JWT
|
|
23
25
|
validate_segment_count!
|
24
26
|
if @verify
|
25
27
|
decode_crypto
|
28
|
+
verify_algo
|
29
|
+
set_key
|
26
30
|
verify_signature
|
27
31
|
verify_claims
|
28
32
|
end
|
29
33
|
raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
|
34
|
+
|
30
35
|
[payload, header]
|
31
36
|
end
|
32
37
|
|
33
38
|
private
|
34
39
|
|
35
40
|
def verify_signature
|
41
|
+
return unless @key || @verify
|
42
|
+
|
43
|
+
return if none_algorithm?
|
44
|
+
|
45
|
+
raise JWT::DecodeError, 'No verification key available' unless @key
|
46
|
+
|
47
|
+
return if Array(@key).any? { |key| verify_signature_for?(key) }
|
48
|
+
|
49
|
+
raise(JWT::VerificationError, 'Signature verification failed')
|
50
|
+
end
|
51
|
+
|
52
|
+
def verify_algo
|
36
53
|
raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms.empty?
|
37
|
-
raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless
|
54
|
+
raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless algorithm
|
38
55
|
raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless options_includes_algo_in_header?
|
56
|
+
end
|
39
57
|
|
58
|
+
def set_key
|
40
59
|
@key = find_key(&@keyfinder) if @keyfinder
|
41
60
|
@key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks]).key_for(header['kid']) if @options[:jwks]
|
61
|
+
if (x5c_options = @options[:x5c])
|
62
|
+
@key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
|
63
|
+
end
|
64
|
+
end
|
42
65
|
|
43
|
-
|
66
|
+
def verify_signature_for?(key)
|
67
|
+
Signature.verify(algorithm, key, signing_input, @signature)
|
44
68
|
end
|
45
69
|
|
46
70
|
def options_includes_algo_in_header?
|
47
|
-
allowed_algorithms.any? { |alg| alg.casecmp(
|
71
|
+
allowed_algorithms.any? { |alg| alg.casecmp(algorithm).zero? }
|
48
72
|
end
|
49
73
|
|
50
74
|
def allowed_algorithms
|
@@ -65,8 +89,10 @@ module JWT
|
|
65
89
|
|
66
90
|
def find_key(&keyfinder)
|
67
91
|
key = (keyfinder.arity == 2 ? yield(header, payload) : yield(header))
|
68
|
-
|
69
|
-
key
|
92
|
+
# key can be of type [string, nil, OpenSSL::PKey, Array]
|
93
|
+
return key if key && !Array(key).empty?
|
94
|
+
|
95
|
+
raise JWT::DecodeError, 'No verification key available'
|
70
96
|
end
|
71
97
|
|
72
98
|
def verify_claims
|
@@ -77,7 +103,7 @@ module JWT
|
|
77
103
|
def validate_segment_count!
|
78
104
|
return if segment_length == 3
|
79
105
|
return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
|
80
|
-
return if segment_length == 2 &&
|
106
|
+
return if segment_length == 2 && none_algorithm?
|
81
107
|
|
82
108
|
raise(JWT::DecodeError, 'Not enough or too many segments')
|
83
109
|
end
|
@@ -86,8 +112,16 @@ module JWT
|
|
86
112
|
@segments.count
|
87
113
|
end
|
88
114
|
|
115
|
+
def none_algorithm?
|
116
|
+
algorithm.casecmp('none').zero?
|
117
|
+
end
|
118
|
+
|
89
119
|
def decode_crypto
|
90
|
-
@signature =
|
120
|
+
@signature = Base64.urlsafe_decode64(@segments[2] || '')
|
121
|
+
end
|
122
|
+
|
123
|
+
def algorithm
|
124
|
+
header['alg']
|
91
125
|
end
|
92
126
|
|
93
127
|
def header
|
@@ -103,8 +137,8 @@ module JWT
|
|
103
137
|
end
|
104
138
|
|
105
139
|
def parse_and_decode(segment)
|
106
|
-
JWT::JSON.parse(
|
107
|
-
rescue ::JSON::ParserError
|
140
|
+
JWT::JSON.parse(Base64.urlsafe_decode64(segment))
|
141
|
+
rescue ::JSON::ParserError, ArgumentError
|
108
142
|
raise JWT::DecodeError, 'Invalid segment encoding'
|
109
143
|
end
|
110
144
|
end
|
data/lib/jwt/default_options.rb
CHANGED
data/lib/jwt/encode.rb
CHANGED
@@ -7,14 +7,14 @@ require_relative './claims_validator'
|
|
7
7
|
module JWT
|
8
8
|
# Encoding logic for JWT
|
9
9
|
class Encode
|
10
|
-
ALG_NONE = 'none'
|
11
|
-
ALG_KEY = 'alg'
|
10
|
+
ALG_NONE = 'none'
|
11
|
+
ALG_KEY = 'alg'
|
12
12
|
|
13
13
|
def initialize(options)
|
14
14
|
@payload = options[:payload]
|
15
15
|
@key = options[:key]
|
16
16
|
_, @algorithm = Algos.find(options[:algorithm])
|
17
|
-
@headers = options[:headers].
|
17
|
+
@headers = options[:headers].transform_keys(&:to_s)
|
18
18
|
end
|
19
19
|
|
20
20
|
def segments
|
@@ -45,7 +45,7 @@ module JWT
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def encode_payload
|
48
|
-
if @payload
|
48
|
+
if @payload.is_a?(Hash)
|
49
49
|
ClaimsValidator.new(@payload).validate!
|
50
50
|
end
|
51
51
|
|
@@ -55,11 +55,11 @@ module JWT
|
|
55
55
|
def encode_signature
|
56
56
|
return '' if @algorithm == ALG_NONE
|
57
57
|
|
58
|
-
|
58
|
+
Base64.urlsafe_encode64(JWT::Signature.sign(@algorithm, encoded_header_and_payload, @key), padding: false)
|
59
59
|
end
|
60
60
|
|
61
61
|
def encode(data)
|
62
|
-
|
62
|
+
Base64.urlsafe_encode64(JWT::JSON.generate(data), padding: false)
|
63
63
|
end
|
64
64
|
|
65
65
|
def combine(*parts)
|
data/lib/jwt/error.rb
CHANGED
@@ -10,6 +10,7 @@ module JWT
|
|
10
10
|
class IncorrectAlgorithm < DecodeError; end
|
11
11
|
class ImmatureSignature < DecodeError; end
|
12
12
|
class InvalidIssuerError < DecodeError; end
|
13
|
+
class UnsupportedEcdsaCurve < IncorrectAlgorithm; end
|
13
14
|
class InvalidIatError < DecodeError; end
|
14
15
|
class InvalidAudError < DecodeError; end
|
15
16
|
class InvalidSubError < DecodeError; end
|
data/lib/jwt/jwk/ec.rb
CHANGED
@@ -8,7 +8,7 @@ module JWT
|
|
8
8
|
extend Forwardable
|
9
9
|
def_delegators :@keypair, :public_key
|
10
10
|
|
11
|
-
KTY = 'EC'
|
11
|
+
KTY = 'EC'
|
12
12
|
KTYS = [KTY, OpenSSL::PKey::EC].freeze
|
13
13
|
BINARY = 2
|
14
14
|
|
@@ -72,11 +72,11 @@ module JWT
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def encode_octets(octets)
|
75
|
-
|
75
|
+
Base64.urlsafe_encode64(octets, padding: false)
|
76
76
|
end
|
77
77
|
|
78
78
|
def encode_open_ssl_bn(key_part)
|
79
|
-
|
79
|
+
Base64.urlsafe_encode64(key_part.to_s(BINARY), padding: false)
|
80
80
|
end
|
81
81
|
|
82
82
|
class << self
|
@@ -138,11 +138,11 @@ module JWT
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def decode_octets(jwk_data)
|
141
|
-
|
141
|
+
Base64.urlsafe_decode64(jwk_data)
|
142
142
|
end
|
143
143
|
|
144
144
|
def decode_open_ssl_bn(jwk_data)
|
145
|
-
OpenSSL::BN.new(
|
145
|
+
OpenSSL::BN.new(Base64.urlsafe_decode64(jwk_data), BINARY)
|
146
146
|
end
|
147
147
|
end
|
148
148
|
end
|
data/lib/jwt/jwk/hmac.rb
CHANGED
data/lib/jwt/jwk/key_base.rb
CHANGED
data/lib/jwt/jwk/rsa.rb
CHANGED
@@ -4,12 +4,13 @@ module JWT
|
|
4
4
|
module JWK
|
5
5
|
class RSA < KeyBase
|
6
6
|
BINARY = 2
|
7
|
-
KTY = 'RSA'
|
7
|
+
KTY = 'RSA'
|
8
8
|
KTYS = [KTY, OpenSSL::PKey::RSA].freeze
|
9
9
|
RSA_KEY_ELEMENTS = %i[n e d p q dp dq qi].freeze
|
10
10
|
|
11
11
|
def initialize(keypair, kid = nil)
|
12
12
|
raise ArgumentError, 'keypair must be of type OpenSSL::PKey::RSA' unless keypair.is_a?(OpenSSL::PKey::RSA)
|
13
|
+
|
13
14
|
super(keypair, kid || generate_kid(keypair.public_key))
|
14
15
|
end
|
15
16
|
|
@@ -54,7 +55,7 @@ module JWT
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def encode_open_ssl_bn(key_part)
|
57
|
-
|
58
|
+
Base64.urlsafe_encode64(key_part.to_s(BINARY), padding: false)
|
58
59
|
end
|
59
60
|
|
60
61
|
class << self
|
@@ -107,7 +108,7 @@ module JWT
|
|
107
108
|
def decode_open_ssl_bn(jwk_data)
|
108
109
|
return nil unless jwk_data
|
109
110
|
|
110
|
-
OpenSSL::BN.new(
|
111
|
+
OpenSSL::BN.new(Base64.urlsafe_decode64(jwk_data), BINARY)
|
111
112
|
end
|
112
113
|
end
|
113
114
|
end
|
data/lib/jwt/jwk.rb
CHANGED