jwt 2.4.1 → 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/.github/workflows/test.yml +14 -13
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +21 -2
- data/README.md +66 -27
- data/lib/jwt/algos/eddsa.rb +2 -0
- data/lib/jwt/base64.rb +19 -0
- 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 +4 -6
- data/lib/jwt/encode.rb +2 -2
- data/lib/jwt/jwk/ec.rb +91 -46
- data/lib/jwt/jwk/hmac.rb +19 -10
- data/lib/jwt/jwk/key_base.rb +23 -7
- 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 -32
- data/lib/jwt/jwk/thumbprint.rb +26 -0
- data/lib/jwt/version.rb +7 -2
- data/lib/jwt/x5c_key_finder.rb +1 -1
- data/lib/jwt.rb +5 -4
- metadata +11 -5
- data/lib/jwt/default_options.rb +0 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a3098671a837e7b291103cde1921277c61ecaa0f0797b955e6adc65328498f0d
|
|
4
|
+
data.tar.gz: 3253833ac6d7743e40a5d5157b161cd0daecc9b77f61dfa7687d6b3da1be56ca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 306c946b1199301a3f1000c8ffba4a77d07fd05dd83f769da86fd29f254827b5af8488a4b6a54b11f1f7f3a028cb88caafb7ed67528e7004c0337f6506e595ea
|
|
7
|
+
data.tar.gz: 57d1eba7a06bc9d9f9fcb76b42aa3808415af5020c53969b4cada890b1646e7d348a96ce18010ab0a978e42825febbeb7b3f205b72e8ce60ef90132cf5887599
|
data/.github/workflows/test.yml
CHANGED
|
@@ -13,7 +13,7 @@ jobs:
|
|
|
13
13
|
timeout-minutes: 30
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
16
|
+
- uses: actions/checkout@v3
|
|
17
17
|
- name: Set up Ruby
|
|
18
18
|
uses: ruby/setup-ruby@v1
|
|
19
19
|
with:
|
|
@@ -22,34 +22,35 @@ jobs:
|
|
|
22
22
|
- name: Run RuboCop
|
|
23
23
|
run: bundle exec rubocop
|
|
24
24
|
test:
|
|
25
|
+
name: ${{ matrix.os }} - Ruby ${{ matrix.ruby }}
|
|
26
|
+
runs-on: ${{ matrix.os }}
|
|
25
27
|
strategy:
|
|
26
28
|
fail-fast: false
|
|
27
29
|
matrix:
|
|
30
|
+
os:
|
|
31
|
+
- ubuntu-20.04
|
|
28
32
|
ruby:
|
|
29
|
-
- 2.5
|
|
30
|
-
- 2.6
|
|
31
|
-
- 2.7
|
|
33
|
+
- "2.5"
|
|
34
|
+
- "2.6"
|
|
35
|
+
- "2.7"
|
|
32
36
|
- "3.0"
|
|
33
|
-
- 3.1
|
|
37
|
+
- "3.1"
|
|
34
38
|
gemfile:
|
|
35
39
|
- gemfiles/standalone.gemfile
|
|
36
40
|
- gemfiles/openssl.gemfile
|
|
37
41
|
- gemfiles/rbnacl.gemfile
|
|
38
42
|
experimental: [false]
|
|
39
43
|
include:
|
|
40
|
-
- ruby: 2.7
|
|
41
|
-
|
|
42
|
-
- ruby: "
|
|
43
|
-
|
|
44
|
-
- ruby: "truffleruby-head"
|
|
45
|
-
experimental: true
|
|
46
|
-
runs-on: ubuntu-20.04
|
|
44
|
+
- { os: ubuntu-20.04, ruby: "2.7", gemfile: 'gemfiles/rbnacl.gemfile', experimental: false }
|
|
45
|
+
- { os: ubuntu-22.04, ruby: "3.1", experimental: false }
|
|
46
|
+
- { os: ubuntu-20.04, ruby: "truffleruby-head", experimental: true }
|
|
47
|
+
- { os: ubuntu-22.04, ruby: "head", experimental: true }
|
|
47
48
|
continue-on-error: ${{ matrix.experimental }}
|
|
48
49
|
env:
|
|
49
50
|
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
|
|
50
51
|
|
|
51
52
|
steps:
|
|
52
|
-
- uses: actions/checkout@
|
|
53
|
+
- uses: actions/checkout@v3
|
|
53
54
|
|
|
54
55
|
- name: Install libsodium
|
|
55
56
|
run: |
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## [v2.5.0](https://github.com/jwt/ruby-jwt/tree/v2.5.0) (NEXT)
|
|
5
|
+
|
|
6
|
+
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.4.1...master)
|
|
7
|
+
|
|
8
|
+
**Features:**
|
|
9
|
+
|
|
10
|
+
- Support JWK thumbprints as key ids [#481](https://github.com/jwt/ruby-jwt/pull/481) ([@anakinj](https://github.com/anakinj)).
|
|
11
|
+
- Your contribution here
|
|
3
12
|
|
|
4
13
|
**Fixes and enhancements:**
|
|
5
|
-
-
|
|
14
|
+
- Bring back the old Base64 (RFC2045) deocode mechanisms [#488](https://github.com/jwt/ruby-jwt/pull/488) ([@anakinj](https://github.com/anakinj)).
|
|
15
|
+
- Rescue RbNaCl exception for EdDSA wrong key [#491](https://github.com/jwt/ruby-jwt/pull/491) ([@n-studio](https://github.com/n-studio)).
|
|
16
|
+
- New parameter name for cases when kid is not found using JWK key loader proc [#501](https://github.com/jwt/ruby-jwt/pull/501) ([@anakinj](https://github.com/anakinj)).
|
|
17
|
+
- Fix NoMethodError when a 2 segment token is missing 'alg' header [#502](https://github.com/jwt/ruby-jwt/pull/502) ([@cmrd-senya](https://github.com/cmrd-senya)).
|
|
18
|
+
- Support OpenSSL >= 3.0 [#496](https://github.com/jwt/ruby-jwt/pull/496) ([@anakinj](https://github.com/anakinj)).
|
|
19
|
+
- Your contribution here
|
|
20
|
+
|
|
21
|
+
## [v2.4.1](https://github.com/jwt/ruby-jwt/tree/v2.4.1) (2022-06-07)
|
|
6
22
|
|
|
7
23
|
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.4.0...v2.4.1)
|
|
8
24
|
|
|
25
|
+
**Fixes and enhancements:**
|
|
26
|
+
- Raise JWT::DecodeError on invalid signature [\#484](https://github.com/jwt/ruby-jwt/pull/484) ([@freakyfelt!](https://github.com/freakyfelt!)).
|
|
27
|
+
|
|
9
28
|
## [v2.4.0](https://github.com/jwt/ruby-jwt/tree/v2.4.0) (2022-06-06)
|
|
10
29
|
|
|
11
30
|
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.3.0...v2.4.0)
|
data/README.md
CHANGED
|
@@ -12,7 +12,7 @@ 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
|
-
* Ruby 2.4 support
|
|
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
|
|
|
@@ -135,17 +135,14 @@ puts decoded_token
|
|
|
135
135
|
* ES256K - ECDSA using P-256K and SHA-256
|
|
136
136
|
|
|
137
137
|
```ruby
|
|
138
|
-
ecdsa_key = OpenSSL::PKey::EC.
|
|
139
|
-
ecdsa_key.generate_key
|
|
140
|
-
ecdsa_public = OpenSSL::PKey::EC.new ecdsa_key
|
|
141
|
-
ecdsa_public.private_key = nil
|
|
138
|
+
ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
|
|
142
139
|
|
|
143
140
|
token = JWT.encode payload, ecdsa_key, 'ES256'
|
|
144
141
|
|
|
145
142
|
# eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg
|
|
146
143
|
puts token
|
|
147
144
|
|
|
148
|
-
decoded_token = JWT.decode token,
|
|
145
|
+
decoded_token = JWT.decode token, ecdsa_key, true, { algorithm: 'ES256' }
|
|
149
146
|
|
|
150
147
|
# Array
|
|
151
148
|
# [
|
|
@@ -186,7 +183,7 @@ decoded_token = JWT.decode token, public_key, true, { algorithm: 'ED25519' }
|
|
|
186
183
|
|
|
187
184
|
### **RSASSA-PSS**
|
|
188
185
|
|
|
189
|
-
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`.
|
|
190
187
|
|
|
191
188
|
```ruby
|
|
192
189
|
gem 'openssl', '~> 2.1'
|
|
@@ -546,30 +543,41 @@ end
|
|
|
546
543
|
|
|
547
544
|
### JSON Web Key (JWK)
|
|
548
545
|
|
|
549
|
-
JWK is a JSON structure representing a cryptographic key. Currently only supports RSA, EC and HMAC keys.
|
|
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.
|
|
550
547
|
|
|
551
|
-
|
|
552
|
-
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), "optional-kid")
|
|
553
|
-
payload, headers = { data: 'data' }, { kid: jwk.kid }
|
|
554
|
-
|
|
555
|
-
token = JWT.encode(payload, jwk.keypair, 'RS512', headers)
|
|
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.
|
|
556
549
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
|
562
570
|
|
|
563
|
-
begin
|
|
564
|
-
|
|
565
|
-
rescue JWT::JWKError
|
|
566
|
-
|
|
567
|
-
rescue JWT::DecodeError
|
|
568
|
-
|
|
569
|
-
end
|
|
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
|
|
570
578
|
```
|
|
571
579
|
|
|
572
|
-
or by passing
|
|
580
|
+
or by passing the JWKs as a simple Hash
|
|
573
581
|
|
|
574
582
|
```
|
|
575
583
|
jwks = { keys: [{ ... }] } # keys accepts both of string and symbol
|
|
@@ -587,8 +595,39 @@ jwk_hash = jwk.export
|
|
|
587
595
|
jwk_hash_with_private_key = jwk.export(include_private: true)
|
|
588
596
|
```
|
|
589
597
|
|
|
590
|
-
|
|
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
|
+
|
|
615
|
+
# Development and Tests
|
|
616
|
+
|
|
617
|
+
We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec and performing releases to rubygems.org, which can be done with
|
|
618
|
+
|
|
619
|
+
```bash
|
|
620
|
+
rake release
|
|
621
|
+
```
|
|
591
622
|
|
|
623
|
+
The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features.
|
|
624
|
+
|
|
625
|
+
```bash
|
|
626
|
+
bundle install
|
|
627
|
+
bundle exec appraisal rake test
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
## How to contribute
|
|
592
631
|
See [CONTRIBUTING](CONTRIBUTING.md).
|
|
593
632
|
|
|
594
633
|
## Contributors
|
data/lib/jwt/algos/eddsa.rb
CHANGED
|
@@ -27,6 +27,8 @@ module JWT
|
|
|
27
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
28
|
|
|
29
29
|
public_key.verify(signature, signing_input)
|
|
30
|
+
rescue RbNaCl::CryptoError
|
|
31
|
+
false
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
end
|
data/lib/jwt/base64.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module JWT
|
|
6
|
+
# Base64 helpers
|
|
7
|
+
class Base64
|
|
8
|
+
class << self
|
|
9
|
+
def url_encode(str)
|
|
10
|
+
::Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def url_decode(str)
|
|
14
|
+
str += '=' * (4 - str.length.modulo(4))
|
|
15
|
+
::Base64.decode64(str.tr('-_', '+/'))
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -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
|
data/lib/jwt/decode.rb
CHANGED
|
@@ -113,13 +113,11 @@ module JWT
|
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
def none_algorithm?
|
|
116
|
-
algorithm
|
|
116
|
+
algorithm == 'none'
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
def decode_crypto
|
|
120
|
-
@signature = Base64.
|
|
121
|
-
rescue ArgumentError
|
|
122
|
-
raise(JWT::DecodeError, 'Invalid segment encoding')
|
|
120
|
+
@signature = ::JWT::Base64.url_decode(@segments[2] || '')
|
|
123
121
|
end
|
|
124
122
|
|
|
125
123
|
def algorithm
|
|
@@ -139,8 +137,8 @@ module JWT
|
|
|
139
137
|
end
|
|
140
138
|
|
|
141
139
|
def parse_and_decode(segment)
|
|
142
|
-
JWT::JSON.parse(Base64.
|
|
143
|
-
rescue ::JSON::ParserError
|
|
140
|
+
JWT::JSON.parse(::JWT::Base64.url_decode(segment))
|
|
141
|
+
rescue ::JSON::ParserError
|
|
144
142
|
raise JWT::DecodeError, 'Invalid segment encoding'
|
|
145
143
|
end
|
|
146
144
|
end
|
data/lib/jwt/encode.rb
CHANGED
|
@@ -55,11 +55,11 @@ module JWT
|
|
|
55
55
|
def encode_signature
|
|
56
56
|
return '' if @algorithm == ALG_NONE
|
|
57
57
|
|
|
58
|
-
Base64.
|
|
58
|
+
::JWT::Base64.url_encode(JWT::Signature.sign(@algorithm, encoded_header_and_payload, @key))
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def encode(data)
|
|
62
|
-
Base64.
|
|
62
|
+
::JWT::Base64.url_encode(JWT::JSON.generate(data))
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def combine(*parts)
|
data/lib/jwt/jwk/ec.rb
CHANGED
|
@@ -4,39 +4,53 @@ require 'forwardable'
|
|
|
4
4
|
|
|
5
5
|
module JWT
|
|
6
6
|
module JWK
|
|
7
|
-
class EC < KeyBase
|
|
7
|
+
class EC < KeyBase # rubocop:disable Metrics/ClassLength
|
|
8
8
|
extend Forwardable
|
|
9
|
-
def_delegators
|
|
9
|
+
def_delegators :keypair, :public_key
|
|
10
10
|
|
|
11
11
|
KTY = 'EC'
|
|
12
12
|
KTYS = [KTY, OpenSSL::PKey::EC].freeze
|
|
13
13
|
BINARY = 2
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
attr_reader :keypair
|
|
16
|
+
|
|
17
|
+
def initialize(keypair, options = {})
|
|
16
18
|
raise ArgumentError, 'keypair must be of type OpenSSL::PKey::EC' unless keypair.is_a?(OpenSSL::PKey::EC)
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
@keypair = keypair
|
|
21
|
+
|
|
22
|
+
super(options)
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
def private?
|
|
23
26
|
@keypair.private_key?
|
|
24
27
|
end
|
|
25
28
|
|
|
26
|
-
def
|
|
29
|
+
def members
|
|
27
30
|
crv, x_octets, y_octets = keypair_components(keypair)
|
|
28
|
-
|
|
31
|
+
{
|
|
29
32
|
kty: KTY,
|
|
30
33
|
crv: crv,
|
|
31
34
|
x: encode_octets(x_octets),
|
|
32
|
-
y: encode_octets(y_octets)
|
|
33
|
-
kid: kid
|
|
35
|
+
y: encode_octets(y_octets)
|
|
34
36
|
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def export(options = {})
|
|
40
|
+
exported_hash = members.merge(kid: kid)
|
|
41
|
+
|
|
35
42
|
return exported_hash unless private? && options[:include_private] == true
|
|
36
43
|
|
|
37
44
|
append_private_parts(exported_hash)
|
|
38
45
|
end
|
|
39
46
|
|
|
47
|
+
def key_digest
|
|
48
|
+
_crv, x_octets, y_octets = keypair_components(keypair)
|
|
49
|
+
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
|
|
50
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
|
|
51
|
+
OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
|
|
52
|
+
end
|
|
53
|
+
|
|
40
54
|
private
|
|
41
55
|
|
|
42
56
|
def append_private_parts(the_hash)
|
|
@@ -46,13 +60,6 @@ module JWT
|
|
|
46
60
|
)
|
|
47
61
|
end
|
|
48
62
|
|
|
49
|
-
def generate_kid(ec_keypair)
|
|
50
|
-
_crv, x_octets, y_octets = keypair_components(ec_keypair)
|
|
51
|
-
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
|
|
52
|
-
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
|
|
53
|
-
OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
63
|
def keypair_components(ec_keypair)
|
|
57
64
|
encoded_point = ec_keypair.public_key.to_bn.to_s(BINARY)
|
|
58
65
|
case ec_keypair.group.curve_name
|
|
@@ -75,11 +82,11 @@ module JWT
|
|
|
75
82
|
end
|
|
76
83
|
|
|
77
84
|
def encode_octets(octets)
|
|
78
|
-
Base64.
|
|
85
|
+
::JWT::Base64.url_encode(octets)
|
|
79
86
|
end
|
|
80
87
|
|
|
81
88
|
def encode_open_ssl_bn(key_part)
|
|
82
|
-
Base64.
|
|
89
|
+
::JWT::Base64.url_encode(key_part.to_s(BINARY))
|
|
83
90
|
end
|
|
84
91
|
|
|
85
92
|
class << self
|
|
@@ -90,7 +97,7 @@ module JWT
|
|
|
90
97
|
jwk_crv, jwk_x, jwk_y, jwk_d, jwk_kid = jwk_attrs(jwk_data, %i[crv x y d kid])
|
|
91
98
|
raise JWT::JWKError, 'Key format is invalid for EC' unless jwk_crv && jwk_x && jwk_y
|
|
92
99
|
|
|
93
|
-
new(ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d), jwk_kid)
|
|
100
|
+
new(ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d), kid: jwk_kid)
|
|
94
101
|
end
|
|
95
102
|
|
|
96
103
|
def to_openssl_curve(crv)
|
|
@@ -114,39 +121,77 @@ module JWT
|
|
|
114
121
|
end
|
|
115
122
|
end
|
|
116
123
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
124
|
+
if ::JWT.openssl_3?
|
|
125
|
+
def ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/MethodLength
|
|
126
|
+
curve = to_openssl_curve(jwk_crv)
|
|
127
|
+
|
|
128
|
+
x_octets = decode_octets(jwk_x)
|
|
129
|
+
y_octets = decode_octets(jwk_y)
|
|
130
|
+
|
|
131
|
+
point = OpenSSL::PKey::EC::Point.new(
|
|
132
|
+
OpenSSL::PKey::EC::Group.new(curve),
|
|
133
|
+
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
sequence = if jwk_d
|
|
137
|
+
# https://datatracker.ietf.org/doc/html/rfc5915.html
|
|
138
|
+
# ECPrivateKey ::= SEQUENCE {
|
|
139
|
+
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
|
|
140
|
+
# privateKey OCTET STRING,
|
|
141
|
+
# parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
|
|
142
|
+
# publicKey [1] BIT STRING OPTIONAL
|
|
143
|
+
# }
|
|
144
|
+
|
|
145
|
+
OpenSSL::ASN1::Sequence([
|
|
146
|
+
OpenSSL::ASN1::Integer(1),
|
|
147
|
+
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
|
|
148
|
+
OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
|
|
149
|
+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
|
|
150
|
+
])
|
|
151
|
+
else
|
|
152
|
+
OpenSSL::ASN1::Sequence([
|
|
153
|
+
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
|
|
154
|
+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
|
|
155
|
+
])
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
OpenSSL::PKey::EC.new(sequence.to_der)
|
|
159
|
+
end
|
|
160
|
+
else
|
|
161
|
+
def ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d)
|
|
162
|
+
curve = to_openssl_curve(jwk_crv)
|
|
163
|
+
|
|
164
|
+
x_octets = decode_octets(jwk_x)
|
|
165
|
+
y_octets = decode_octets(jwk_y)
|
|
166
|
+
|
|
167
|
+
key = OpenSSL::PKey::EC.new(curve)
|
|
168
|
+
|
|
169
|
+
# The details of the `Point` instantiation are covered in:
|
|
170
|
+
# - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
|
|
171
|
+
# - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
|
|
172
|
+
# - https://tools.ietf.org/html/rfc5480#section-2.2
|
|
173
|
+
# - https://www.secg.org/SEC1-Ver-1.0.pdf
|
|
174
|
+
# Section 2.3.3 of the last of these references specifies that the
|
|
175
|
+
# encoding of an uncompressed point consists of the byte `0x04` followed
|
|
176
|
+
# by the x value then the y value.
|
|
177
|
+
point = OpenSSL::PKey::EC::Point.new(
|
|
178
|
+
OpenSSL::PKey::EC::Group.new(curve),
|
|
179
|
+
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
key.public_key = point
|
|
183
|
+
key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
|
|
184
|
+
|
|
185
|
+
key
|
|
186
|
+
end
|
|
142
187
|
end
|
|
143
188
|
|
|
144
189
|
def decode_octets(jwk_data)
|
|
145
|
-
Base64.
|
|
190
|
+
::JWT::Base64.url_decode(jwk_data)
|
|
146
191
|
end
|
|
147
192
|
|
|
148
193
|
def decode_open_ssl_bn(jwk_data)
|
|
149
|
-
OpenSSL::BN.new(Base64.
|
|
194
|
+
OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
|
|
150
195
|
end
|
|
151
196
|
end
|
|
152
197
|
end
|
data/lib/jwt/jwk/hmac.rb
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
module JWT
|
|
4
4
|
module JWK
|
|
5
5
|
class HMAC < KeyBase
|
|
6
|
-
KTY
|
|
6
|
+
KTY = 'oct'
|
|
7
7
|
KTYS = [KTY, String].freeze
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
raise ArgumentError, 'keypair must be of type String' unless keypair.is_a?(String)
|
|
9
|
+
attr_reader :signing_key
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
def initialize(signing_key, options = {})
|
|
12
|
+
raise ArgumentError, 'signing_key must be of type String' unless signing_key.is_a?(String)
|
|
13
|
+
|
|
14
|
+
@signing_key = signing_key
|
|
15
|
+
super(options)
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
def private?
|
|
@@ -31,14 +33,21 @@ module JWT
|
|
|
31
33
|
return exported_hash unless private? && options[:include_private] == true
|
|
32
34
|
|
|
33
35
|
exported_hash.merge(
|
|
34
|
-
k:
|
|
36
|
+
k: signing_key
|
|
35
37
|
)
|
|
36
38
|
end
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
def members
|
|
41
|
+
{
|
|
42
|
+
kty: KTY,
|
|
43
|
+
k: signing_key
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
alias keypair signing_key # for backwards compatibility
|
|
39
48
|
|
|
40
|
-
def
|
|
41
|
-
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::UTF8String.new(
|
|
49
|
+
def key_digest
|
|
50
|
+
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::UTF8String.new(signing_key),
|
|
42
51
|
OpenSSL::ASN1::UTF8String.new(KTY)])
|
|
43
52
|
OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
|
|
44
53
|
end
|
|
@@ -50,7 +59,7 @@ module JWT
|
|
|
50
59
|
|
|
51
60
|
raise JWT::JWKError, 'Key format is invalid for HMAC' unless jwk_k
|
|
52
61
|
|
|
53
|
-
new(jwk_k, jwk_kid)
|
|
62
|
+
new(jwk_k, kid: jwk_kid)
|
|
54
63
|
end
|
|
55
64
|
end
|
|
56
65
|
end
|
data/lib/jwt/jwk/key_base.rb
CHANGED
|
@@ -3,17 +3,33 @@
|
|
|
3
3
|
module JWT
|
|
4
4
|
module JWK
|
|
5
5
|
class KeyBase
|
|
6
|
-
attr_reader :keypair, :kid
|
|
7
|
-
|
|
8
|
-
def initialize(keypair, kid = nil)
|
|
9
|
-
@keypair = keypair
|
|
10
|
-
@kid = kid
|
|
11
|
-
end
|
|
12
|
-
|
|
13
6
|
def self.inherited(klass)
|
|
14
7
|
super
|
|
15
8
|
::JWT::JWK.classes << klass
|
|
16
9
|
end
|
|
10
|
+
|
|
11
|
+
def initialize(options)
|
|
12
|
+
options ||= {}
|
|
13
|
+
|
|
14
|
+
if options.is_a?(String) # For backwards compatibility when kid was a String
|
|
15
|
+
options = { kid: options }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@kid = options[:kid]
|
|
19
|
+
@kid_generator = options[:kid_generator] || ::JWT.configuration.jwk.kid_generator
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def kid
|
|
23
|
+
@kid ||= generate_kid
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
attr_reader :kid_generator
|
|
29
|
+
|
|
30
|
+
def generate_kid
|
|
31
|
+
kid_generator.new(self).generate
|
|
32
|
+
end
|
|
17
33
|
end
|
|
18
34
|
end
|
|
19
35
|
end
|
data/lib/jwt/jwk/key_finder.rb
CHANGED
data/lib/jwt/jwk/rsa.rb
CHANGED
|
@@ -8,10 +8,14 @@ module JWT
|
|
|
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
|
+
attr_reader :keypair
|
|
12
|
+
|
|
13
|
+
def initialize(keypair, options = {})
|
|
12
14
|
raise ArgumentError, 'keypair must be of type OpenSSL::PKey::RSA' unless keypair.is_a?(OpenSSL::PKey::RSA)
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
@keypair = keypair
|
|
17
|
+
|
|
18
|
+
super(options)
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
def private?
|
|
@@ -23,26 +27,29 @@ module JWT
|
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
def export(options = {})
|
|
26
|
-
exported_hash =
|
|
27
|
-
kty: KTY,
|
|
28
|
-
n: encode_open_ssl_bn(public_key.n),
|
|
29
|
-
e: encode_open_ssl_bn(public_key.e),
|
|
30
|
-
kid: kid
|
|
31
|
-
}
|
|
30
|
+
exported_hash = members.merge(kid: kid)
|
|
32
31
|
|
|
33
32
|
return exported_hash unless private? && options[:include_private] == true
|
|
34
33
|
|
|
35
34
|
append_private_parts(exported_hash)
|
|
36
35
|
end
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
def members
|
|
38
|
+
{
|
|
39
|
+
kty: KTY,
|
|
40
|
+
n: encode_open_ssl_bn(public_key.n),
|
|
41
|
+
e: encode_open_ssl_bn(public_key.e)
|
|
42
|
+
}
|
|
43
|
+
end
|
|
39
44
|
|
|
40
|
-
def
|
|
45
|
+
def key_digest
|
|
41
46
|
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
|
|
42
47
|
OpenSSL::ASN1::Integer.new(public_key.e)])
|
|
43
48
|
OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
|
|
44
49
|
end
|
|
45
50
|
|
|
51
|
+
private
|
|
52
|
+
|
|
46
53
|
def append_private_parts(the_hash)
|
|
47
54
|
the_hash.merge(
|
|
48
55
|
d: encode_open_ssl_bn(keypair.d),
|
|
@@ -55,7 +62,7 @@ module JWT
|
|
|
55
62
|
end
|
|
56
63
|
|
|
57
64
|
def encode_open_ssl_bn(key_part)
|
|
58
|
-
Base64.
|
|
65
|
+
::JWT::Base64.url_encode(key_part.to_s(BINARY))
|
|
59
66
|
end
|
|
60
67
|
|
|
61
68
|
class << self
|
|
@@ -63,8 +70,7 @@ module JWT
|
|
|
63
70
|
pkey_params = jwk_attributes(jwk_data, *RSA_KEY_ELEMENTS) do |value|
|
|
64
71
|
decode_open_ssl_bn(value)
|
|
65
72
|
end
|
|
66
|
-
kid
|
|
67
|
-
new(rsa_pkey(pkey_params), kid)
|
|
73
|
+
new(rsa_pkey(pkey_params), kid: jwk_attributes(jwk_data, :kid)[:kid])
|
|
68
74
|
end
|
|
69
75
|
|
|
70
76
|
private
|
|
@@ -80,35 +86,51 @@ module JWT
|
|
|
80
86
|
def rsa_pkey(rsa_parameters)
|
|
81
87
|
raise JWT::JWKError, 'Key format is invalid for RSA' unless rsa_parameters[:n] && rsa_parameters[:e]
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
create_rsa_key(rsa_parameters)
|
|
84
90
|
end
|
|
85
91
|
|
|
86
|
-
if
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
if ::JWT.openssl_3?
|
|
93
|
+
ASN1_SEQUENCE = %i[n e d p q dp dq qi].freeze
|
|
94
|
+
def create_rsa_key(rsa_parameters)
|
|
95
|
+
sequence = ASN1_SEQUENCE.each_with_object([]) do |key, arr|
|
|
96
|
+
next if rsa_parameters[key].nil?
|
|
97
|
+
|
|
98
|
+
arr << OpenSSL::ASN1::Integer.new(rsa_parameters[key])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if sequence.size > 2 # For a private key
|
|
102
|
+
sequence.unshift(OpenSSL::ASN1::Integer.new(0))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der)
|
|
106
|
+
end
|
|
107
|
+
elsif OpenSSL::PKey::RSA.new.respond_to?(:set_key)
|
|
108
|
+
def create_rsa_key(rsa_parameters)
|
|
109
|
+
OpenSSL::PKey::RSA.new.tap do |rsa_key|
|
|
110
|
+
rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d])
|
|
111
|
+
rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q]
|
|
112
|
+
rsa_key.set_crt_params(rsa_parameters[:dp], rsa_parameters[:dq], rsa_parameters[:qi]) if rsa_parameters[:dp] && rsa_parameters[:dq] && rsa_parameters[:qi]
|
|
113
|
+
end
|
|
92
114
|
end
|
|
93
115
|
else
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
116
|
+
def create_rsa_key(rsa_parameters) # rubocop:disable Metrics/AbcSize
|
|
117
|
+
OpenSSL::PKey::RSA.new.tap do |rsa_key|
|
|
118
|
+
rsa_key.n = rsa_parameters[:n]
|
|
119
|
+
rsa_key.e = rsa_parameters[:e]
|
|
120
|
+
rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d]
|
|
121
|
+
rsa_key.p = rsa_parameters[:p] if rsa_parameters[:p]
|
|
122
|
+
rsa_key.q = rsa_parameters[:q] if rsa_parameters[:q]
|
|
123
|
+
rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp]
|
|
124
|
+
rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq]
|
|
125
|
+
rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
|
|
126
|
+
end
|
|
105
127
|
end
|
|
106
128
|
end
|
|
107
129
|
|
|
108
130
|
def decode_open_ssl_bn(jwk_data)
|
|
109
131
|
return nil unless jwk_data
|
|
110
132
|
|
|
111
|
-
OpenSSL::BN.new(Base64.
|
|
133
|
+
OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
|
|
112
134
|
end
|
|
113
135
|
end
|
|
114
136
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JWT
|
|
4
|
+
module JWK
|
|
5
|
+
# https://tools.ietf.org/html/rfc7638
|
|
6
|
+
class Thumbprint
|
|
7
|
+
attr_reader :jwk
|
|
8
|
+
|
|
9
|
+
def initialize(jwk)
|
|
10
|
+
@jwk = jwk
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def generate
|
|
14
|
+
::Base64.urlsafe_encode64(
|
|
15
|
+
Digest::SHA256.digest(
|
|
16
|
+
JWT::JSON.generate(
|
|
17
|
+
jwk.members.sort.to_h
|
|
18
|
+
)
|
|
19
|
+
), padding: false
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
alias to_s generate
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/jwt/version.rb
CHANGED
|
@@ -11,13 +11,18 @@ module JWT
|
|
|
11
11
|
# major version
|
|
12
12
|
MAJOR = 2
|
|
13
13
|
# minor version
|
|
14
|
-
MINOR =
|
|
14
|
+
MINOR = 5
|
|
15
15
|
# tiny version
|
|
16
|
-
TINY =
|
|
16
|
+
TINY = 0
|
|
17
17
|
# alpha, beta, etc. tag
|
|
18
18
|
PRE = nil
|
|
19
19
|
|
|
20
20
|
# Build version string
|
|
21
21
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
def self.openssl_3?
|
|
25
|
+
return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL')
|
|
26
|
+
return true if OpenSSL::OPENSSL_VERSION_NUMBER >= 3 * 0x10000000
|
|
27
|
+
end
|
|
23
28
|
end
|
data/lib/jwt/x5c_key_finder.rb
CHANGED
data/lib/jwt.rb
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'jwt/version'
|
|
4
|
+
require 'jwt/base64'
|
|
4
5
|
require 'jwt/json'
|
|
5
6
|
require 'jwt/decode'
|
|
6
|
-
require 'jwt/
|
|
7
|
+
require 'jwt/configuration'
|
|
7
8
|
require 'jwt/encode'
|
|
8
9
|
require 'jwt/error'
|
|
9
10
|
require 'jwt/jwk'
|
|
@@ -13,7 +14,7 @@ require 'jwt/jwk'
|
|
|
13
14
|
# Should be up to date with the latest spec:
|
|
14
15
|
# https://tools.ietf.org/html/rfc7519
|
|
15
16
|
module JWT
|
|
16
|
-
|
|
17
|
+
extend ::JWT::Configuration
|
|
17
18
|
|
|
18
19
|
module_function
|
|
19
20
|
|
|
@@ -25,6 +26,6 @@ module JWT
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter
|
|
28
|
-
Decode.new(jwt, key, verify,
|
|
29
|
+
Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
|
|
29
30
|
end
|
|
30
31
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jwt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tim Rudat
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-08-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: appraisal
|
|
@@ -127,9 +127,13 @@ files:
|
|
|
127
127
|
- lib/jwt/algos/ps.rb
|
|
128
128
|
- lib/jwt/algos/rsa.rb
|
|
129
129
|
- lib/jwt/algos/unsupported.rb
|
|
130
|
+
- lib/jwt/base64.rb
|
|
130
131
|
- lib/jwt/claims_validator.rb
|
|
132
|
+
- lib/jwt/configuration.rb
|
|
133
|
+
- lib/jwt/configuration/container.rb
|
|
134
|
+
- lib/jwt/configuration/decode_configuration.rb
|
|
135
|
+
- lib/jwt/configuration/jwk_configuration.rb
|
|
131
136
|
- lib/jwt/decode.rb
|
|
132
|
-
- lib/jwt/default_options.rb
|
|
133
137
|
- lib/jwt/encode.rb
|
|
134
138
|
- lib/jwt/error.rb
|
|
135
139
|
- lib/jwt/json.rb
|
|
@@ -138,7 +142,9 @@ files:
|
|
|
138
142
|
- lib/jwt/jwk/hmac.rb
|
|
139
143
|
- lib/jwt/jwk/key_base.rb
|
|
140
144
|
- lib/jwt/jwk/key_finder.rb
|
|
145
|
+
- lib/jwt/jwk/kid_as_key_digest.rb
|
|
141
146
|
- lib/jwt/jwk/rsa.rb
|
|
147
|
+
- lib/jwt/jwk/thumbprint.rb
|
|
142
148
|
- lib/jwt/security_utils.rb
|
|
143
149
|
- lib/jwt/signature.rb
|
|
144
150
|
- lib/jwt/verify.rb
|
|
@@ -150,7 +156,7 @@ licenses:
|
|
|
150
156
|
- MIT
|
|
151
157
|
metadata:
|
|
152
158
|
bug_tracker_uri: https://github.com/jwt/ruby-jwt/issues
|
|
153
|
-
changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.
|
|
159
|
+
changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.5.0/CHANGELOG.md
|
|
154
160
|
post_install_message:
|
|
155
161
|
rdoc_options: []
|
|
156
162
|
require_paths:
|
|
@@ -166,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
166
172
|
- !ruby/object:Gem::Version
|
|
167
173
|
version: '0'
|
|
168
174
|
requirements: []
|
|
169
|
-
rubygems_version: 3.3.
|
|
175
|
+
rubygems_version: 3.3.21
|
|
170
176
|
signing_key:
|
|
171
177
|
specification_version: 4
|
|
172
178
|
summary: JSON Web Token implementation in Ruby
|
data/lib/jwt/default_options.rb
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module JWT
|
|
4
|
-
module DefaultOptions
|
|
5
|
-
DEFAULT_OPTIONS = {
|
|
6
|
-
verify_expiration: true,
|
|
7
|
-
verify_not_before: true,
|
|
8
|
-
verify_iss: false,
|
|
9
|
-
verify_iat: false,
|
|
10
|
-
verify_jti: false,
|
|
11
|
-
verify_aud: false,
|
|
12
|
-
verify_sub: false,
|
|
13
|
-
leeway: 0,
|
|
14
|
-
algorithms: ['HS256'],
|
|
15
|
-
required_claims: []
|
|
16
|
-
}.freeze
|
|
17
|
-
end
|
|
18
|
-
end
|