jwt 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/.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 +28 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +99 -0
- data/Gemfile +3 -1
- data/README.md +68 -27
- data/Rakefile +2 -0
- data/lib/jwt/algos/ecdsa.rb +37 -8
- 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 +45 -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 +9 -5
- data/lib/jwt/jwk/hmac.rb +2 -2
- data/lib/jwt/jwk/key_base.rb +1 -0
- data/lib/jwt/jwk/rsa.rb +5 -4
- 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 +2 -2
- data/ruby-jwt.gemspec +6 -3
- metadata +25 -7
- data/.rubocop_todo.yml +0 -185
- data/lib/jwt/base64.rb +0 -19
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 is going to be 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,11 +127,12 @@ 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
138
|
ecdsa_key = OpenSSL::PKey::EC.new 'prime256v1'
|
|
@@ -152,7 +155,7 @@ decoded_token = JWT.decode token, ecdsa_public, true, { algorithm: 'ES256' }
|
|
|
152
155
|
puts decoded_token
|
|
153
156
|
```
|
|
154
157
|
|
|
155
|
-
**EDDSA**
|
|
158
|
+
### **EDDSA**
|
|
156
159
|
|
|
157
160
|
In order to use this algorithm you need to add the `RbNaCl` gem to you `Gemfile`.
|
|
158
161
|
|
|
@@ -181,7 +184,7 @@ decoded_token = JWT.decode token, public_key, true, { algorithm: 'ED25519' }
|
|
|
181
184
|
|
|
182
185
|
```
|
|
183
186
|
|
|
184
|
-
**RSASSA-PSS**
|
|
187
|
+
### **RSASSA-PSS**
|
|
185
188
|
|
|
186
189
|
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
190
|
|
|
@@ -370,6 +373,36 @@ rescue JWT::InvalidIssuerError
|
|
|
370
373
|
end
|
|
371
374
|
```
|
|
372
375
|
|
|
376
|
+
You can also pass a Regexp or Proc (with arity 1), verification will pass if the regexp matches or the proc returns truthy.
|
|
377
|
+
On supported ruby versions (>= 2.5) you can also delegate to methods, on older versions you will have
|
|
378
|
+
to convert them to proc (using `to_proc`)
|
|
379
|
+
|
|
380
|
+
```ruby
|
|
381
|
+
JWT.decode token, hmac_secret, true,
|
|
382
|
+
iss: %r'https://my.awesome.website/',
|
|
383
|
+
verify_iss: true,
|
|
384
|
+
algorithm: 'HS256'
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
```ruby
|
|
388
|
+
JWT.decode token, hmac_secret, true,
|
|
389
|
+
iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') },
|
|
390
|
+
verify_iss: true,
|
|
391
|
+
algorithm: 'HS256'
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
JWT.decode token, hmac_secret, true,
|
|
396
|
+
iss: method(:valid_issuer?),
|
|
397
|
+
verify_iss: true,
|
|
398
|
+
algorithm: 'HS256'
|
|
399
|
+
|
|
400
|
+
# somewhere in the same class:
|
|
401
|
+
def valid_issuer?(issuer)
|
|
402
|
+
# custom validation
|
|
403
|
+
end
|
|
404
|
+
```
|
|
405
|
+
|
|
373
406
|
### Audience Claim
|
|
374
407
|
|
|
375
408
|
From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3):
|
|
@@ -486,13 +519,34 @@ end
|
|
|
486
519
|
|
|
487
520
|
You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing
|
|
488
521
|
```ruby
|
|
489
|
-
# Will raise a JWT::
|
|
522
|
+
# Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
|
|
490
523
|
JWT.decode token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' }
|
|
491
524
|
```
|
|
492
525
|
|
|
526
|
+
### X.509 certificates in x5c header
|
|
527
|
+
|
|
528
|
+
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).
|
|
529
|
+
|
|
530
|
+
```ruby
|
|
531
|
+
root_certificates = [] # trusted `OpenSSL::X509::Certificate` objects
|
|
532
|
+
crl_uris = root_certificates.map(&:crl_uris)
|
|
533
|
+
crls = crl_uris.map do |uri|
|
|
534
|
+
# look up cached CRL by `uri` and return it if found, otherwise continue
|
|
535
|
+
crl = Net::HTTP.get(uri)
|
|
536
|
+
crl = OpenSSL::X509::CRL.new(crl)
|
|
537
|
+
# cache `crl` using `uri` as the key, expiry set to `crl.next_update` timestamp
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
begin
|
|
541
|
+
JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls })
|
|
542
|
+
rescue JWT::DecodeError
|
|
543
|
+
# Handle error, e.g. x5c header certificate revoked or expired
|
|
544
|
+
end
|
|
545
|
+
```
|
|
546
|
+
|
|
493
547
|
### JSON Web Key (JWK)
|
|
494
548
|
|
|
495
|
-
JWK is a JSON structure representing a cryptographic key. Currently only supports RSA
|
|
549
|
+
JWK is a JSON structure representing a cryptographic key. Currently only supports RSA, EC and HMAC keys.
|
|
496
550
|
|
|
497
551
|
```ruby
|
|
498
552
|
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), "optional-kid")
|
|
@@ -524,7 +578,7 @@ JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks})
|
|
|
524
578
|
|
|
525
579
|
### Importing and exporting JSON Web Keys
|
|
526
580
|
|
|
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
|
|
581
|
+
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
582
|
|
|
529
583
|
```ruby
|
|
530
584
|
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
|
|
@@ -533,27 +587,14 @@ jwk_hash = jwk.export
|
|
|
533
587
|
jwk_hash_with_private_key = jwk.export(include_private: true)
|
|
534
588
|
```
|
|
535
589
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec and performing releases to rubygems.org, which can be done with
|
|
539
|
-
|
|
540
|
-
```bash
|
|
541
|
-
rake release
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
|
|
545
|
-
|
|
546
|
-
```bash
|
|
547
|
-
bundle install
|
|
548
|
-
bundle exec appraisal rake test
|
|
549
|
-
```
|
|
590
|
+
## How to contribute
|
|
550
591
|
|
|
551
|
-
|
|
592
|
+
See [CONTRIBUTING](CONTRIBUTING.md).
|
|
552
593
|
|
|
553
594
|
## Contributors
|
|
554
595
|
|
|
555
|
-
See
|
|
596
|
+
See [AUTHORS](AUTHORS).
|
|
556
597
|
|
|
557
598
|
## License
|
|
558
599
|
|
|
559
|
-
See
|
|
600
|
+
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,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,18 @@ 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
|
+
rescue ArgumentError
|
|
122
|
+
raise(JWT::DecodeError, 'Invalid segment encoding')
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def algorithm
|
|
126
|
+
header['alg']
|
|
91
127
|
end
|
|
92
128
|
|
|
93
129
|
def header
|
|
@@ -103,8 +139,8 @@ module JWT
|
|
|
103
139
|
end
|
|
104
140
|
|
|
105
141
|
def parse_and_decode(segment)
|
|
106
|
-
JWT::JSON.parse(
|
|
107
|
-
rescue ::JSON::ParserError
|
|
142
|
+
JWT::JSON.parse(Base64.urlsafe_decode64(segment))
|
|
143
|
+
rescue ::JSON::ParserError, ArgumentError
|
|
108
144
|
raise JWT::DecodeError, 'Invalid segment encoding'
|
|
109
145
|
end
|
|
110
146
|
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
|
|
|
@@ -59,6 +59,9 @@ module JWT
|
|
|
59
59
|
when 'prime256v1'
|
|
60
60
|
crv = 'P-256'
|
|
61
61
|
x_octets, y_octets = encoded_point.unpack('xa32a32')
|
|
62
|
+
when 'secp256k1'
|
|
63
|
+
crv = 'P-256K'
|
|
64
|
+
x_octets, y_octets = encoded_point.unpack('xa32a32')
|
|
62
65
|
when 'secp384r1'
|
|
63
66
|
crv = 'P-384'
|
|
64
67
|
x_octets, y_octets = encoded_point.unpack('xa48a48')
|
|
@@ -72,11 +75,11 @@ module JWT
|
|
|
72
75
|
end
|
|
73
76
|
|
|
74
77
|
def encode_octets(octets)
|
|
75
|
-
|
|
78
|
+
Base64.urlsafe_encode64(octets, padding: false)
|
|
76
79
|
end
|
|
77
80
|
|
|
78
81
|
def encode_open_ssl_bn(key_part)
|
|
79
|
-
|
|
82
|
+
Base64.urlsafe_encode64(key_part.to_s(BINARY), padding: false)
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
class << self
|
|
@@ -98,6 +101,7 @@ module JWT
|
|
|
98
101
|
when 'P-256' then 'prime256v1'
|
|
99
102
|
when 'P-384' then 'secp384r1'
|
|
100
103
|
when 'P-521' then 'secp521r1'
|
|
104
|
+
when 'P-256K' then 'secp256k1'
|
|
101
105
|
else raise JWT::JWKError, 'Invalid curve provided'
|
|
102
106
|
end
|
|
103
107
|
end
|
|
@@ -138,11 +142,11 @@ module JWT
|
|
|
138
142
|
end
|
|
139
143
|
|
|
140
144
|
def decode_octets(jwk_data)
|
|
141
|
-
|
|
145
|
+
Base64.urlsafe_decode64(jwk_data)
|
|
142
146
|
end
|
|
143
147
|
|
|
144
148
|
def decode_open_ssl_bn(jwk_data)
|
|
145
|
-
OpenSSL::BN.new(
|
|
149
|
+
OpenSSL::BN.new(Base64.urlsafe_decode64(jwk_data), BINARY)
|
|
146
150
|
end
|
|
147
151
|
end
|
|
148
152
|
end
|
data/lib/jwt/jwk/hmac.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module JWT
|
|
4
4
|
module JWK
|
|
5
5
|
class HMAC < KeyBase
|
|
6
|
-
KTY = 'oct'
|
|
6
|
+
KTY = 'oct'
|
|
7
7
|
KTYS = [KTY, String].freeze
|
|
8
8
|
|
|
9
9
|
def initialize(keypair, kid = nil)
|
|
@@ -50,7 +50,7 @@ module JWT
|
|
|
50
50
|
|
|
51
51
|
raise JWT::JWKError, 'Key format is invalid for HMAC' unless jwk_k
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
new(jwk_k, jwk_kid)
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
end
|
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
|
|
@@ -63,7 +64,7 @@ module JWT
|
|
|
63
64
|
decode_open_ssl_bn(value)
|
|
64
65
|
end
|
|
65
66
|
kid = jwk_attributes(jwk_data, :kid)[:kid]
|
|
66
|
-
|
|
67
|
+
new(rsa_pkey(pkey_params), kid)
|
|
67
68
|
end
|
|
68
69
|
|
|
69
70
|
private
|
|
@@ -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