New Upstream Release - ruby-json-jwt

Ready changes

Summary

Merged new upstream version: 1.16.3 (was: 1.15.3).

Diff

diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml
new file mode 100644
index 0000000..19509fb
--- /dev/null
+++ b/.github/workflows/spec.yml
@@ -0,0 +1,31 @@
+name: Spec
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+
+permissions:
+  contents: read
+
+jobs:
+  spec:
+    strategy:
+      matrix:
+        os: ['ubuntu-20.04', 'ubuntu-22.04']
+        ruby-version: ['3.1', '3.2']
+        include:
+        - os: 'ubuntu-20.04'
+          ruby-version: '3.0'
+    runs-on: ${{ matrix.os }}
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Ruby
+      uses: ruby/setup-ruby@v1
+      with:
+        ruby-version: ${{ matrix.ruby-version }}
+        bundler-cache: true
+    - name: Run Specs
+      run: bundle exec rake spec
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 55db3e4..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-before_install:
-  - gem install bundler
-  - git submodule update --init --recursive
-
-rvm:
-  - 2.5.8
-  - 2.6.6
-  - 2.7.2
-  - 3.0.2
-
-jdk:
-  - openjdk11
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..91720ff
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,17 @@
+## [Unreleased]
+
+## [1.16.0] - 2022-10-08
+
+### Fixed
+
+- Remove padding oracle by @btoews in https://github.com/nov/json-jwt/pull/109
+
+## [1.16.0] - 2022-10-08
+
+### Added
+
+- start recording CHANGELOG
+
+### Changed
+
+* Switch from httpclient to faraday v2 https://github.com/nov/json-jwt/pull/110
\ No newline at end of file
diff --git a/README.md b/README.md
index 1ea16c4..fe6c579 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,6 @@
 
 JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and JSON Web Key) in Ruby
 
