jwt 2.2.3 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
4
6
 
5
- gem 'rubocop', '~> 0.52.0' # Same as codeclimate default
7
+ gem 'rubocop', '~> 1.23.0' # Keep .codeclimate.yml channel in sync with this one
data/README.md CHANGED
@@ -38,11 +38,11 @@ And run `bundle install`
38
38
 
39
39
  ## Algorithms and Usage
40
40
 
41
- The JWT spec supports NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms for cryptographic signing. Currently the jwt gem supports NONE, HMAC, RSASSA and ECDSA. If you are using cryptographic signing, you need to specify the algorithm in the options hash whenever you call JWT.decode to ensure that an attacker [cannot bypass the algorithm verification step](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). **It is strongly recommended that you hard code the algorithm, as you may leave yourself vulnerable by dynamically picking the algorithm**
41
+ The JWT spec supports NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms for cryptographic signing. Currently the jwt gem supports NONE, HMAC, RSASSA and ECDSA. If you are using cryptographic signing, you need to specify the algorithm in the options hash whenever you call JWT.decode to ensure that an attacker [cannot bypass the algorithm verification step](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/). **It is strongly recommended that you hard code the algorithm, as you may leave yourself vulnerable by dynamically picking the algorithm**
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)
@@ -76,6 +76,7 @@ puts decoded_token
76
76
  * HS512 - HMAC using SHA-512 hash algorithm
77
77
 
78
78
  ```ruby
79
+ # The secret must be a string. A JWT::DecodeError will be raised if it isn't provided.
79
80
  hmac_secret = 'my$ecretK3y'
80
81
 
81
82
  token = JWT.encode payload, hmac_secret, 'HS256'
@@ -85,21 +86,6 @@ puts token
85
86
 
86
87
  decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' }
87
88
 
88
- # Array
89
- # [
90
- # {"data"=>"test"}, # payload
91
- # {"alg"=>"HS256"} # header
92
- # ]
93
- puts decoded_token
94
-
95
- # Without secret key
96
- token = JWT.encode payload, nil, 'HS256'
97
-
98
- # eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pVzcY2dX8JNM3LzIYeP2B1e1Wcpt1K3TWVvIYSF4x-o
99
- puts token
100
-
101
- decoded_token = JWT.decode token, nil, true, { algorithm: 'HS256' }
102
-
103
89
  # Array
104
90
  # [
105
91
  # {"data"=>"test"}, # payload
@@ -114,7 +100,7 @@ Note: If [RbNaCl](https://github.com/cryptosphere/rbnacl) is loadable, ruby-jwt
114
100
  [libsodium](https://github.com/jedisct1/libsodium), it can be installed
115
101
  on MacOS with `brew install libsodium`.
116
102
 
117
- **RSA**
103
+ ### **RSA**
118
104
 
119
105
  * RS256 - RSA using SHA-256 hash algorithm
120
106
  * RS384 - RSA using SHA-384 hash algorithm
@@ -139,7 +125,7 @@ decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' }
139
125
  puts decoded_token
140
126
  ```
141
127
 
142
- **ECDSA**
128
+ ### **ECDSA**
143
129
 
144
130
  * ES256 - ECDSA using P-256 and SHA-256
145
131
  * ES384 - ECDSA using P-384 and SHA-384
@@ -166,7 +152,7 @@ decoded_token = JWT.decode token, ecdsa_public, true, { algorithm: 'ES256' }
166
152
  puts decoded_token
167
153
  ```
168
154
 
169
- **EDDSA**
155
+ ### **EDDSA**
170
156
 
171
157
  In order to use this algorithm you need to add the `RbNaCl` gem to you `Gemfile`.
172
158
 
@@ -195,7 +181,7 @@ decoded_token = JWT.decode token, public_key, true, { algorithm: 'ED25519' }
195
181
 
196
182
  ```
197
183
 
198
- **RSASSA-PSS**
184
+ ### **RSASSA-PSS**
199
185
 