-[![Build Status](https://secure.travis-ci.org/nov/json-jwt.png)](http://travis-ci.org/nov/json-jwt)
-
 ## Installation
 
 ```
@@ -49,6 +47,17 @@ input = "jwt_header.jwt_claims.jwt_signature"
 JSON::JWT.decode(input, public_key)
 ```
 
+If you need to get a JWK from `jwks_uri` of OpenID Connect IdP, you can use `JSON::JWK::Set::Fetcher` to fetch (& optionally cache) it.
+
+```ruby
+# JWK Set Fetching & Caching
+# NOTE: Optionally by setting cache instance, JWKs are cached by kid.
+JSON::JWK::Set::Fetcher.cache = Rails.cache
+
+JSON::JWK::Set::Fetcher.fetch(jwks_uri, kid: kid)
+# => returns JSON::JWK instance or raise JSON::JWK::Set::KidNotFound
+```
+
 For more details, read [Documentation Wiki](https://github.com/nov/json-jwt/wiki).
 
 ## Note on Patches/Pull Requests
diff --git a/VERSION b/VERSION
index cd99d38..b8ae5a5 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.14.0
\ No newline at end of file
+1.16.3
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
index 9347cad..97cd739 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+ruby-json-jwt (1.16.3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Tue, 06 Jun 2023 20:41:49 -0000
+
 ruby-json-jwt (1.14.0-2) unstable; urgency=medium
 
   * No-change rebuild for unstable. (Closes: #1011682)
diff --git a/debian/patches/001-remove-simplecov.patch b/debian/patches/001-remove-simplecov.patch
index e6aebe3..858b4d7 100644
--- a/debian/patches/001-remove-simplecov.patch
+++ b/debian/patches/001-remove-simplecov.patch
@@ -7,10 +7,10 @@ Forwarded: not-needed
  spec/spec_helper.rb | 8 ++++----
  1 file changed, 4 insertions(+), 4 deletions(-)
 
-diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
-index b27d079..145598b 100644
---- a/spec/spec_helper.rb
-+++ b/spec/spec_helper.rb
+Index: ruby-json-jwt.git/spec/spec_helper.rb
+===================================================================
+--- ruby-json-jwt.git.orig/spec/spec_helper.rb
++++ ruby-json-jwt.git/spec/spec_helper.rb
 @@ -1,8 +1,8 @@
 -require 'simplecov'
 +#require 'simplecov'
diff --git a/json-jwt.gemspec b/json-jwt.gemspec
index a9c73f7..943d1f4 100644
--- a/json-jwt.gemspec
+++ b/json-jwt.gemspec
@@ -16,8 +16,11 @@ Gem::Specification.new do |gem|
   gem.add_runtime_dependency 'activesupport', '>= 4.2'
   gem.add_runtime_dependency 'bindata'
   gem.add_runtime_dependency 'aes_key_wrap'
+  gem.add_runtime_dependency 'faraday', '~> 2.0'
+  gem.add_runtime_dependency 'faraday-follow_redirects'
   gem.add_development_dependency 'rake'
   gem.add_development_dependency 'simplecov'
+  gem.add_development_dependency 'webmock'
   gem.add_development_dependency 'rspec'
   gem.add_development_dependency 'rspec-its'
 end
diff --git a/lib/json/jose.rb b/lib/json/jose.rb
index 53dfa93..eb94b10 100644
--- a/lib/json/jose.rb
+++ b/lib/json/jose.rb
@@ -26,9 +26,7 @@ module JSON
       when JSON::JWK
         key.to_key
       when JSON::JWK::Set
-        key.detect do |jwk|
-          jwk[:kid] && jwk[:kid] == kid
-        end&.to_key or raise JWK::Set::KidNotFound
+        key[kid]&.to_key or raise JWK::Set::KidNotFound
       else
         key
       end
diff --git a/lib/json/jwe.rb b/lib/json/jwe.rb
index a46f2d1..eea1c9c 100644
--- a/lib/json/jwe.rb
+++ b/lib/json/jwe.rb
@@ -43,9 +43,12 @@ module JSON
       raise UnexpectedAlgorithm.new('Unexpected alg header') unless algorithms.blank? || Array(algorithms).include?(alg)
       raise UnexpectedAlgorithm.new('Unexpected enc header') unless encryption_methods.blank? || Array(encryption_methods).include?(enc)
       self.private_key_or_secret = with_jwk_support private_key_or_secret
-      cipher.decrypt
       self.content_encryption_key = decrypt_content_encryption_key
       self.mac_key, self.encryption_key = derive_encryption_and_mac_keys
+
+      verify_cbc_authentication_tag! if cbc?
+
+      cipher.decrypt
       cipher.key = encryption_key
       cipher.iv = iv # NOTE: 'iv' has to be set after 'key' for GCM
       if gcm?
@@ -54,8 +57,15 @@ module JSON
         cipher.auth_tag = authentication_tag
         cipher.auth_data = auth_data
       end
-      self.plain_text = cipher.update(cipher_text) + cipher.final
-      verify_cbc_authentication_tag! if cbc?
+
+      begin
+        self.plain_text = cipher.update(cipher_text) + cipher.final
+      rescue OpenSSL::OpenSSLError
+        # Ensure that the same error is raised for invalid PKCS7 padding
+        # as for invalid signatures. This prevents padding-oracle attacks.
+        raise DecryptionFailed
+      end
+
       self
     end
 
@@ -244,7 +254,7 @@ module JSON
         sha_digest, mac_key, secured_input
       )[0, sha_size / 2 / 8]
       unless secure_compare(authentication_tag, expected_authentication_tag)
-        raise DecryptionFailed.new('Invalid authentication tag')
+        raise DecryptionFailed
       end
     end
 
diff --git a/lib/json/jwk/set.rb b/lib/json/jwk/set.rb
index be9752f..9a66160 100644
--- a/lib/json/jwk/set.rb
+++ b/lib/json/jwk/set.rb
@@ -19,6 +19,12 @@ module JSON
         'application/jwk-set+json'
       end
 
+      def [](kid)
+        detect do |jwk|
+          jwk[:kid] && jwk[:kid] == kid
+        end
+      end
+
       def as_json(options = {})
         # NOTE: Array.new wrapper is requied to avoid CircularReferenceError
         {keys: Array.new(self)}
diff --git a/lib/json/jwk/set/fetcher.rb b/lib/json/jwk/set/fetcher.rb
new file mode 100644
index 0000000..6d3d577
--- /dev/null
+++ b/lib/json/jwk/set/fetcher.rb
@@ -0,0 +1,86 @@
+module JSON
+  class JWK
+    class Set
+      module Fetcher
+        class Cache
+          def fetch(cache_key, options = {})
+            yield
+          end
+
+          def delete(cache_key, options = {}); end
+        end
+
+        def self.logger
+          @@logger
+        end
+        def self.logger=(logger)
+          @@logger = logger
+        end
+        self.logger = Logger.new(STDOUT)
+        self.logger.progname = 'JSON::JWK::Set::Fetcher'
+
+        def self.debugging?
+          @@debugging
+        end
+        def self.debugging=(boolean)
+          @@debugging = boolean
+        end
+        def self.debug!
+          self.debugging = true
+        end
+        def self.debug(&block)
+          original = self.debugging?
+          debug!
+          yield
+        ensure
+          self.debugging = original
+        end
+        self.debugging = false
+
+        def self.http_client
+          Faraday.new(headers: {user_agent: "JSON::JWK::Set::Fetcher #{VERSION}"}) do |faraday|
+            faraday.response :raise_error
+            faraday.response :follow_redirects
+            faraday.response :logger, JSON::JWK::Set::Fetcher.logger if debugging?
+            faraday.adapter Faraday.default_adapter
+            http_config.try(:call, faraday)
+          end
+        end
+        def self.http_config(&block)
+          @@http_config ||= block
+        end
+
+        def self.cache=(cache)
+          @@cache = cache
+        end
+        def self.cache
+          @@cache
+        end
+        self.cache = Cache.new
+
+        def self.fetch(jwks_uri, kid:, auto_detect: true, **options)
+          cache_key = [
+            'json:jwk:set',
+            OpenSSL::Digest::MD5.hexdigest(jwks_uri),
+            kid
+          ].collect(&:to_s).join(':')
+
+          jwks = Set.new(
+            JSON.parse(
+              cache.fetch(cache_key, options) do
+                http_client.get(jwks_uri).body
+              end
+            )
+          )
+          cache.delete(cache_key, options) if jwks[kid].blank?
+
+          if auto_detect
+            jwks[kid] or raise KidNotFound
+          else
+            jwks
+          end
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/json/jwt.rb b/lib/json/jwt.rb
index 46f2732..9958751 100644
--- a/lib/json/jwt.rb
+++ b/lib/json/jwt.rb
@@ -1,11 +1,17 @@
 require 'openssl'
 require 'base64'
+require 'faraday'
+require 'faraday/follow_redirects'
 require 'active_support'
 require 'active_support/core_ext'
 require 'json/jose'
 
 module JSON
   class JWT < ActiveSupport::HashWithIndifferentAccess
+    VERSION = ::File.read(
+      ::File.join(::File.dirname(__FILE__), '../../VERSION')
+    ).chomp
+
     attr_accessor :blank_payload
     attr_accessor :signature
 
@@ -132,3 +138,4 @@ require 'json/jwe'
 require 'json/jwk'
 require 'json/jwk/jwkizable'
 require 'json/jwk/set'
+require 'json/jwk/set/fetcher'
\ No newline at end of file
diff --git a/spec/helpers/webmock_helper.rb b/spec/helpers/webmock_helper.rb
new file mode 100644
index 0000000..5a26b12
--- /dev/null
+++ b/spec/helpers/webmock_helper.rb
@@ -0,0 +1,34 @@
+require 'webmock/rspec'
+
+module WebMockHelper
+  def mock_json(method, endpoint, response_file, options = {})
+    stub_request(method, endpoint).to_return(
+      response_for(response_file, options)
+    )
+    result = yield
+    a_request(method, endpoint).should have_been_made.once
+    result
+  end
+
+  def request_to(endpoint, method = :get)
+    raise_error { |e|
+      e.should be_instance_of WebMock::NetConnectNotAllowedError
+      e.message.should include("Unregistered request: #{method.to_s.upcase}")
+      e.message.should include(endpoint)
+    }
+  end
+
+  private
+
+  def response_for(response_file, options = {})
+    response = {}
+    response[:body] = File.new(File.join(File.dirname(__FILE__), '../mock_response', "#{response_file}.#{options[:format] || :json}"))
+    if options[:status]
+      response[:status] = options[:status]
+    end
+    response
+  end
+end
+
+include WebMockHelper
+WebMock.disable_net_connect!
diff --git a/spec/json/jwe_spec.rb b/spec/json/jwe_spec.rb
index 18a3d28..598e937 100644
--- a/spec/json/jwe_spec.rb
+++ b/spec/json/jwe_spec.rb
@@ -91,22 +91,60 @@ describe JSON::JWE do
     end
 
     shared_examples_for :verify_cbc_authentication_tag do
-      let(:jwe_string) do
+      let(:jwe_parts) do
         _jwe_ = JSON::JWE.new plain_text
         _jwe_.alg, _jwe_.enc = alg, enc
         _jwe_.encrypt! key
-        hdr, extra, iv, cipher_text, _ = _jwe_.to_s.split '.'
-        [hdr, extra, iv, cipher_text, ''].join '.'
+        _jwe_.to_s.split '.'
       end
 
-      it do
-        # fetching those variables outside of exception block to make sure
-        # we intercept exception in decrypt! and not in other place
-        j = jwe
-        k = key
-        expect do
-          j.decrypt! k
-        end.to raise_error JSON::JWE::DecryptionFailed
+      let(:hdr)         { jwe_parts[0] }
+      let(:extra)       { jwe_parts[1] }
+      let(:iv)          { jwe_parts[2] }
+      let(:cipher_text) { jwe_parts[3] }
+      let(:signature)   { jwe_parts[4] }
+
+      let(:jwe_string) { [hdr, extra, iv, cipher_text, signature].join '.' }
+
+      shared_examples_for :signature_verification_failure do
+        it do
+          # fetching those variables outside of exception block to make sure
+          # we intercept exception in decrypt! and not in other place
+          j = jwe
+          k = key
+          expect do
+            j.decrypt! k
+          end.to raise_error JSON::JWE::DecryptionFailed
+        end
+      end
+
+      describe "with missing signature" do
+        let(:signature) { "" }
+        it_behaves_like :signature_verification_failure
+      end
+
+      describe "with good pkcs7 padding and bad signature" do
+        let(:iv) do
+          good_padding_byte = 16 - (plain_text.bytesize % 16)
+          bad_padding_byte = 1
+          iv_bytes = Base64.urlsafe_decode64(super()).bytes
+          iv_bytes[-1] = iv_bytes[-1] ^ good_padding_byte ^ bad_padding_byte
+          Base64.urlsafe_encode64(iv_bytes.pack("C*"), padding: false)
+        end
+
+        it_behaves_like :signature_verification_failure
+      end
+
+      describe "with bad pkcs7 padding and bad signature" do
+        let(:iv) do
+          good_padding_byte = 16 - (plain_text.bytesize % 16)
+          bad_padding_byte = good_padding_byte - 1
+          iv_bytes = Base64.urlsafe_decode64(super()).bytes
+          iv_bytes[-1] = iv_bytes[-1] ^ good_padding_byte ^ bad_padding_byte
+          Base64.urlsafe_encode64(iv_bytes.pack("C*"), padding: false)
+        end
+
+        it_behaves_like :signature_verification_failure
       end
     end
 
diff --git a/spec/json/jwk/set/fetcher_spec.rb b/spec/json/jwk/set/fetcher_spec.rb
new file mode 100644
index 0000000..5d14783
--- /dev/null
+++ b/spec/json/jwk/set/fetcher_spec.rb
@@ -0,0 +1,227 @@
+require 'spec_helper'
+
+describe JSON::JWK::Set::Fetcher do
+  describe JSON::JWK::Set::Fetcher::Cache do
+    let(:something) { SecureRandom.hex(32) }
+
+    it 'just execute givne block' do
+      expect(
+        subject.fetch('cache_key') do
+          something
+        end
+      ).to eq something
+    end
+  end
+
+  describe 'debugging feature' do
+    after { JSON::JWK::Set::Fetcher.debugging = false }
+
+    its(:logger) { should be_a Logger }
+    its(:debugging?) { should == false }
+
+    describe '.debug!' do
+      before { JSON::JWK::Set::Fetcher.debug! }
+      its(:debugging?) { should == true }
+    end
+
+    describe '.debug' do
+      it 'should enable debugging within given block' do
+        JSON::JWK::Set::Fetcher.debug do
+          JSON::JWK::Set::Fetcher.debugging?.should == true
+        end
+        JSON::JWK::Set::Fetcher.debugging?.should == false
+      end
+
+      it 'should not force disable debugging' do
+        JSON::JWK::Set::Fetcher.debug!
+        JSON::JWK::Set::Fetcher.debug do
+          JSON::JWK::Set::Fetcher.debugging?.should == true
+        end
+        JSON::JWK::Set::Fetcher.debugging?.should == true
+      end
+    end
+  end
+
+  describe '.http_client' do
+    context 'with http_config' do
+      before do
+        JSON::JWK::Set::Fetcher.http_config do |config|
+          config.ssl.verify = false
+        end
+      end
+      it 'should configure OpenIDConnect, SWD and Rack::OAuth2\'s http_client' do
+        JSON::JWK::Set::Fetcher.http_client.ssl.verify = false
+      end
+    end
+  end
+
+  describe 'fetching feature' do
+    class CustomCache
+      JWKS_URI = 'https://idp.example.com/jwks'
+
+      def fetch(cache_key, options = {})
+        base_key = "json:jwk:set:#{OpenSSL::Digest::MD5.hexdigest JWKS_URI}"
+        case cache_key
+        when "#{base_key}:known"
+          File.read(File.join(File.dirname(__FILE__), '../../../mock_response/jwks.json'))
+        else
+          yield
+        end
+      end
+
+      def delete(cache_key)
+        # ignore
+      end
+    end
+
+    let(:jwks_uri) { CustomCache::JWKS_URI }
+
+    describe '.cache' do
+      subject { JSON::JWK::Set::Fetcher.cache }
+
+      context 'as default' do
+        it { should be_instance_of JSON::JWK::Set::Fetcher::Cache }
+      end
+
+      context 'when specified' do
+        around do |example|
+          JSON::JWK::Set::Fetcher.cache = CustomCache.new
+          example.run
+          JSON::JWK::Set::Fetcher.cache = JSON::JWK::Set::Fetcher::Cache.new
+        end
+        it { should be_instance_of CustomCache }
+      end
+    end
+
+    describe '.fetch' do
+      subject { JSON::JWK::Set::Fetcher.fetch jwks_uri, kid: kid }
+
+      around do |example|
+        JSON::JWK::Set::Fetcher.cache = CustomCache.new
+        example.run
+        JSON::JWK::Set::Fetcher.cache = JSON::JWK::Set::Fetcher::Cache.new
+      end
+
+      context 'when not cached' do
+        let(:kid) { 'not_cached' }
+
+        it "should request to jwks_uri" do
+          expect do
+            subject
+          end.to request_to jwks_uri
+        end
+
+        context 'when unknown' do
+          let(:kid) { 'unknown' }
+          let(:cache_key) do
+            [
+              'json:jwk:set',
+              OpenSSL::Digest::MD5.hexdigest(jwks_uri),
+              kid
+            ].collect(&:to_s).join(':')
+          end
+
+          it do
+            expect(JSON::JWK::Set::Fetcher.cache).to receive(:delete).with(cache_key, {})
+            expect do
+              mock_json :get, jwks_uri, 'jwks' do
+                subject
+              end
+            end.to raise_error JSON::JWK::Set::KidNotFound
+          end
+        end
+      end
+
+      context 'when cached' do
+        context 'when known' do
+          let(:kid) { 'known' }
+
+          it "should not request to jwks_uri" do
+            expect do
+              subject
+            end.not_to request_to jwks_uri
+          end
+
+          it do
+            should be_instance_of JSON::JWK
+          end
+
+          context 'when auto_detect disabled' do
+            subject { JSON::JWK::Set::Fetcher.fetch jwks_uri, kid: kid, auto_detect: false }
+
+            it do
+              should be_instance_of JSON::JWK::Set
+            end
+          end
+        end
+      end
+
+      describe 'cache options' do
+        let(:kid) { 'known' }
+
+        shared_context :receive_options_as_hash do
+          it do
+            expect_any_instance_of(CustomCache).to receive(:fetch).with(
+              "json:jwk:set:#{OpenSSL::Digest::MD5.hexdigest CustomCache::JWKS_URI}:#{kid}",
+              options
+            ).and_return(
+              File.read(File.join(File.dirname(__FILE__), '../../../mock_response/jwks.json'))
+            )
+            subject
+          end
+        end
+        shared_context :receive_options_as_blank_hash do
+          let(:options) { {} }
+          it_behaves_like :receive_options_as_hash
+        end
+
+        context 'when cache options not given' do
+          context 'with auto_detect' do
+            subject { JSON::JWK::Set::Fetcher.fetch jwks_uri, kid: kid }
+            it_behaves_like :receive_options_as_blank_hash
+          end
+        end
+
+        context 'when cache options given' do
+          let(:options) do
+            {
+              force: true,
+              expires_in: 10.minutes
+            }
+          end
+
+          context 'with auto_detect' do
+            subject { JSON::JWK::Set::Fetcher.fetch jwks_uri, kid: kid, auto_detect: true, **options }
+            it_behaves_like :receive_options_as_hash
+          end
+
+          context 'without auto_detect' do
+            subject { JSON::JWK::Set::Fetcher.fetch jwks_uri, kid: kid, **options }
+            it_behaves_like :receive_options_as_hash
+          end
+
+          context 'when kid & auto_detect are included in the given options' do
+            context 'as hash' do
+              subject { JSON::JWK::Set::Fetcher.fetch jwks_uri, **options.merge(kid: kid, auto_detect: true) }
+              it_behaves_like :receive_options_as_hash
+            end
+
+            context 'as keyward args' do
+              subject { JSON::JWK::Set::Fetcher.fetch jwks_uri, options.merge(kid: kid, auto_detect: true) }
+
+              if Gem.ruby_version >= Gem::Version.create(3.0)
+                it do
+                  expect do
+                    subject
+                  end.to raise_error ArgumentError
+                end
+              else
+                it_behaves_like :receive_options_as_hash
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/json/jwt_spec.rb b/spec/json/jwt_spec.rb
index 75aeeb3..6305133 100644
--- a/spec/json/jwt_spec.rb
+++ b/spec/json/jwt_spec.rb
@@ -19,6 +19,10 @@ describe JSON::JWT do
     'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.'
   end
 
+  its(:version) do
+    JSON::JWT::VERSION.should_not be_blank
+  end
+
   context 'when not signed nor encrypted' do
     it do
       jwt.to_s.should == no_signed
diff --git a/spec/mock_response/jwks.json b/spec/mock_response/jwks.json
new file mode 100644
index 0000000..f0d185f
--- /dev/null
+++ b/spec/mock_response/jwks.json
@@ -0,0 +1,19 @@
+{
+  "keys": [{
+      "kty": "RSA",
+      "kid": "known",
+      "use": "sig",
+      "alg": "RS256",
+      "n": "lxrwmuYSAsTfn-lUu4goZSXBD9ackM9OJuwUVQHmbZo6GW4Fu_auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY-RNwCWdjNfEaY_esUPY3OVMrNDI15Ns13xspWS3q-13kdGv9jHI28P87RvMpjz_JCpQ5IM44oSyRnYtVJO-320SB8E2Bw92pmrenbp67KRUzTEVfGU4-obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd_JhmqX5CAaT9Pgi0J8lU_pcl215oANqjy7Ob-VMhug9eGyxAWVfu_1u6QJKePlE-w",
+      "e": "AQAB"
+    },
+    {
+      "kty": "RSA",
+      "kid": "fh6Bs8C",
+      "use": "sig",
+      "alg": "RS256",
+      "n": "u704gotMSZc6CSSVNCZ1d0S9dZKwO2BVzfdTKYz8wSNm7R_KIufOQf3ru7Pph1FjW6gQ8zgvhnv4IebkGWsZJlodduTC7c0sRb5PZpEyM6PtO8FPHowaracJJsK1f6_rSLstLdWbSDXeSq7vBvDu3Q31RaoV_0YlEzQwPsbCvD45oVy5Vo5oBePUm4cqi6T3cZ-10gr9QJCVwvx7KiQsttp0kUkHM94PlxbG_HAWlEZjvAlxfEDc-_xZQwC6fVjfazs3j1b2DZWsGmBRdx1snO75nM7hpyRRQB4jVejW9TuZDtPtsNadXTr9I5NjxPdIYMORj9XKEh44Z73yfv0gtw",
+      "e": "AQAB"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index fb56f7c..01bd5f6 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -16,3 +16,4 @@ end
 
 require 'helpers/sign_key_fixture_helper'
 require 'helpers/nimbus_spec_helper'
+require 'helpers/webmock_helper'

More details

Full run details

Historical runs