200
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`.
201
187
 
@@ -384,6 +370,36 @@ rescue JWT::InvalidIssuerError
384
370
  end
385
371
  ```
386
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
+
387
403
  ### Audience Claim
388
404
 
389
405
  From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3):
@@ -474,12 +490,63 @@ rescue JWT::InvalidSubError
474
490
  end
475
491
  ```
476
492
 
493
+ ### Finding a Key
494
+
495
+ To dynamically find the key for verifying the JWT signature, pass a block to the decode block. The block receives headers and the original payload as parameters. It should return with the key to verify the signature that was used to sign the JWT.
496
+
497
+ ```ruby
498
+ issuers = %w[My_Awesome_Company1 My_Awesome_Company2]
499
+ iss_payload = { data: 'data', iss: issuers.first }
500
+
501
+ secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' }
502
+
503
+ token = JWT.encode iss_payload, hmac_secret, 'HS256'
504
+
505
+ begin
506
+ # Add iss to the validation to check if the token has been manipulated
507
+ decoded_token = JWT.decode(token, nil, true, { iss: issuers, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload|
508
+ secrets[payload['iss']]
509
+ end
510
+ rescue JWT::InvalidIssuerError
511
+ # Handle invalid token, e.g. logout user or deny access
512
+ end
513
+ ```
514
+
515
+ ### Required Claims
516
+
517
+ You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing
518
+ ```ruby
519
+ # Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
520
+ JWT.decode token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' }
521
+ ```
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
+
477
544
  ### JSON Web Key (JWK)
478
545
 
479
- JWK is a JSON structure representing a cryptographic key. Currently only supports RSA public keys.
546
+ JWK is a JSON structure representing a cryptographic key. Currently only supports RSA, EC and HMAC keys.
480
547
 
481
548
  ```ruby
482
- jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
549
+ jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), "optional-kid")
483
550
  payload, headers = { data: 'data' }, { kid: jwk.kid }
484
551
 
485
552
  token = JWT.encode(payload, jwk.keypair, 'RS512', headers)
@@ -502,7 +569,7 @@ end
502
569
  or by passing JWK as a simple Hash
503
570
 
504
571
  ```
505
- jwks = { keys: [{ ... }] } # keys needs to be Symbol
572
+ jwks = { keys: [{ ... }] } # keys accepts both of string and symbol
506
573
  JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks})
507
574
  ```
508
575
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/setup'
2
4
  require 'bundler/gem_tasks'
3
5
 
@@ -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
- key_algorithm = NAMED_CURVES[key.group.curve_name]
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(algorithm.sub('ES', 'sha'))
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
- key_algorithm = NAMED_CURVES[public_key.group.curve_name]
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
- digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
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
@@ -1,21 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Eddsa
4
6
  module_function
5
7
 
6
- SUPPORTED = %w[ED25519].freeze
8
+ SUPPORTED = %w[ED25519 EdDSA].freeze
7
9
 
8
10
  def sign(to_sign)
9
11
  algorithm, msg, key = to_sign.values
10
- raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey" if key.class != RbNaCl::Signatures::Ed25519::SigningKey
11
- raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided" if algorithm.downcase.to_sym != key.primitive
12
+ if key.class != RbNaCl::Signatures::Ed25519::SigningKey
13
+ raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey"
14
+ end
15
+ unless SUPPORTED.map(&:downcase).map(&:to_sym).include?(algorithm.downcase.to_sym)
16
+ raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided"
17
+ end
18
+
12
19
  key.sign(msg)
13
20
  end
14
21
 
15
22
  def verify(to_verify)
16
23
  algorithm, public_key, signing_input, signature = to_verify.values
17
- raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{public_key.primitive} verification key was provided" if algorithm.downcase.to_sym != public_key.primitive
24
+ unless SUPPORTED.map(&:downcase).map(&:to_sym).include?(algorithm.downcase.to_sym)
25
+ raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided"
26
+ end
18
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
+
19
29
  public_key.verify(signature, signing_input)
20
30
  end
21
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Hmac
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module None
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
- major, minor = OpenSSL::VERSION.split('.').first(2)
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.class == String
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
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Unsupported
@@ -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.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
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,27 +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?
54
+ raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless algorithm
37
55
  raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless options_includes_algo_in_header?
56
+ end
38
57
 
58
+ def set_key
39
59
  @key = find_key(&@keyfinder) if @keyfinder
40
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
41
65
 
42
- Signature.verify(header['alg'], @key, signing_input, @signature)
66
+ def verify_signature_for?(key)
67
+ Signature.verify(algorithm, key, signing_input, @signature)
43
68
  end
44
69
 
45
70
  def options_includes_algo_in_header?
46
- allowed_algorithms.any? { |alg| alg.casecmp(header['alg']).zero? }
71
+ allowed_algorithms.any? { |alg| alg.casecmp(algorithm).zero? }
47
72
  end
48
73
 
49
74
  def allowed_algorithms
@@ -64,18 +89,21 @@ module JWT
64
89
 
65
90
  def find_key(&keyfinder)
66
91
  key = (keyfinder.arity == 2 ? yield(header, payload) : yield(header))
67
- raise JWT::DecodeError, 'No verification key available' unless key
68
- 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'
69
96
  end
70
97
 
71
98
  def verify_claims
72
99
  Verify.verify_claims(payload, @options)
100
+ Verify.verify_required_claims(payload, @options)
73
101
  end
74
102
 
75
103
  def validate_segment_count!
76
104
  return if segment_length == 3
77
105
  return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
78
- return if segment_length == 2 && header['alg'] == 'none'
106
+ return if segment_length == 2 && none_algorithm?
79
107
 
80
108
  raise(JWT::DecodeError, 'Not enough or too many segments')
81
109
  end
@@ -84,8 +112,16 @@ module JWT
84
112
  @segments.count
85
113
  end
86
114
 
115
+ def none_algorithm?
116
+ algorithm.casecmp('none').zero?
117
+ end
118
+
87
119
  def decode_crypto
88
- @signature = JWT::Base64.url_decode(@segments[2] || '')
120
+ @signature = Base64.urlsafe_decode64(@segments[2] || '')
121
+ end
122
+
123
+ def algorithm
124
+ header['alg']
89
125
  end
90
126
 
91
127
  def header
@@ -101,8 +137,8 @@ module JWT
101
137
  end
102
138
 
103
139
  def parse_and_decode(segment)
104
- JWT::JSON.parse(JWT::Base64.url_decode(segment))
105
- rescue ::JSON::ParserError
140
+ JWT::JSON.parse(Base64.urlsafe_decode64(segment))
141
+ rescue ::JSON::ParserError, ArgumentError
106
142
  raise JWT::DecodeError, 'Invalid segment encoding'
107
143
  end
108
144
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module DefaultOptions
3
5
  DEFAULT_OPTIONS = {
@@ -9,7 +11,8 @@ module JWT
9
11
  verify_aud: false,
10
12
  verify_sub: false,
11
13
  leeway: 0,
12
- algorithms: ['HS256']
14
+ algorithms: ['HS256'],
15
+ required_claims: []
13
16
  }.freeze
14
17
  end
15
18
  end
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'.freeze
11
- ALG_KEY = 'alg'.freeze
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].each_with_object({}) { |(key, value), headers| headers[key.to_s] = value }
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 && @payload.is_a?(Hash)
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
- JWT::Base64.url_encode(JWT::Signature.sign(@algorithm, encoded_header_and_payload, @key))
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
- JWT::Base64.url_encode(JWT::JSON.generate(data))
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,11 +10,13 @@ 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
16
17
  class InvalidJtiError < DecodeError; end
17
18
  class InvalidPayload < DecodeError; end
19
+ class MissingRequiredClaim < DecodeError; end
18
20
 
19
21
  class JWKError < DecodeError; end
20
22
  end