diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 770f18165..000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,10 +0,0 @@ -### Description - -This Pull Request changes/fixes this thing - -### Checklist - -Before the PR can be merged be sure the following are checked: - -- [ ] There are tests for the fix or feature added/changed -- [ ] A description of the changes and a reference to the PR has been added to CHANGELOG.md. More details in the [CONTRIBUTING.md](https://github.com/jwt/ruby-jwt/blob/main/CONTRIBUTING.md) diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml deleted file mode 100644 index 368a08062..000000000 --- a/.github/workflows/deploy_docs.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: GitHub Pages - -on: - push: - branches: - - main - -jobs: - deploy: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ruby - bundler-cache: true - - name: Install yard - run: gem install yard - - name: Install redcarpet - run: gem install redcarpet - - name: Build docs - run: yard - - name: Configure CNAME - run: echo "ruby-jwt.org" > ./doc/CNAME - - name: Deploy - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml deleted file mode 100644 index 3d4d88048..000000000 --- a/.github/workflows/push_gem.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- -"on": - push: - tags: - - v* -name: Push Gem -jobs: - push: - runs-on: ubuntu-latest - permissions: - contents: write - id-token: write - steps: - - uses: rubygems/configure-rubygems-credentials@main - with: - role-to-assume: ${{ secrets.RUBYGEMS_PUSH_ROLE }} - - uses: actions/checkout@v4 - - name: Set remote URL - run: | - # Attribute commits to the last committer on HEAD - git config --global user.email "$(git log -1 --pretty=format:'%ae')" - git config --global user.name "$(git log -1 --pretty=format:'%an')" - git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY" - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - ruby-version: ruby - - name: Release - run: bundle exec rake release - - name: Wait for release to propagate - run: | - gem install rubygems-await - gem_tuple="$(ruby -rbundler/setup -rbundler -e ' - spec = Bundler.definition.specs.find {|s| s.name == ARGV[0] } - raise "No spec for #{ARGV[0]}" unless spec - print [spec.name, spec.version, spec.platform].join(":") - ' "jwt")" - gem await "${gem_tuple}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 7068e6951..000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,114 +0,0 @@ ---- -permissions: read-all -name: test -on: - push: - branches: - - "*" - pull_request: - branches: - - "*" -jobs: - rubocop: - name: RuboCop - timeout-minutes: 30 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ruby - bundler-cache: true - - name: Run RuboCop - run: bundle exec rubocop - test: - name: ${{ matrix.os }} - Ruby ${{ matrix.ruby }} - ${{ matrix.gemfile }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - ruby: - - "2.5" - - "2.6" - - "2.7" - - "3.0" - - "3.1" - - "3.2" - - "3.3" - - "3.4" - gemfile: - - gemfiles/standalone.gemfile - experimental: [false] - include: - - os: ubuntu-latest - ruby: "2.5" - gemfile: gemfiles/openssl.gemfile - experimental: false - - os: ubuntu-latest - ruby: "truffleruby-head" - gemfile: "gemfiles/standalone.gemfile" - experimental: true - - os: ubuntu-latest - ruby: head - gemfile: gemfiles/standalone.gemfile - experimental: true - continue-on-error: ${{ matrix.experimental }} - env: - BUNDLE_GEMFILE: ${{ matrix.gemfile }} - steps: - - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true - - - name: Run tests - run: bundle exec rspec - - - name: Sanitize gemfile path - run: echo "SANITIZED_GEMFILE=${{ matrix.gemfile }}" | tr '/' '-' >> $GITHUB_ENV - - - name: Upload test coverage folder for later reporting - uses: actions/upload-artifact@v4 - with: - name: coverage-${{ matrix.os }}-${{ matrix.ruby }}-${{ env.SANITIZED_GEMFILE }} - path: coverage/*.json - retention-days: 1 - - coverage: - name: Report coverage to Qlty - runs-on: ubuntu-latest - needs: test - if: success() - steps: - - uses: actions/checkout@v4 - - - name: Download coverage reports from the test job - uses: actions/download-artifact@v4 - - - uses: qltysh/qlty-action/coverage@v1 - with: - token: ${{ secrets.QLTY_COVERAGE_TOKEN }} - files: coverage-*/*.json - - smoke: - name: Built GEM smoke test - timeout-minutes: 30 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ruby - - name: Build GEM - run: gem build - - name: Install built GEM - run: gem install jwt-*.gem - - name: Run test - run: bin/smoke.rb diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 91d73925f..000000000 --- a/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -.idea/ -jwt.gemspec -pkg -Gemfile.lock -coverage/ -.DS_Store -.rbenv-gemsets -.ruby-version -.vscode/ -.bundle -*gemfile.lock -.byebug_history -*.gem -doc/ -.yardoc/ diff --git a/.markdownlint.json b/.markdownlint.json deleted file mode 100644 index 67d2ae55b..000000000 --- a/.markdownlint.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "MD013": false -} diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/.qlty/.gitignore b/.qlty/.gitignore deleted file mode 100644 index 30366188d..000000000 --- a/.qlty/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -* -!configs -!configs/** -!hooks -!hooks/** -!qlty.toml -!.gitignore diff --git a/.qlty/configs/.yamllint.yaml b/.qlty/configs/.yamllint.yaml deleted file mode 100644 index d22fa7799..000000000 --- a/.qlty/configs/.yamllint.yaml +++ /dev/null @@ -1,8 +0,0 @@ -rules: - document-start: disable - quoted-strings: - required: only-when-needed - extra-allowed: ["{|}"] - key-duplicates: {} - octal-values: - forbid-implicit-octal: true diff --git a/.qlty/qlty.toml b/.qlty/qlty.toml deleted file mode 100644 index 2a2cbbdc1..000000000 --- a/.qlty/qlty.toml +++ /dev/null @@ -1,95 +0,0 @@ -# This file was automatically generated by `qlty init`. -# You can modify it to suit your needs. -# We recommend you to commit this file to your repository. -# -# This configuration is used by both Qlty CLI and Qlty Cloud. -# -# Qlty CLI -- Code quality toolkit for developers -# Qlty Cloud -- Fully automated Code Health Platform -# -# Try Qlty Cloud: https://qlty.sh -# -# For a guide to configuration, visit https://qlty.sh/d/config -# Or for a full reference, visit https://qlty.sh/d/qlty-toml -config_version = "0" - -exclude_patterns = [ - "*_min.*", - "*-min.*", - "*.min.*", - "**/.yarn/**", - "**/*.d.ts", - "**/assets/**", - "**/bower_components/**", - "**/build/**", - "**/cache/**", - "**/config/**", - "**/db/**", - "**/deps/**", - "**/dist/**", - "**/extern/**", - "**/external/**", - "**/generated/**", - "**/Godeps/**", - "**/gradlew/**", - "**/mvnw/**", - "**/node_modules/**", - "**/protos/**", - "**/seed/**", - "**/target/**", - "**/templates/**", - "**/testdata/**", - "**/vendor/**", -] - -test_patterns = [ - "**/test/**", - "**/spec/**", - "**/*.test.*", - "**/*.spec.*", - "**/*_test.*", - "**/*_spec.*", - "**/test_*.*", - "**/spec_*.*", -] - -[smells] -mode = "comment" - -[[source]] -name = "default" -default = true - - -[[plugin]] -name = "actionlint" - -[[plugin]] -name = "checkov" - -[[plugin]] -name = "markdownlint" -mode = "comment" - -[[plugin]] -name = "prettier" - -[[plugin]] -name = "ripgrep" -mode = "comment" - -[[plugin]] -name = "rubocop" -version = "1.76.1" - -[[plugin]] -name = "trivy" -drivers = [ - "config", -] - -[[plugin]] -name = "trufflehog" - -[[plugin]] -name = "yamllint" diff --git a/.rspec b/.rspec deleted file mode 100644 index 3687797e5..000000000 --- a/.rspec +++ /dev/null @@ -1,2 +0,0 @@ ---require spec_helper ---color diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index 8093a01d4..000000000 --- a/.rubocop.yml +++ /dev/null @@ -1,27 +0,0 @@ -AllCops: - TargetRubyVersion: 2.5 - NewCops: enable - SuggestExtensions: false - Exclude: - - gemfiles/*.gemfile - - vendor/**/* - -Metrics/AbcSize: - Max: 25 - -Metrics/MethodLength: - Max: 18 - -Metrics/BlockLength: - Exclude: - - spec/**/*_spec.rb - - "*.gemspec" - -Layout/LineLength: - Enabled: false - -Gemspec/DevelopmentDependencies: - EnforcedStyle: gemspec - -Naming/PredicateMethod: - Enabled: false diff --git a/.simplecov b/.simplecov deleted file mode 100644 index 021210c6f..000000000 --- a/.simplecov +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require 'openssl' -require 'simplecov_json_formatter' - -SimpleCov.start do - command_name "Job #{File.basename(ENV['BUNDLE_GEMFILE'])}" if ENV['BUNDLE_GEMFILE'] - project_name 'Ruby JWT - Ruby JSON Web Token implementation' - add_filter 'spec' -end - -SimpleCov.formatters = SimpleCov::Formatter::JSONFormatter if ENV['CI'] diff --git a/.yardopts b/.yardopts deleted file mode 100644 index 40f8b42fa..000000000 --- a/.yardopts +++ /dev/null @@ -1,8 +0,0 @@ ---protected ---no-private -- -README.md -CHANGELOG.md -CONTRIBUTING.md -UPGRADING.md -LICENSE diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index c0be88c30..000000000 --- a/AUTHORS +++ /dev/null @@ -1,119 +0,0 @@ -Tim Rudat -Joakim Antman -Jeff Lindsay -A.B -shields -Bob Aman -Emilio Cristalli -Egon Zemmer -Zane Shannon -Nikita Shatov -Paul Battley -Oliver -blackanger -Ville Lautanala -Tyler Pickett -James Stonehill -Adam Michael -Martin Emde -Saverio Trioni -Peter M. Goldstein -Korstiaan de Ridder -Richard Larocque -Andrew Davis -Yason Khaburzaniya -Klaas Jan Wierenga -Nick Hammond -Bart de Water -Steve Sloan -Antonis Berkakis -Bill Mill -Kevin Olbrich -Simon Fish -jb08 -lukas -Rodrigo López Dato -ojab -Ritikesh -sawyerzhang -Larry Lv -smudge -wohlgejm -Tom Wey -yann ARMAND -Brian Flethcer -Jurriaan Pruis -Erik Michaels-Ober -Matthew Simpson -Steven Davidovitz -Nicolas Leger -Pierre Michard -RahulBajaj -Rob Wygand -Ryan Brushett -Ryan McIlmoyl -Ryan Metzler -Severin Schoepke -Shaun Guth -Steve Teti -T.J. Schuck -Taiki Sugawara -Takehiro Adachi -Tobias Haar -Toby Pinder -Tomé Duarte -Travis Hunter -Yuji Yaginuma -Zuzanna Stolińska -aarongray -danielgrippi -fusagiko/takayamaki -mai fujii -nycvotes-dev -revodoge -rono23 -antonmorant -Adam Greene -Alexander Boyd -Alexandr Kostrikov -Aman Gupta -Ariel Salomon -Arnaud Mesureur -Artsiom Kuts -Austin Kabiru -B -Bouke van der Bijl -Brandon Keepers -Dan Leyden -Dave Grijalva -Dmitry Pashkevich -Dorian Marié -Ernie Miller -Evgeni Golov -Ewoud Kohl van Wijngaarden -HoneyryderChuck -Igor Victor -Ilyaaaaaaaaaaaaa Zhitomirskiy -Jens Hausherr -Jeremiah Wuenschel -John Downey -Jordan Brough -Josh Bodah -JotaSe -Juanito Fatas -Julio Lopez -Katelyn Kasperowicz -Leonardo Saraiva -Lowell Kirsh -Loïc Lengrand -Lucas Mazza -Makoto Chiba -Manuel Bustillo -Marco Adkins -Meredith Leu -Micah Gates -Michał Begejowicz -Mike Eirih -Mike Pastore -Mingan -Mitch Birti diff --git a/Appraisals b/Appraisals deleted file mode 100644 index ca5f125da..000000000 --- a/Appraisals +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -appraise 'standalone' do - remove_gem 'rubocop' -end - -appraise 'openssl' do - gem 'openssl', '~> 2.1' - remove_gem 'rubocop' -end diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index bb8b0ead7..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,1004 +0,0 @@ -# Changelog - -## [v3.1.3](https://github.com/jwt/ruby-jwt/tree/v3.1.3) (NEXT) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.1.2...v3.1.3) - -**Features:** - -- Your contribution here - -**Fixes and enhancements:** - -- Fix compatibility with the openssl 4.0 gem [#706](https://github.com/jwt/ruby-jwt/pull/706) -- Your contribution here - -## [v3.1.2](https://github.com/jwt/ruby-jwt/tree/v3.1.2) (2025-06-28) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.1.1...v3.1.2) - -**Fixes and enhancements:** - -- Avoid using the same digest across calls in JWT::JWA::Ecdsa and JWT::JWA::Rsa [#697](https://github.com/jwt/ruby-jwt/pull/697) -- Fix signing with a EC JWK [#699](https://github.com/jwt/ruby-jwt/pull/699) ([@anakinj](https://github.com/anakinj)) - -## [v3.1.1](https://github.com/jwt/ruby-jwt/tree/v3.1.1) (2025-06-24) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.1.0...v3.1.1) - -**Fixes and enhancements:** - -- Require the algorithm to be provided when signing and verifying tokens using JWKs [#695](https://github.com/jwt/ruby-jwt/pull/695) ([@anakinj](https://github.com/anakinj)) - -## [v3.1.0](https://github.com/jwt/ruby-jwt/tree/v3.1.0) (2025-06-23) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.0.0...v3.1.0) - -**Features:** - -- Add support for x5t header parameter for X.509 certificate thumbprint verification [#669](https://github.com/jwt/ruby-jwt/pull/669) ([@hieuk09](https://github.com/hieuk09)) -- Raise an error if the ECDSA signing or verification key is not an instance of `OpenSSL::PKey::EC` [#688](https://github.com/jwt/ruby-jwt/pull/688) ([@anakinj](https://github.com/anakinj)) -- Allow `OpenSSL::PKey::EC::Point` to be used as the verification key in ECDSA [#689](https://github.com/jwt/ruby-jwt/pull/689) ([@anakinj](https://github.com/anakinj)) -- Require claims to have been verified before accessing the `JWT::EncodedToken#payload` [#690](https://github.com/jwt/ruby-jwt/pull/690) ([@anakinj](https://github.com/anakinj)) -- Support signing and verifying tokens using a JWK [#692](https://github.com/jwt/ruby-jwt/pull/692) ([@anakinj](https://github.com/anakinj)) - -## [v3.0.0](https://github.com/jwt/ruby-jwt/tree/v3.0.0) (2025-06-14) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.10.1...v3.0.0) - -**Breaking changes:** - -- Require token signature to be verified before accessing payload [#648](https://github.com/jwt/ruby-jwt/pull/648) ([@anakinj](https://github.com/anakinj)) -- Drop support for the HS512256 algorithm [#650](https://github.com/jwt/ruby-jwt/pull/650) ([@anakinj](https://github.com/anakinj)) -- Remove deprecated claim verification methods [#654](https://github.com/jwt/ruby-jwt/pull/654) ([@anakinj](https://github.com/anakinj)) -- Remove dependency to rbnacl [#655](https://github.com/jwt/ruby-jwt/pull/655) ([@anakinj](https://github.com/anakinj)) -- Support only stricter base64 decoding (RFC 4648) [#658](https://github.com/jwt/ruby-jwt/pull/658) ([@anakinj](https://github.com/anakinj)) -- Custom algorithms are required to include `JWT::JWA::SigningAlgorithm` [#660](https://github.com/jwt/ruby-jwt/pull/660) ([@anakinj](https://github.com/anakinj)) -- Require RSA keys to be at least 2048 bits [#661](https://github.com/jwt/ruby-jwt/pull/661) ([@anakinj](https://github.com/anakinj)) -- Base64 encode and decode the k value for HMAC JWKs [#662](https://github.com/jwt/ruby-jwt/pull/662) ([@anakinj](https://github.com/anakinj)) - -Take a look at the [upgrade guide](UPGRADING.md) for more details. - -**Features:** - -- JWT::EncodedToken#verify! method that bundles signature and claim validation [#647](https://github.com/jwt/ruby-jwt/pull/647) ([@anakinj](https://github.com/anakinj)) -- Do not override the alg header if already given [#659](https://github.com/jwt/ruby-jwt/pull/659) ([@anakinj](https://github.com/anakinj)) -- Make `JWK::KeyFinder` compatible with `JWT::EncodedToken` [#663](https://github.com/jwt/ruby-jwt/pull/663) ([@anakinj](https://github.com/anakinj)) - -**Fixes and enhancements:** - -- Ruby 3.4 to CI matrix [#649](https://github.com/jwt/ruby-jwt/pull/649) ([@anakinj](https://github.com/anakinj)) -- Add logger as development dependency [#670](https://github.com/jwt/ruby-jwt/pull/670) ([@hieuk09](https://github.com/hieuk09)) - -## [v2.10.1](https://github.com/jwt/ruby-jwt/tree/v2.10.1) (2024-12-26) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.10.0...v2.10.1) - -**Fixes and enhancements:** - -- Make version constants public again [#646](https://github.com/jwt/ruby-jwt/pull/646) ([@anakinj](https://github.com/anakinj)) - -## [v2.10.0](https://github.com/jwt/ruby-jwt/tree/v2.10.0) (2024-12-25) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.9.3...v2.10.0) - -**Features:** - -- JWT::Token and JWT::EncodedToken for signing and verifying tokens [#621](https://github.com/jwt/ruby-jwt/pull/621) ([@anakinj](https://github.com/anakinj)) -- Detached payload support for JWT::Token and JWT::EncodedToken [#630](https://github.com/jwt/ruby-jwt/pull/630) ([@anakinj](https://github.com/anakinj)) -- Skip decoding payload if b64 header is present and false [#631](https://github.com/jwt/ruby-jwt/pull/631) ([@anakinj](https://github.com/anakinj)) -- Remove a few custom Rubocop configs [#638](https://github.com/jwt/ruby-jwt/pull/638) ([@anakinj](https://github.com/anakinj)) - -**Fixes and enhancements:** - -- Deprecation warnings for deprecated methods and classes [#629](https://github.com/jwt/ruby-jwt/pull/629) ([@anakinj](https://github.com/anakinj)) -- Improved documentation for public apis [#629](https://github.com/jwt/ruby-jwt/pull/629) ([@anakinj](https://github.com/anakinj)) -- Use correct methods when raising error during signing/verification with EdDSA [#633](https://github.com/jwt/ruby-jwt/pull/633) -- Fix JWT::EncodedToken behavior with empty string as token [#640](https://github.com/jwt/ruby-jwt/pull/640) ([@ragalie](https://github.com/ragalie)) -- Deprecation warnings for rbnacl backed functionality [#641](https://github.com/jwt/ruby-jwt/pull/641) ([@anakinj](https://github.com/anakinj)) - -## [v2.9.3](https://github.com/jwt/ruby-jwt/tree/v2.9.3) (2024-10-03) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.9.2...v2.9.3) - -**Fixes and enhancements:** - -- Return truthy value for `::JWT::ClaimsValidator#validate!` and `::JWT::Verify.verify_claims` [#628](https://github.com/jwt/ruby-jwt/pull/628) ([@anakinj](https://github.com/anakinj)) - -## [v2.9.2](https://github.com/jwt/ruby-jwt/tree/v2.9.2) (2024-10-03) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.9.1...v2.9.2) - -**Features:** - -- Standalone claim verification interface [#626](https://github.com/jwt/ruby-jwt/pull/626) ([@anakinj](https://github.com/anakinj)) - -**Fixes and enhancements:** - -- Updated README to correctly document `OpenSSL::HMAC` documentation [#617](https://github.com/jwt/ruby-jwt/pull/617) ([@aedryan](https://github.com/aedryan)) -- Verify JWT header format [#622](https://github.com/jwt/ruby-jwt/pull/622) ([@304](https://github.com/304)) -- Bring back `::JWT::ClaimsValidator`, `::JWT::Verify` and a few other removed interfaces for preserved backwards compatibility [#624](https://github.com/jwt/ruby-jwt/pull/624) ([@anakinj](https://github.com/anakinj)) - -## [v2.9.1](https://github.com/jwt/ruby-jwt/tree/v2.9.1) (2024-09-23) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.9.0...v2.9.1) - -**Fixes and enhancements:** - -- Fix regression in `iss` and `aud` claim validation [#619](https://github.com/jwt/ruby-jwt/pull/619) ([@anakinj](https://github.com/anakinj)) - -## [v2.9.0](https://github.com/jwt/ruby-jwt/tree/v2.9.0) (2024-09-15) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.2...v2.9.0) - -**Features:** - -- Build and push gem using a GH action [#612](https://github.com/jwt/ruby-jwt/pull/612) ([@anakinj](https://github.com/anakinj)) - -**Fixes and enhancements:** - -- Refactor claim validators into their own classes [#605](https://github.com/jwt/ruby-jwt/pull/605) ([@anakinj](https://github.com/anakinj), [@MatteoPierro](https://github.com/MatteoPierro)) -- Allow extending available algorithms [#607](https://github.com/jwt/ruby-jwt/pull/607) ([@anakinj](https://github.com/anakinj)) -- Do not include the EdDSA algorithm if rbnacl not available [#613](https://github.com/jwt/ruby-jwt/pull/613) ([@anakinj](https://github.com/anakinj)) - -## [v2.8.2](https://github.com/jwt/ruby-jwt/tree/v2.8.2) (2024-06-18) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.1...v2.8.2) - -**Fixes and enhancements:** - -- Print deprecation warnings only on when token decoding succeeds [#600](https://github.com/jwt/ruby-jwt/pull/600) ([@anakinj](https://github.com/anakinj)) -- Unify code style [#602](https://github.com/jwt/ruby-jwt/pull/602) ([@anakinj](https://github.com/anakinj)) - -## [v2.8.1](https://github.com/jwt/ruby-jwt/tree/v2.8.1) (2024-02-29) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.0...v2.8.1) - -**Features:** - -- Configurable base64 decode behaviour [#589](https://github.com/jwt/ruby-jwt/pull/589) ([@anakinj](https://github.com/anakinj)) - -**Fixes and enhancements:** - -- Output deprecation warnings once [#589](https://github.com/jwt/ruby-jwt/pull/589) ([@anakinj](https://github.com/anakinj)) - -## [v2.8.0](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2024-02-17) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.7.1...v2.8.0) - -**Features:** - -- Updated rubocop to 1.56 [#573](https://github.com/jwt/ruby-jwt/pull/573) ([@anakinj](https://github.com/anakinj)) -- Run CI on Ruby 3.3 [#577](https://github.com/jwt/ruby-jwt/pull/577) ([@anakinj](https://github.com/anakinj)) -- Deprecation warning added for the HMAC algorithm HS512256 (HMAC-SHA-512 truncated to 256-bits) [#575](https://github.com/jwt/ruby-jwt/pull/575) ([@anakinj](https://github.com/anakinj)) -- Stop using RbNaCl for standard HMAC algorithms [#575](https://github.com/jwt/ruby-jwt/pull/575) ([@anakinj](https://github.com/anakinj)) - -**Fixes and enhancements:** - -- Fix signature has expired error if payload is a string [#555](https://github.com/jwt/ruby-jwt/pull/555) ([@GobinathAL](https://github.com/GobinathAL)) -- Fix key base equality and spaceship operators [#569](https://github.com/jwt/ruby-jwt/pull/569) ([@magneland](https://github.com/magneland)) -- Remove explicit base64 require from x5c_key_finder [#580](https://github.com/jwt/ruby-jwt/pull/580) ([@anakinj](https://github.com/anakinj)) -- Performance improvements and cleanup of tests [#581](https://github.com/jwt/ruby-jwt/pull/581) ([@anakinj](https://github.com/anakinj)) -- Repair EC x/y coordinates when importing JWK [#585](https://github.com/jwt/ruby-jwt/pull/585) ([@julik](https://github.com/julik)) -- Explicit dependency to the base64 gem [#582](https://github.com/jwt/ruby-jwt/pull/582) ([@anakinj](https://github.com/anakinj)) -- Deprecation warning for decoding content not compliant with RFC 4648 [#582](https://github.com/jwt/ruby-jwt/pull/582) ([@anakinj](https://github.com/anakinj)) -- Algorithms moved under the `::JWT::JWA` module ([@anakinj](https://github.com/anakinj)) - -## [v2.7.1](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2023-06-09) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.7.0...v2.7.1) - -**Fixes and enhancements:** - -- Handle invalid algorithm when decoding JWT [#559](https://github.com/jwt/ruby-jwt/pull/559) ([@nataliastanko](https://github.com/nataliastanko)) -- Do not raise error when verifying bad HMAC signature [#563](https://github.com/jwt/ruby-jwt/pull/563) ([@hieuk09](https://github.com/hieuk09)) - -## [v2.7.0](https://github.com/jwt/ruby-jwt/tree/v2.7.0) (2023-02-01) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.6.0...v2.7.0) - -**Features:** - -- Support OKP (Ed25519) keys for JWKs [#540](https://github.com/jwt/ruby-jwt/pull/540) ([@anakinj](https://github.com/anakinj)) -- JWK Sets can now be used for tokens with nil kid [#543](https://github.com/jwt/ruby-jwt/pull/543) ([@bellebaum](https://github.com/bellebaum)) - -**Fixes and enhancements:** - -- Fix issue with multiple keys returned by keyfinder and multiple allowed algorithms [#545](https://github.com/jwt/ruby-jwt/pull/545) ([@mpospelov](https://github.com/mpospelov)) -- Non-string `kid` header values are now rejected [#543](https://github.com/jwt/ruby-jwt/pull/543) ([@bellebaum](https://github.com/bellebaum)) - -## [v2.6.0](https://github.com/jwt/ruby-jwt/tree/v2.6.0) (2022-12-22) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.5.0...v2.6.0) - -**Features:** - -- Support custom algorithms by passing algorithm objects [#512](https://github.com/jwt/ruby-jwt/pull/512) ([@anakinj](https://github.com/anakinj)) -- Support descriptive (not key related) JWK parameters [#520](https://github.com/jwt/ruby-jwt/pull/520) ([@bellebaum](https://github.com/bellebaum)) -- Support for JSON Web Key Sets [#525](https://github.com/jwt/ruby-jwt/pull/525) ([@bellebaum](https://github.com/bellebaum)) -- Support HMAC keys over 32 chars when using RbNaCl [#521](https://github.com/jwt/ruby-jwt/pull/521) ([@anakinj](https://github.com/anakinj)) - -**Fixes and enhancements:** - -- Raise descriptive error on empty hmac_secret and OpenSSL 3.0/openssl gem <3.0.1 [#530](https://github.com/jwt/ruby-jwt/pull/530) ([@jonmchan](https://github.com/jonmchan)) - -## [v2.5.0](https://github.com/jwt/ruby-jwt/tree/v2.5.0) (2022-08-25) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.4.1...v2.5.0) - -**Features:** - -- Support JWK thumbprints as key ids [#481](https://github.com/jwt/ruby-jwt/pull/481) ([@anakinj](https://github.com/anakinj)) -- Support OpenSSL >= 3.0 [#496](https://github.com/jwt/ruby-jwt/pull/496) ([@anakinj](https://github.com/anakinj)) - -**Fixes and enhancements:** - -- Bring back the old Base64 (RFC2045) deocode mechanisms [#488](https://github.com/jwt/ruby-jwt/pull/488) ([@anakinj](https://github.com/anakinj)) -- Rescue RbNaCl exception for EdDSA wrong key [#491](https://github.com/jwt/ruby-jwt/pull/491) ([@n-studio](https://github.com/n-studio)) -- 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)) -- 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)) - -## [v2.4.1](https://github.com/jwt/ruby-jwt/tree/v2.4.1) (2022-06-07) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.4.0...v2.4.1) - -**Fixes and enhancements:** - -- Raise JWT::DecodeError on invalid signature [\#484](https://github.com/jwt/ruby-jwt/pull/484) ([@freakyfelt!](https://github.com/freakyfelt!)) - -## [v2.4.0](https://github.com/jwt/ruby-jwt/tree/v2.4.0) (2022-06-06) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.3.0...v2.4.0) - -**Features:** - -- Dropped support for Ruby 2.5 and older [#453](https://github.com/jwt/ruby-jwt/pull/453) - ([@anakinj](https://github.com/anakinj)) -- Use Ruby built-in url-safe base64 methods [#454](https://github.com/jwt/ruby-jwt/pull/454) - ([@bdewater](https://github.com/bdewater)) -- Updated rubocop to 1.23.0 [#457](https://github.com/jwt/ruby-jwt/pull/457) - ([@anakinj](https://github.com/anakinj)) -- Add x5c header key finder [#338](https://github.com/jwt/ruby-jwt/pull/338) - ([@bdewater](https://github.com/bdewater)) -- Author driven changelog process [#463](https://github.com/jwt/ruby-jwt/pull/463) - ([@anakinj](https://github.com/anakinj)) -- Allow regular expressions and procs to verify issuer [\#437](https://github.com/jwt/ruby-jwt/pull/437) ([rewritten](https://github.com/rewritten)) -- Add Support to be able to verify from multiple keys [\#425](https://github.com/jwt/ruby-jwt/pull/425) ([ritikesh](https://github.com/ritikesh)) - -**Fixes and enhancements:** - -- Readme: Typo fix re MissingRequiredClaim [\#451](https://github.com/jwt/ruby-jwt/pull/451) ([antonmorant](https://github.com/antonmorant)) -- Fix RuboCop TODOs [\#476](https://github.com/jwt/ruby-jwt/pull/476) ([typhoon2099](https://github.com/typhoon2099)) -- Make specific algorithms in README linkable [\#472](https://github.com/jwt/ruby-jwt/pull/472) ([milieu](https://github.com/milieu)) -- Update note about supported JWK types [\#475](https://github.com/jwt/ruby-jwt/pull/475) ([dpashkevich](https://github.com/dpashkevich)) -- Create CODE_OF_CONDUCT.md [\#449](https://github.com/jwt/ruby-jwt/pull/449) ([loic5](https://github.com/loic5)) - -## [v2.3.0](https://github.com/jwt/ruby-jwt/tree/v2.3.0) (2021-10-03) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.2.3...v2.3.0) - -**Closed issues:** - -- \[SECURITY\] Algorithm Confusion Through kid Header [\#440](https://github.com/jwt/ruby-jwt/issues/440) -- JWT to memory [\#436](https://github.com/jwt/ruby-jwt/issues/436) -- ArgumentError: wrong number of arguments \(given 2, expected 1\) [\#429](https://github.com/jwt/ruby-jwt/issues/429) -- HMAC section of README outdated [\#421](https://github.com/jwt/ruby-jwt/issues/421) -- NoMethodError: undefined method `zero?' for nil:NilClass if JWT has no 'alg' field [\#410](https://github.com/jwt/ruby-jwt/issues/410) -- Release new version [\#409](https://github.com/jwt/ruby-jwt/issues/409) -- NameError: uninitialized constant JWT::JWK [\#403](https://github.com/jwt/ruby-jwt/issues/403) - -**Merged pull requests:** - -- Release 2.3.0 [\#448](https://github.com/jwt/ruby-jwt/pull/448) ([excpt](https://github.com/excpt)) -- Fix Style/MultilineIfModifier issues [\#447](https://github.com/jwt/ruby-jwt/pull/447) ([anakinj](https://github.com/anakinj)) -- feat\(EdDSA\): Accept EdDSA as algorithm header [\#446](https://github.com/jwt/ruby-jwt/pull/446) ([Pierre-Michard](https://github.com/Pierre-Michard)) -- Pass kid param through JWT::JWK.create_from [\#445](https://github.com/jwt/ruby-jwt/pull/445) ([shaun-guth-allscripts](https://github.com/shaun-guth-allscripts)) -- fix document about passing JWKs as a simple Hash [\#443](https://github.com/jwt/ruby-jwt/pull/443) ([takayamaki](https://github.com/takayamaki)) -- Tests for mixing JWK keys with mismatching algorithms [\#441](https://github.com/jwt/ruby-jwt/pull/441) ([anakinj](https://github.com/anakinj)) -- verify_claims test shouldnt be within the verify_sub test [\#431](https://github.com/jwt/ruby-jwt/pull/431) ([andyjdavis](https://github.com/andyjdavis)) -- Allow decode options to specify required claims [\#430](https://github.com/jwt/ruby-jwt/pull/430) ([andyjdavis](https://github.com/andyjdavis)) -- Fix OpenSSL::PKey::EC public_key handing in tests [\#427](https://github.com/jwt/ruby-jwt/pull/427) ([anakinj](https://github.com/anakinj)) -- Add documentation for find_key [\#426](https://github.com/jwt/ruby-jwt/pull/426) ([ritikesh](https://github.com/ritikesh)) -- Give ruby 3.0 as a string to avoid number formatting issues [\#424](https://github.com/jwt/ruby-jwt/pull/424) ([anakinj](https://github.com/anakinj)) -- Tests for iat verification behaviour [\#423](https://github.com/jwt/ruby-jwt/pull/423) ([anakinj](https://github.com/anakinj)) -- Remove HMAC with nil secret from documentation [\#422](https://github.com/jwt/ruby-jwt/pull/422) ([boardfish](https://github.com/boardfish)) -- Update broken link in README [\#420](https://github.com/jwt/ruby-jwt/pull/420) ([severin](https://github.com/severin)) -- Add metadata for RubyGems [\#418](https://github.com/jwt/ruby-jwt/pull/418) ([nickhammond](https://github.com/nickhammond)) -- Fixed a typo about class name [\#417](https://github.com/jwt/ruby-jwt/pull/417) ([mai-f](https://github.com/mai-f)) -- Fix references for v2.2.3 on CHANGELOG [\#416](https://github.com/jwt/ruby-jwt/pull/416) ([vyper](https://github.com/vyper)) -- Raise IncorrectAlgorithm if token has no alg header [\#411](https://github.com/jwt/ruby-jwt/pull/411) ([bouk](https://github.com/bouk)) - -## [v2.2.3](https://github.com/jwt/ruby-jwt/tree/v2.2.3) (2021-04-19) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.2.2...v2.2.3) - -**Implemented enhancements:** - -- Verify algorithm before evaluating keyfinder [\#343](https://github.com/jwt/ruby-jwt/issues/343) -- Why jwt depends on json \< 2.0 ? [\#179](https://github.com/jwt/ruby-jwt/issues/179) -- Support for JWK in-lieu of rsa_public [\#158](https://github.com/jwt/ruby-jwt/issues/158) -- Fix rspec `raise_error` warning [\#413](https://github.com/jwt/ruby-jwt/pull/413) ([excpt](https://github.com/excpt)) -- Add support for JWKs with HMAC key type. [\#372](https://github.com/jwt/ruby-jwt/pull/372) ([phlegx](https://github.com/phlegx)) -- Improve 'none' algorithm handling [\#365](https://github.com/jwt/ruby-jwt/pull/365) ([danleyden](https://github.com/danleyden)) -- Handle parsed JSON JWKS input with string keys [\#348](https://github.com/jwt/ruby-jwt/pull/348) ([martinemde](https://github.com/martinemde)) -- Allow Numeric values during encoding [\#327](https://github.com/jwt/ruby-jwt/pull/327) ([fanfilmu](https://github.com/fanfilmu)) - -**Closed issues:** - -- "Signature verification raised", yet jwt.io says "Signature Verified" [\#401](https://github.com/jwt/ruby-jwt/issues/401) -- truffleruby-head build is failing [\#396](https://github.com/jwt/ruby-jwt/issues/396) -- JWT::JWK::EC needs `require 'forwardable'` [\#392](https://github.com/jwt/ruby-jwt/issues/392) -- How to use a 'signing key' as used by next-auth [\#389](https://github.com/jwt/ruby-jwt/issues/389) -- undefined method `verify' for nil:NilClass when validate a JWT with JWK [\#383](https://github.com/jwt/ruby-jwt/issues/383) -- Make specifying "algorithm" optional on decode [\#380](https://github.com/jwt/ruby-jwt/issues/380) -- ADFS created access tokens can't be validated due to missing 'kid' header [\#370](https://github.com/jwt/ruby-jwt/issues/370) -- new version? [\#355](https://github.com/jwt/ruby-jwt/issues/355) -- JWT gitlab OmniAuth provider setup support [\#354](https://github.com/jwt/ruby-jwt/issues/354) -- Release with support for RSA.import for ruby \< 2.4 hasn't been released [\#347](https://github.com/jwt/ruby-jwt/issues/347) -- cannot load such file -- jwt [\#339](https://github.com/jwt/ruby-jwt/issues/339) - -**Merged pull requests:** - -- Prepare 2.2.3 release [\#415](https://github.com/jwt/ruby-jwt/pull/415) ([excpt](https://github.com/excpt)) -- Remove codeclimate code coverage dev dependency [\#414](https://github.com/jwt/ruby-jwt/pull/414) ([excpt](https://github.com/excpt)) -- Add forwardable dependency [\#408](https://github.com/jwt/ruby-jwt/pull/408) ([anakinj](https://github.com/anakinj)) -- Ignore casing of algorithm [\#405](https://github.com/jwt/ruby-jwt/pull/405) ([johnnyshields](https://github.com/johnnyshields)) -- Document function and add tests for verify claims method [\#404](https://github.com/jwt/ruby-jwt/pull/404) ([yasonk](https://github.com/yasonk)) -- documenting calling verify_jti callback with 2 arguments in the readme [\#402](https://github.com/jwt/ruby-jwt/pull/402) ([HoneyryderChuck](https://github.com/HoneyryderChuck)) -- Target the master branch on the build status badge [\#399](https://github.com/jwt/ruby-jwt/pull/399) ([anakinj](https://github.com/anakinj)) -- Improving the local development experience [\#397](https://github.com/jwt/ruby-jwt/pull/397) ([anakinj](https://github.com/anakinj)) -- Fix sourcelevel broken links [\#395](https://github.com/jwt/ruby-jwt/pull/395) ([anakinj](https://github.com/anakinj)) -- Don't recommend installing gem with sudo [\#391](https://github.com/jwt/ruby-jwt/pull/391) ([tjschuck](https://github.com/tjschuck)) -- Enable rubocop locally and on ci [\#390](https://github.com/jwt/ruby-jwt/pull/390) ([anakinj](https://github.com/anakinj)) -- Ci and test cleanup [\#387](https://github.com/jwt/ruby-jwt/pull/387) ([anakinj](https://github.com/anakinj)) -- Make JWT::JWK::EC compatible with Ruby 2.3 [\#386](https://github.com/jwt/ruby-jwt/pull/386) ([anakinj](https://github.com/anakinj)) -- Support JWKs for pre 2.3 rubies [\#382](https://github.com/jwt/ruby-jwt/pull/382) ([anakinj](https://github.com/anakinj)) -- Replace Travis CI with GitHub Actions \(also favor openssl/rbnacl combinations over rails compatibility tests\) [\#381](https://github.com/jwt/ruby-jwt/pull/381) ([anakinj](https://github.com/anakinj)) -- Add auth0 sponsor message [\#379](https://github.com/jwt/ruby-jwt/pull/379) ([excpt](https://github.com/excpt)) -- Adapt HMAC to JWK RSA code style. [\#378](https://github.com/jwt/ruby-jwt/pull/378) ([phlegx](https://github.com/phlegx)) -- Disable Rails cops [\#376](https://github.com/jwt/ruby-jwt/pull/376) ([anakinj](https://github.com/anakinj)) -- Support exporting RSA JWK private keys [\#375](https://github.com/jwt/ruby-jwt/pull/375) ([anakinj](https://github.com/anakinj)) -- Ebert is SourceLevel nowadays [\#374](https://github.com/jwt/ruby-jwt/pull/374) ([anakinj](https://github.com/anakinj)) -- Add support for JWKs with EC key type [\#371](https://github.com/jwt/ruby-jwt/pull/371) ([richardlarocque](https://github.com/richardlarocque)) -- Add Truffleruby head to CI [\#368](https://github.com/jwt/ruby-jwt/pull/368) ([gogainda](https://github.com/gogainda)) -- Add more docs about JWK support [\#341](https://github.com/jwt/ruby-jwt/pull/341) ([take](https://github.com/take)) - -## [v2.2.2](https://github.com/jwt/ruby-jwt/tree/v2.2.2) (2020-08-18) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.2.1...v2.2.2) - -**Implemented enhancements:** - -- JWK does not decode. [\#332](https://github.com/jwt/ruby-jwt/issues/332) -- Inconsistent use of symbol and string keys in args \(exp and algorithm\). [\#331](https://github.com/jwt/ruby-jwt/issues/331) -- Pin simplecov to \< 0.18 [\#356](https://github.com/jwt/ruby-jwt/pull/356) ([anakinj](https://github.com/anakinj)) -- verifies algorithm before evaluating keyfinder [\#346](https://github.com/jwt/ruby-jwt/pull/346) ([jb08](https://github.com/jb08)) -- Update Rails 6 appraisal to use actual release version [\#336](https://github.com/jwt/ruby-jwt/pull/336) ([smudge](https://github.com/smudge)) -- Update Travis [\#326](https://github.com/jwt/ruby-jwt/pull/326) ([berkos](https://github.com/berkos)) -- Improvement/encode hmac without key [\#312](https://github.com/jwt/ruby-jwt/pull/312) ([JotaSe](https://github.com/JotaSe)) - -**Fixed bugs:** - -- v2.2.1 warning: already initialized constant JWT Error [\#335](https://github.com/jwt/ruby-jwt/issues/335) -- 2.2.1 is no longer raising `JWT::DecodeError` on `nil` verification key [\#328](https://github.com/jwt/ruby-jwt/issues/328) -- Fix algorithm picking from decode options [\#359](https://github.com/jwt/ruby-jwt/pull/359) ([excpt](https://github.com/excpt)) -- Raise error when verification key is empty [\#358](https://github.com/jwt/ruby-jwt/pull/358) ([anakinj](https://github.com/anakinj)) - -**Closed issues:** - -- JWT RSA: is it possible to encrypt using the public key? [\#366](https://github.com/jwt/ruby-jwt/issues/366) -- Example unsigned token that bypasses verification [\#364](https://github.com/jwt/ruby-jwt/issues/364) -- Verify exp claim/field even if it's not present [\#363](https://github.com/jwt/ruby-jwt/issues/363) -- Decode any token [\#360](https://github.com/jwt/ruby-jwt/issues/360) -- \[question\] example of using a pub/priv keys for signing? [\#351](https://github.com/jwt/ruby-jwt/issues/351) -- JWT::ExpiredSignature raised for non-JSON payloads [\#350](https://github.com/jwt/ruby-jwt/issues/350) -- verify_aud only verifies that at least one aud is expected [\#345](https://github.com/jwt/ruby-jwt/issues/345) -- Sinatra 4.90s TTFB [\#344](https://github.com/jwt/ruby-jwt/issues/344) -- How to Logout [\#342](https://github.com/jwt/ruby-jwt/issues/342) -- jwt token decoding even when wrong token is provided for some letters [\#337](https://github.com/jwt/ruby-jwt/issues/337) -- Need to use `symbolize_keys` everywhere! [\#330](https://github.com/jwt/ruby-jwt/issues/330) -- eval\(\) used in Forwardable limits usage in iOS App Store [\#324](https://github.com/jwt/ruby-jwt/issues/324) -- HS512256 OpenSSL Exception: First num too large [\#322](https://github.com/jwt/ruby-jwt/issues/322) -- Can we change the separator character? [\#321](https://github.com/jwt/ruby-jwt/issues/321) -- Verifying iat without leeway may break with poorly synced clocks [\#319](https://github.com/jwt/ruby-jwt/issues/319) -- Adding support for 'hd' hosted domain string [\#314](https://github.com/jwt/ruby-jwt/issues/314) -- There is no "typ" header in version 2.0.0 [\#233](https://github.com/jwt/ruby-jwt/issues/233) - -**Merged pull requests:** - -- Release v2.2.2 [\#367](https://github.com/jwt/ruby-jwt/pull/367) ([excpt](https://github.com/excpt)) -- Fix 'already initialized constant JWT Error' [\#357](https://github.com/jwt/ruby-jwt/pull/357) ([excpt](https://github.com/excpt)) -- Support RSA.import for all Ruby versions. [\#333](https://github.com/jwt/ruby-jwt/pull/333) ([rabajaj0509](https://github.com/rabajaj0509)) -- Removed forwardable dependency [\#325](https://github.com/jwt/ruby-jwt/pull/325) ([anakinj](https://github.com/anakinj)) - -## [v2.2.1](https://github.com/jwt/ruby-jwt/tree/v2.2.1) (2019-05-24) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.2.0...v2.2.1) - -**Fixed bugs:** - -- need to `require 'forwardable'` to use `Forwardable` [\#316](https://github.com/jwt/ruby-jwt/issues/316) -- Add forwardable dependency for JWK RSA KeyFinder [\#317](https://github.com/jwt/ruby-jwt/pull/317) ([excpt](https://github.com/excpt)) - -**Merged pull requests:** - -- Release 2.2.1 [\#318](https://github.com/jwt/ruby-jwt/pull/318) ([excpt](https://github.com/excpt)) - -## [v2.2.0](https://github.com/jwt/ruby-jwt/tree/v2.2.0) (2019-05-23) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.2.0.pre.beta.0...v2.2.0) - -**Closed issues:** - -- misspelled es512 curve name [\#310](https://github.com/jwt/ruby-jwt/issues/310) -- With Base64 decode i can read the hashed content [\#306](https://github.com/jwt/ruby-jwt/issues/306) -- hide post-it's for graphviz views [\#303](https://github.com/jwt/ruby-jwt/issues/303) - -**Merged pull requests:** - -- Release 2.2.0 [\#315](https://github.com/jwt/ruby-jwt/pull/315) ([excpt](https://github.com/excpt)) - -## [v2.2.0.pre.beta.0](https://github.com/jwt/ruby-jwt/tree/v2.2.0.pre.beta.0) (2019-03-20) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.1.0...v2.2.0.pre.beta.0) - -**Implemented enhancements:** - -- Use iat_leeway option [\#273](https://github.com/jwt/ruby-jwt/issues/273) -- Use of global state in latest version breaks thread safety of JWT.decode [\#268](https://github.com/jwt/ruby-jwt/issues/268) -- JSON support [\#246](https://github.com/jwt/ruby-jwt/issues/246) -- Change the Github homepage URL to https [\#301](https://github.com/jwt/ruby-jwt/pull/301) ([ekohl](https://github.com/ekohl)) -- Fix Salt length for conformance with PS family specification. [\#300](https://github.com/jwt/ruby-jwt/pull/300) ([tobypinder](https://github.com/tobypinder)) -- Add support for Ruby 2.6 [\#299](https://github.com/jwt/ruby-jwt/pull/299) ([bustikiller](https://github.com/bustikiller)) -- update homepage in gemspec to use HTTPS [\#298](https://github.com/jwt/ruby-jwt/pull/298) ([evgeni](https://github.com/evgeni)) -- Make sure alg parameter value isn't added twice [\#297](https://github.com/jwt/ruby-jwt/pull/297) ([korstiaan](https://github.com/korstiaan)) -- Claims Validation [\#295](https://github.com/jwt/ruby-jwt/pull/295) ([jamesstonehill](https://github.com/jamesstonehill)) -- JWT::Encode refactorings, alg and exp related bugfixes [\#293](https://github.com/jwt/ruby-jwt/pull/293) ([anakinj](https://github.com/anakinj)) -- Proposal of simple JWK support [\#289](https://github.com/jwt/ruby-jwt/pull/289) ([anakinj](https://github.com/anakinj)) -- Add RSASSA-PSS signature signing support [\#285](https://github.com/jwt/ruby-jwt/pull/285) ([oliver-hohn](https://github.com/oliver-hohn)) -- Add note about using a hard coded algorithm in README [\#280](https://github.com/jwt/ruby-jwt/pull/280) ([revodoge](https://github.com/revodoge)) -- Add Appraisal support [\#278](https://github.com/jwt/ruby-jwt/pull/278) ([olbrich](https://github.com/olbrich)) -- Fix decode threading issue [\#269](https://github.com/jwt/ruby-jwt/pull/269) ([ab320012](https://github.com/ab320012)) -- Removed leeway from verify_iat [\#257](https://github.com/jwt/ruby-jwt/pull/257) ([ab320012](https://github.com/ab320012)) - -**Fixed bugs:** - -- Inconsistent handling of payload claim data types [\#282](https://github.com/jwt/ruby-jwt/issues/282) -- Issued at validation [\#247](https://github.com/jwt/ruby-jwt/issues/247) -- Fix bug and simplify segment validation [\#292](https://github.com/jwt/ruby-jwt/pull/292) ([anakinj](https://github.com/anakinj)) - -**Security fixes:** - -- Decoding JWT with ES256 and secp256k1 curve [\#277](https://github.com/jwt/ruby-jwt/issues/277) - -**Closed issues:** - -- RS256, public and private keys [\#291](https://github.com/jwt/ruby-jwt/issues/291) -- Allow passing current time to `decode` [\#288](https://github.com/jwt/ruby-jwt/issues/288) -- Verify exp claim without verifying jwt [\#281](https://github.com/jwt/ruby-jwt/issues/281) -- Audience as an array - how to specify? [\#276](https://github.com/jwt/ruby-jwt/issues/276) -- signature validation using decode method for JWT [\#271](https://github.com/jwt/ruby-jwt/issues/271) -- JWT is easily breakable [\#267](https://github.com/jwt/ruby-jwt/issues/267) -- Ruby JWT Token [\#265](https://github.com/jwt/ruby-jwt/issues/265) -- ECDSA supported algorithms constant is defined as a string, not an array [\#264](https://github.com/jwt/ruby-jwt/issues/264) -- NoMethodError: undefined method `group' for \ [\#261](https://github.com/jwt/ruby-jwt/issues/261) -- 'DecodeError'will replace 'ExpiredSignature' [\#260](https://github.com/jwt/ruby-jwt/issues/260) -- TypeError: no implicit conversion of OpenSSL::PKey::RSA into String [\#259](https://github.com/jwt/ruby-jwt/issues/259) -- NameError: uninitialized constant JWT::Algos::Eddsa::RbNaCl [\#258](https://github.com/jwt/ruby-jwt/issues/258) -- Get new token if current token expired [\#256](https://github.com/jwt/ruby-jwt/issues/256) -- Infer algorithm from header [\#254](https://github.com/jwt/ruby-jwt/issues/254) -- Why is the result of decode is an array? [\#252](https://github.com/jwt/ruby-jwt/issues/252) -- Add support for headless token [\#251](https://github.com/jwt/ruby-jwt/issues/251) -- Leeway or exp_leeway [\#215](https://github.com/jwt/ruby-jwt/issues/215) -- Could you describe purpose of cert fixtures and their cryptokey lengths. [\#185](https://github.com/jwt/ruby-jwt/issues/185) - -**Merged pull requests:** - -- Release v2.2.0-beta.0 [\#302](https://github.com/jwt/ruby-jwt/pull/302) ([excpt](https://github.com/excpt)) -- Misc config improvements [\#296](https://github.com/jwt/ruby-jwt/pull/296) ([jamesstonehill](https://github.com/jamesstonehill)) -- Fix JSON conflict between \#293 and \#292 [\#294](https://github.com/jwt/ruby-jwt/pull/294) ([anakinj](https://github.com/anakinj)) -- Drop Ruby 2.2 from test matrix [\#290](https://github.com/jwt/ruby-jwt/pull/290) ([anakinj](https://github.com/anakinj)) -- Remove broken reek config [\#283](https://github.com/jwt/ruby-jwt/pull/283) ([excpt](https://github.com/excpt)) -- Add missing test, Update common files [\#275](https://github.com/jwt/ruby-jwt/pull/275) ([excpt](https://github.com/excpt)) -- Remove iat_leeway option [\#274](https://github.com/jwt/ruby-jwt/pull/274) ([wohlgejm](https://github.com/wohlgejm)) -- improving code quality of jwt module [\#266](https://github.com/jwt/ruby-jwt/pull/266) ([ab320012](https://github.com/ab320012)) -- fixed ECDSA supported versions const [\#263](https://github.com/jwt/ruby-jwt/pull/263) ([starbeast](https://github.com/starbeast)) -- Added my name to contributor list [\#262](https://github.com/jwt/ruby-jwt/pull/262) ([ab320012](https://github.com/ab320012)) -- Use `Class#new` Shorthand For Error Subclasses [\#255](https://github.com/jwt/ruby-jwt/pull/255) ([akabiru](https://github.com/akabiru)) -- \[CI\] Test against Ruby 2.5 [\#253](https://github.com/jwt/ruby-jwt/pull/253) ([nicolasleger](https://github.com/nicolasleger)) -- Fix README [\#250](https://github.com/jwt/ruby-jwt/pull/250) ([rono23](https://github.com/rono23)) -- Fix link format [\#248](https://github.com/jwt/ruby-jwt/pull/248) ([y-yagi](https://github.com/y-yagi)) - -## [v2.1.0](https://github.com/jwt/ruby-jwt/tree/v2.1.0) (2017-10-06) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.0.0...v2.1.0) - -**Implemented enhancements:** - -- Ed25519 support planned? [\#217](https://github.com/jwt/ruby-jwt/issues/217) -- Verify JTI Proc [\#207](https://github.com/jwt/ruby-jwt/issues/207) -- Allow a list of algorithms for decode [\#241](https://github.com/jwt/ruby-jwt/pull/241) ([lautis](https://github.com/lautis)) -- verify takes 2 params, second being payload closes: \#207 [\#238](https://github.com/jwt/ruby-jwt/pull/238) ([ab320012](https://github.com/ab320012)) -- simplified logic for keyfinder [\#237](https://github.com/jwt/ruby-jwt/pull/237) ([ab320012](https://github.com/ab320012)) -- Show backtrace if rbnacl-libsodium not loaded [\#231](https://github.com/jwt/ruby-jwt/pull/231) ([buzztaiki](https://github.com/buzztaiki)) -- Support for ED25519 [\#229](https://github.com/jwt/ruby-jwt/pull/229) ([ab320012](https://github.com/ab320012)) - -**Fixed bugs:** - -- JWT.encode failing on encode for string [\#235](https://github.com/jwt/ruby-jwt/issues/235) -- The README says it uses an algorithm by default [\#226](https://github.com/jwt/ruby-jwt/issues/226) -- Fix string payload issue [\#236](https://github.com/jwt/ruby-jwt/pull/236) ([excpt](https://github.com/excpt)) - -**Security fixes:** - -- Add HS256 algorithm to decode default options [\#228](https://github.com/jwt/ruby-jwt/pull/228) ([marcoadkins](https://github.com/marcoadkins)) - -**Closed issues:** - -- Change from 1.5.6 to 2.0.0 and appears a "Completed 401 Unauthorized" [\#240](https://github.com/jwt/ruby-jwt/issues/240) -- Why doesn't the decode function use a default algorithm? [\#227](https://github.com/jwt/ruby-jwt/issues/227) - -**Merged pull requests:** - -- Release 2.1.0 preparations [\#243](https://github.com/jwt/ruby-jwt/pull/243) ([excpt](https://github.com/excpt)) -- Update README.md [\#242](https://github.com/jwt/ruby-jwt/pull/242) ([excpt](https://github.com/excpt)) -- Update ebert configuration [\#232](https://github.com/jwt/ruby-jwt/pull/232) ([excpt](https://github.com/excpt)) -- added algos/strategy classes + structs for inputs [\#230](https://github.com/jwt/ruby-jwt/pull/230) ([ab320012](https://github.com/ab320012)) - -## [v2.0.0](https://github.com/jwt/ruby-jwt/tree/v2.0.0) (2017-09-03) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.0.0.beta1...v2.0.0) - -**Fixed bugs:** - -- Support versions outside 2.1 [\#209](https://github.com/jwt/ruby-jwt/issues/209) -- Verifying expiration without leeway throws exception [\#206](https://github.com/jwt/ruby-jwt/issues/206) -- Ruby interpreter warning [\#200](https://github.com/jwt/ruby-jwt/issues/200) -- TypeError: no implicit conversion of String into Integer [\#188](https://github.com/jwt/ruby-jwt/issues/188) -- Fix JWT.encode\(nil\) [\#203](https://github.com/jwt/ruby-jwt/pull/203) ([tmm1](https://github.com/tmm1)) - -**Closed issues:** - -- Possibility to disable claim verifications [\#222](https://github.com/jwt/ruby-jwt/issues/222) -- Proper way to verify Firebase id tokens [\#216](https://github.com/jwt/ruby-jwt/issues/216) - -**Merged pull requests:** - -- Release 2.0.0 preparations :\) [\#225](https://github.com/jwt/ruby-jwt/pull/225) ([excpt](https://github.com/excpt)) -- Skip 'exp' claim validation for array payloads [\#224](https://github.com/jwt/ruby-jwt/pull/224) ([excpt](https://github.com/excpt)) -- Use a default leeway of 0 [\#223](https://github.com/jwt/ruby-jwt/pull/223) ([travisofthenorth](https://github.com/travisofthenorth)) -- Fix reported codesmells [\#221](https://github.com/jwt/ruby-jwt/pull/221) ([excpt](https://github.com/excpt)) -- Add fancy gem version badge [\#220](https://github.com/jwt/ruby-jwt/pull/220) ([excpt](https://github.com/excpt)) -- Add missing dist option to .travis.yml [\#219](https://github.com/jwt/ruby-jwt/pull/219) ([excpt](https://github.com/excpt)) -- Fix ruby version requirements in gemspec file [\#218](https://github.com/jwt/ruby-jwt/pull/218) ([excpt](https://github.com/excpt)) -- Fix a little typo in the readme [\#214](https://github.com/jwt/ruby-jwt/pull/214) ([RyanBrushett](https://github.com/RyanBrushett)) -- Update README.md [\#212](https://github.com/jwt/ruby-jwt/pull/212) ([zuzannast](https://github.com/zuzannast)) -- Fix typo in HS512256 algorithm description [\#211](https://github.com/jwt/ruby-jwt/pull/211) ([ojab](https://github.com/ojab)) -- Allow configuration of multiple acceptable issuers [\#210](https://github.com/jwt/ruby-jwt/pull/210) ([ojab](https://github.com/ojab)) -- Enforce `exp` to be an `Integer` [\#205](https://github.com/jwt/ruby-jwt/pull/205) ([lucasmazza](https://github.com/lucasmazza)) -- ruby 1.9.3 support message upd [\#204](https://github.com/jwt/ruby-jwt/pull/204) ([maokomioko](https://github.com/maokomioko)) - -## [v2.0.0.beta1](https://github.com/jwt/ruby-jwt/tree/v2.0.0.beta1) (2017-02-27) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.6...v2.0.0.beta1) - -**Implemented enhancements:** - -- Error with method sign for String [\#171](https://github.com/jwt/ruby-jwt/issues/171) -- Refactor the encondig code [\#121](https://github.com/jwt/ruby-jwt/issues/121) -- Refactor [\#196](https://github.com/jwt/ruby-jwt/pull/196) ([EmilioCristalli](https://github.com/EmilioCristalli)) -- Move signature logic to its own module [\#195](https://github.com/jwt/ruby-jwt/pull/195) ([EmilioCristalli](https://github.com/EmilioCristalli)) -- Add options for claim-specific leeway [\#187](https://github.com/jwt/ruby-jwt/pull/187) ([EmilioCristalli](https://github.com/EmilioCristalli)) -- Add user friendly encode error if private key is a String, \#171 [\#176](https://github.com/jwt/ruby-jwt/pull/176) ([ogonki-vetochki](https://github.com/ogonki-vetochki)) -- Return empty string if signature less than byte_size \#155 [\#175](https://github.com/jwt/ruby-jwt/pull/175) ([ogonki-vetochki](https://github.com/ogonki-vetochki)) -- Remove 'typ' optional parameter [\#174](https://github.com/jwt/ruby-jwt/pull/174) ([ogonki-vetochki](https://github.com/ogonki-vetochki)) -- Pass payload to keyfinder [\#172](https://github.com/jwt/ruby-jwt/pull/172) ([CodeMonkeySteve](https://github.com/CodeMonkeySteve)) -- Use RbNaCl for HMAC if available with fallback to OpenSSL [\#149](https://github.com/jwt/ruby-jwt/pull/149) ([mwpastore](https://github.com/mwpastore)) - -**Fixed bugs:** - -- ruby-jwt::raw_to_asn1: Fails for signatures less than byte_size [\#155](https://github.com/jwt/ruby-jwt/issues/155) -- The leeway parameter is applies to all time based verifications [\#129](https://github.com/jwt/ruby-jwt/issues/129) -- Make algorithm option required to verify signature [\#184](https://github.com/jwt/ruby-jwt/pull/184) ([EmilioCristalli](https://github.com/EmilioCristalli)) -- Validate audience when payload is a scalar and options is an array [\#183](https://github.com/jwt/ruby-jwt/pull/183) ([steti](https://github.com/steti)) - -**Closed issues:** - -- Different encoded value between servers with same password [\#197](https://github.com/jwt/ruby-jwt/issues/197) -- Signature is different at each run [\#190](https://github.com/jwt/ruby-jwt/issues/190) -- Include custom headers with password [\#189](https://github.com/jwt/ruby-jwt/issues/189) -- can't create token - 'NotImplementedError: Unsupported signing method' [\#186](https://github.com/jwt/ruby-jwt/issues/186) -- Cannot verify JWT at all?? [\#177](https://github.com/jwt/ruby-jwt/issues/177) -- verify_iss: true is raising JWT::DecodeError instead of JWT::InvalidIssuerError [\#170](https://github.com/jwt/ruby-jwt/issues/170) - -**Merged pull requests:** - -- Version bump 2.0.0.beta1 [\#199](https://github.com/jwt/ruby-jwt/pull/199) ([excpt](https://github.com/excpt)) -- Update CHANGELOG.md and minor fixes [\#198](https://github.com/jwt/ruby-jwt/pull/198) ([excpt](https://github.com/excpt)) -- Add Codacy coverage reporter [\#194](https://github.com/jwt/ruby-jwt/pull/194) ([excpt](https://github.com/excpt)) -- Add minimum required ruby version to gemspec [\#193](https://github.com/jwt/ruby-jwt/pull/193) ([excpt](https://github.com/excpt)) -- Code smell fixes [\#192](https://github.com/jwt/ruby-jwt/pull/192) ([excpt](https://github.com/excpt)) -- Version bump to 2.0.0.dev [\#191](https://github.com/jwt/ruby-jwt/pull/191) ([excpt](https://github.com/excpt)) -- Basic encode module refactoring \#121 [\#182](https://github.com/jwt/ruby-jwt/pull/182) ([ogonki-vetochki](https://github.com/ogonki-vetochki)) -- Fix travis ci build configuration [\#181](https://github.com/jwt/ruby-jwt/pull/181) ([excpt](https://github.com/excpt)) -- Fix travis ci build configuration [\#180](https://github.com/jwt/ruby-jwt/pull/180) ([excpt](https://github.com/excpt)) -- Fix typo in README [\#178](https://github.com/jwt/ruby-jwt/pull/178) ([tomeduarte](https://github.com/tomeduarte)) -- Fix code style [\#173](https://github.com/jwt/ruby-jwt/pull/173) ([excpt](https://github.com/excpt)) -- Fixed a typo in a spec name [\#169](https://github.com/jwt/ruby-jwt/pull/169) ([mingan](https://github.com/mingan)) - -## [v1.5.6](https://github.com/jwt/ruby-jwt/tree/v1.5.6) (2016-09-19) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.5...v1.5.6) - -**Fixed bugs:** - -- Fix missing symbol handling in aud verify code [\#166](https://github.com/jwt/ruby-jwt/pull/166) ([excpt](https://github.com/excpt)) - -**Merged pull requests:** - -- Update changelog [\#168](https://github.com/jwt/ruby-jwt/pull/168) ([excpt](https://github.com/excpt)) -- Fix rubocop code smells [\#167](https://github.com/jwt/ruby-jwt/pull/167) ([excpt](https://github.com/excpt)) - -## [v1.5.5](https://github.com/jwt/ruby-jwt/tree/v1.5.5) (2016-09-16) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.4...v1.5.5) - -**Implemented enhancements:** - -- JWT.decode always raises JWT::ExpiredSignature for tokens created with Time objects passed as the `exp` parameter [\#148](https://github.com/jwt/ruby-jwt/issues/148) - -**Fixed bugs:** - -- expiration check does not give "Signature has expired" error for the exact time of expiration [\#157](https://github.com/jwt/ruby-jwt/issues/157) -- JTI claim broken? [\#152](https://github.com/jwt/ruby-jwt/issues/152) -- Audience Claim broken? [\#151](https://github.com/jwt/ruby-jwt/issues/151) -- 1.5.3 breaks compatibility with 1.5.2 [\#133](https://github.com/jwt/ruby-jwt/issues/133) -- Version 1.5.3 breaks 1.9.3 compatibility, but not documented as such [\#132](https://github.com/jwt/ruby-jwt/issues/132) -- Fix: exp claim check [\#161](https://github.com/jwt/ruby-jwt/pull/161) ([excpt](https://github.com/excpt)) - -**Security fixes:** - -- \[security\] Signature verified after expiration/sub/iss checks [\#153](https://github.com/jwt/ruby-jwt/issues/153) -- Signature validation before claim verification [\#160](https://github.com/jwt/ruby-jwt/pull/160) ([excpt](https://github.com/excpt)) - -**Closed issues:** - -- Rendering Json Results in JWT::DecodeError [\#162](https://github.com/jwt/ruby-jwt/issues/162) -- PHP Libraries [\#154](https://github.com/jwt/ruby-jwt/issues/154) -- Is ruby-jwt thread-safe? [\#150](https://github.com/jwt/ruby-jwt/issues/150) -- JWT 1.5.3 [\#143](https://github.com/jwt/ruby-jwt/issues/143) -- gem install v 1.5.3 returns error [\#141](https://github.com/jwt/ruby-jwt/issues/141) -- Adding a CHANGELOG [\#140](https://github.com/jwt/ruby-jwt/issues/140) - -**Merged pull requests:** - -- Bump version [\#165](https://github.com/jwt/ruby-jwt/pull/165) ([excpt](https://github.com/excpt)) -- Improve error message for exp claim in payload [\#164](https://github.com/jwt/ruby-jwt/pull/164) ([excpt](https://github.com/excpt)) -- Fix \#151 and code refactoring [\#163](https://github.com/jwt/ruby-jwt/pull/163) ([excpt](https://github.com/excpt)) -- Create specs for README.md examples [\#159](https://github.com/jwt/ruby-jwt/pull/159) ([excpt](https://github.com/excpt)) -- Tiny Readme Improvement [\#156](https://github.com/jwt/ruby-jwt/pull/156) ([b264](https://github.com/b264)) -- Added test execution to Rakefile [\#147](https://github.com/jwt/ruby-jwt/pull/147) ([jabbrwcky](https://github.com/jabbrwcky)) -- Bump version [\#145](https://github.com/jwt/ruby-jwt/pull/145) ([excpt](https://github.com/excpt)) -- Add a changelog file [\#142](https://github.com/jwt/ruby-jwt/pull/142) ([excpt](https://github.com/excpt)) -- Return decoded_segments [\#139](https://github.com/jwt/ruby-jwt/pull/139) ([akostrikov](https://github.com/akostrikov)) - -## [v1.5.4](https://github.com/jwt/ruby-jwt/tree/v1.5.4) (2016-03-24) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.3...v1.5.4) - -**Closed issues:** - -- 404 at [https://rubygems.global.ssl.fastly.net/gems/jwt-1.5.3.gem](https://rubygems.global.ssl.fastly.net/gems/jwt-1.5.3.gem) [\#137](https://github.com/jwt/ruby-jwt/issues/137) - -**Merged pull requests:** - -- Update README.md [\#138](https://github.com/jwt/ruby-jwt/pull/138) ([excpt](https://github.com/excpt)) -- Fix base64url_decode [\#136](https://github.com/jwt/ruby-jwt/pull/136) ([excpt](https://github.com/excpt)) -- Fix ruby 1.9.3 compatibility [\#135](https://github.com/jwt/ruby-jwt/pull/135) ([excpt](https://github.com/excpt)) -- iat can be a float value [\#134](https://github.com/jwt/ruby-jwt/pull/134) ([llimllib](https://github.com/llimllib)) - -## [v1.5.3](https://github.com/jwt/ruby-jwt/tree/v1.5.3) (2016-02-24) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.5.2...v1.5.3) - -**Implemented enhancements:** - -- Refactor obsolete code for ruby 1.8 support [\#120](https://github.com/jwt/ruby-jwt/issues/120) -- Fix "Rubocop/Metrics/CyclomaticComplexity" issue in lib/jwt.rb [\#106](https://github.com/jwt/ruby-jwt/issues/106) -- Fix "Rubocop/Metrics/CyclomaticComplexity" issue in lib/jwt.rb [\#105](https://github.com/jwt/ruby-jwt/issues/105) -- Allow a proc to be passed for JTI verification [\#126](https://github.com/jwt/ruby-jwt/pull/126) ([yahooguntu](https://github.com/yahooguntu)) -- Relax restrictions on "jti" claim verification [\#113](https://github.com/jwt/ruby-jwt/pull/113) ([lwe](https://github.com/lwe)) - -**Closed issues:** - -- Verifications not functioning in latest release [\#128](https://github.com/jwt/ruby-jwt/issues/128) -- Base64 is generating invalid length base64 strings - cross language interop [\#127](https://github.com/jwt/ruby-jwt/issues/127) -- Digest::Digest is deprecated; use Digest [\#119](https://github.com/jwt/ruby-jwt/issues/119) -- verify_rsa no method 'verify' for class String [\#115](https://github.com/jwt/ruby-jwt/issues/115) -- Add a changelog [\#111](https://github.com/jwt/ruby-jwt/issues/111) - -**Merged pull requests:** - -- Drop ruby 1.9.3 support [\#131](https://github.com/jwt/ruby-jwt/pull/131) ([excpt](https://github.com/excpt)) -- Allow string hash keys in validation configurations [\#130](https://github.com/jwt/ruby-jwt/pull/130) ([tpickett66](https://github.com/tpickett66)) -- Add ruby 2.3.0 for travis ci testing [\#123](https://github.com/jwt/ruby-jwt/pull/123) ([excpt](https://github.com/excpt)) -- Remove obsolete json code [\#122](https://github.com/jwt/ruby-jwt/pull/122) ([excpt](https://github.com/excpt)) -- Add fancy badges to README.md [\#118](https://github.com/jwt/ruby-jwt/pull/118) ([excpt](https://github.com/excpt)) -- Refactor decode and verify functionality [\#117](https://github.com/jwt/ruby-jwt/pull/117) ([excpt](https://github.com/excpt)) -- Drop echoe dependency for gem releases [\#116](https://github.com/jwt/ruby-jwt/pull/116) ([excpt](https://github.com/excpt)) -- Updated readme for iss/aud options [\#114](https://github.com/jwt/ruby-jwt/pull/114) ([ryanmcilmoyl](https://github.com/ryanmcilmoyl)) -- Fix error misspelling [\#112](https://github.com/jwt/ruby-jwt/pull/112) ([kat3kasper](https://github.com/kat3kasper)) - -## [jwt-1.5.2](https://github.com/jwt/ruby-jwt/tree/jwt-1.5.2) (2015-10-27) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.5.1...jwt-1.5.2) - -**Implemented enhancements:** - -- Must we specify algorithm when calling decode to avoid vulnerabilities? [\#107](https://github.com/jwt/ruby-jwt/issues/107) -- Code review: Rspec test refactoring [\#85](https://github.com/jwt/ruby-jwt/pull/85) ([excpt](https://github.com/excpt)) - -**Fixed bugs:** - -- aud verifies if aud is passed in, :sub does not [\#102](https://github.com/jwt/ruby-jwt/issues/102) -- iat check does not use leeway so nbf could pass, but iat fail [\#83](https://github.com/jwt/ruby-jwt/issues/83) - -**Closed issues:** - -- Test ticket from Code Climate [\#104](https://github.com/jwt/ruby-jwt/issues/104) -- Test ticket from Code Climate [\#100](https://github.com/jwt/ruby-jwt/issues/100) -- Is it possible to decode the payload without validating the signature? [\#97](https://github.com/jwt/ruby-jwt/issues/97) -- What is audience? [\#96](https://github.com/jwt/ruby-jwt/issues/96) -- Options hash uses both symbols and strings as keys. [\#95](https://github.com/jwt/ruby-jwt/issues/95) - -**Merged pull requests:** - -- Fix incorrect `iat` examples [\#109](https://github.com/jwt/ruby-jwt/pull/109) ([kjwierenga](https://github.com/kjwierenga)) -- Update docs to include instructions for the algorithm parameter. [\#108](https://github.com/jwt/ruby-jwt/pull/108) ([aarongray](https://github.com/aarongray)) -- make sure :sub check behaves like :aud check [\#103](https://github.com/jwt/ruby-jwt/pull/103) ([skippy](https://github.com/skippy)) -- Change hash syntax [\#101](https://github.com/jwt/ruby-jwt/pull/101) ([excpt](https://github.com/excpt)) -- Include LICENSE and README.md in gem [\#99](https://github.com/jwt/ruby-jwt/pull/99) ([bkeepers](https://github.com/bkeepers)) -- Remove unused variable in the sample code. [\#98](https://github.com/jwt/ruby-jwt/pull/98) ([hypermkt](https://github.com/hypermkt)) -- Fix iat claim example [\#94](https://github.com/jwt/ruby-jwt/pull/94) ([larrylv](https://github.com/larrylv)) -- Fix wrong description in README.md [\#93](https://github.com/jwt/ruby-jwt/pull/93) ([larrylv](https://github.com/larrylv)) -- JWT and JWA are now RFC. [\#92](https://github.com/jwt/ruby-jwt/pull/92) ([aj-michael](https://github.com/aj-michael)) -- Update README.md [\#91](https://github.com/jwt/ruby-jwt/pull/91) ([nsarno](https://github.com/nsarno)) -- Fix missing verify parameter in docs [\#90](https://github.com/jwt/ruby-jwt/pull/90) ([ernie](https://github.com/ernie)) -- Iat check uses leeway. [\#89](https://github.com/jwt/ruby-jwt/pull/89) ([aj-michael](https://github.com/aj-michael)) -- nbf check allows exact time matches. [\#88](https://github.com/jwt/ruby-jwt/pull/88) ([aj-michael](https://github.com/aj-michael)) - -## [jwt-1.5.1](https://github.com/jwt/ruby-jwt/tree/jwt-1.5.1) (2015-06-22) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.5.0...jwt-1.5.1) - -**Implemented enhancements:** - -- Fix either README or source code [\#78](https://github.com/jwt/ruby-jwt/issues/78) -- Validate against draft 20 [\#38](https://github.com/jwt/ruby-jwt/issues/38) - -**Fixed bugs:** - -- ECDSA signature verification fails for valid tokens [\#84](https://github.com/jwt/ruby-jwt/issues/84) -- Shouldn't verification of additional claims, like iss, aud etc. be enforced when in options? [\#81](https://github.com/jwt/ruby-jwt/issues/81) -- decode fails with 'none' algorithm and verify [\#75](https://github.com/jwt/ruby-jwt/issues/75) - -**Closed issues:** - -- Doc mismatch: uninitialized constant JWT::ExpiredSignature [\#79](https://github.com/jwt/ruby-jwt/issues/79) -- TypeError when specifying a wrong algorithm [\#77](https://github.com/jwt/ruby-jwt/issues/77) -- jti verification doesn't prevent replays [\#73](https://github.com/jwt/ruby-jwt/issues/73) - -**Merged pull requests:** - -- Correctly sign ECDSA JWTs [\#87](https://github.com/jwt/ruby-jwt/pull/87) ([jurriaan](https://github.com/jurriaan)) -- fixed results of decoded tokens in readme [\#86](https://github.com/jwt/ruby-jwt/pull/86) ([piscolomo](https://github.com/piscolomo)) -- Force verification of "iss" and "aud" claims [\#82](https://github.com/jwt/ruby-jwt/pull/82) ([lwe](https://github.com/lwe)) - -## [jwt-1.5.0](https://github.com/jwt/ruby-jwt/tree/jwt-1.5.0) (2015-05-09) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.4.1...jwt-1.5.0) - -**Implemented enhancements:** - -- Needs to support asymmetric key signatures over shared secrets [\#46](https://github.com/jwt/ruby-jwt/issues/46) -- Implement Elliptic Curve Crypto Signatures [\#74](https://github.com/jwt/ruby-jwt/pull/74) ([jtdowney](https://github.com/jtdowney)) -- Add an option to verify the signature on decode [\#71](https://github.com/jwt/ruby-jwt/pull/71) ([javawizard](https://github.com/javawizard)) - -**Closed issues:** - -- Check JWT vulnerability [\#76](https://github.com/jwt/ruby-jwt/issues/76) - -**Merged pull requests:** - -- Fixed some examples to make them copy-pastable [\#72](https://github.com/jwt/ruby-jwt/pull/72) ([jer](https://github.com/jer)) - -## [jwt-1.4.1](https://github.com/jwt/ruby-jwt/tree/jwt-1.4.1) (2015-03-12) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.4.0...jwt-1.4.1) - -**Fixed bugs:** - -- jti verification not working per the spec [\#68](https://github.com/jwt/ruby-jwt/issues/68) -- Verify ISS should be off by default [\#66](https://github.com/jwt/ruby-jwt/issues/66) - -**Merged pull requests:** - -- Fix \#66 \#68 [\#69](https://github.com/jwt/ruby-jwt/pull/69) ([excpt](https://github.com/excpt)) -- When throwing errors, mention expected/received values [\#65](https://github.com/jwt/ruby-jwt/pull/65) ([rolodato](https://github.com/rolodato)) - -## [jwt-1.4.0](https://github.com/jwt/ruby-jwt/tree/jwt-1.4.0) (2015-03-10) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.3.0...jwt-1.4.0) - -**Closed issues:** - -- The behavior using 'json' differs from 'multi_json' [\#41](https://github.com/jwt/ruby-jwt/issues/41) - -**Merged pull requests:** - -- Release 1.4.0 [\#64](https://github.com/jwt/ruby-jwt/pull/64) ([excpt](https://github.com/excpt)) -- Update README.md and remove dead code [\#63](https://github.com/jwt/ruby-jwt/pull/63) ([excpt](https://github.com/excpt)) -- Add 'iat/ aud/ sub/ jti' support for ruby-jwt [\#62](https://github.com/jwt/ruby-jwt/pull/62) ([ZhangHanDong](https://github.com/ZhangHanDong)) -- Add 'iss' support for ruby-jwt [\#61](https://github.com/jwt/ruby-jwt/pull/61) ([ZhangHanDong](https://github.com/ZhangHanDong)) -- Clarify .encode API in README [\#60](https://github.com/jwt/ruby-jwt/pull/60) ([jbodah](https://github.com/jbodah)) - -## [jwt-1.3.0](https://github.com/jwt/ruby-jwt/tree/jwt-1.3.0) (2015-02-24) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.2.1...jwt-1.3.0) - -**Closed issues:** - -- Signature Verification to Return Verification Error rather than decode error [\#57](https://github.com/jwt/ruby-jwt/issues/57) -- Incorrect readme for leeway [\#55](https://github.com/jwt/ruby-jwt/issues/55) -- What is the reason behind stripping the = in base64 encoding? [\#54](https://github.com/jwt/ruby-jwt/issues/54) -- Preparations for version 2.x [\#50](https://github.com/jwt/ruby-jwt/issues/50) -- Release a new version [\#47](https://github.com/jwt/ruby-jwt/issues/47) -- Catch up for ActiveWhatever 4.1.1 series [\#40](https://github.com/jwt/ruby-jwt/issues/40) - -**Merged pull requests:** - -- raise verification error for signature verification [\#58](https://github.com/jwt/ruby-jwt/pull/58) ([punkle](https://github.com/punkle)) -- Added support for not before claim verification [\#56](https://github.com/jwt/ruby-jwt/pull/56) ([punkle](https://github.com/punkle)) - -## [jwt-1.2.1](https://github.com/jwt/ruby-jwt/tree/jwt-1.2.1) (2015-01-22) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.2.0...jwt-1.2.1) - -**Closed issues:** - -- JWT.encode\({"exp": 10}, "secret"\) [\#52](https://github.com/jwt/ruby-jwt/issues/52) -- JWT.encode\({"exp": 10}, "secret"\) [\#51](https://github.com/jwt/ruby-jwt/issues/51) - -**Merged pull requests:** - -- Accept expiration claims as string [\#53](https://github.com/jwt/ruby-jwt/pull/53) ([yarmand](https://github.com/yarmand)) - -## [jwt-1.2.0](https://github.com/jwt/ruby-jwt/tree/jwt-1.2.0) (2014-11-24) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.13...jwt-1.2.0) - -**Closed issues:** - -- set token to expire [\#42](https://github.com/jwt/ruby-jwt/issues/42) - -**Merged pull requests:** - -- Added support for `exp` claim [\#45](https://github.com/jwt/ruby-jwt/pull/45) ([zshannon](https://github.com/zshannon)) -- rspec 3 breaks passing tests [\#44](https://github.com/jwt/ruby-jwt/pull/44) ([zshannon](https://github.com/zshannon)) - -## [jwt-0.1.13](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.13) (2014-05-08) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.0.0...jwt-0.1.13) - -**Closed issues:** - -- yanking of version 0.1.12 causes issues [\#39](https://github.com/jwt/ruby-jwt/issues/39) -- Semantic versioning [\#37](https://github.com/jwt/ruby-jwt/issues/37) -- Update gem to get latest changes [\#36](https://github.com/jwt/ruby-jwt/issues/36) - -## [jwt-1.0.0](https://github.com/jwt/ruby-jwt/tree/jwt-1.0.0) (2014-05-07) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.11...jwt-1.0.0) - -**Closed issues:** - -- API request - JWT::decoded_header\(\) [\#26](https://github.com/jwt/ruby-jwt/issues/26) - -**Merged pull requests:** - -- return header along with playload after decoding [\#35](https://github.com/jwt/ruby-jwt/pull/35) ([sawyerzhang](https://github.com/sawyerzhang)) -- Raise JWT::DecodeError on nil token [\#34](https://github.com/jwt/ruby-jwt/pull/34) ([tjmw](https://github.com/tjmw)) -- Make MultiJson optional for Ruby 1.9+ [\#33](https://github.com/jwt/ruby-jwt/pull/33) ([petergoldstein](https://github.com/petergoldstein)) -- Allow access to header and payload without signature verification [\#32](https://github.com/jwt/ruby-jwt/pull/32) ([petergoldstein](https://github.com/petergoldstein)) -- Update specs to use RSpec 3.0.x syntax [\#31](https://github.com/jwt/ruby-jwt/pull/31) ([petergoldstein](https://github.com/petergoldstein)) -- Travis - Add Ruby 2.0.0, 2.1.0, Rubinius [\#30](https://github.com/jwt/ruby-jwt/pull/30) ([petergoldstein](https://github.com/petergoldstein)) - -## [jwt-0.1.11](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.11) (2014-01-17) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.10...jwt-0.1.11) - -**Closed issues:** - -- url safe encode and decode [\#28](https://github.com/jwt/ruby-jwt/issues/28) -- Release [\#27](https://github.com/jwt/ruby-jwt/issues/27) - -**Merged pull requests:** - -- fixed urlsafe base64 encoding [\#29](https://github.com/jwt/ruby-jwt/pull/29) ([tobscher](https://github.com/tobscher)) - -## [jwt-0.1.10](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.10) (2014-01-10) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.8...jwt-0.1.10) - -**Closed issues:** - -- change to signature of JWT.decode method [\#14](https://github.com/jwt/ruby-jwt/issues/14) - -**Merged pull requests:** - -- Fix warning: assigned but unused variable - e [\#25](https://github.com/jwt/ruby-jwt/pull/25) ([sferik](https://github.com/sferik)) -- Echoe doesn't define a license= method [\#24](https://github.com/jwt/ruby-jwt/pull/24) ([sferik](https://github.com/sferik)) -- Use OpenSSL::Digest instead of deprecated OpenSSL::Digest::Digest [\#23](https://github.com/jwt/ruby-jwt/pull/23) ([JuanitoFatas](https://github.com/JuanitoFatas)) -- Handle some invalid JWTs [\#22](https://github.com/jwt/ruby-jwt/pull/22) ([steved](https://github.com/steved)) -- Add MIT license to gemspec [\#21](https://github.com/jwt/ruby-jwt/pull/21) ([nycvotes-dev](https://github.com/nycvotes-dev)) -- Tweaks and improvements [\#20](https://github.com/jwt/ruby-jwt/pull/20) ([threedaymonk](https://github.com/threedaymonk)) -- Don't leave errors in OpenSSL.errors when there is a decoding error. [\#19](https://github.com/jwt/ruby-jwt/pull/19) ([lowellk](https://github.com/lowellk)) - -## [jwt-0.1.8](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.8) (2013-03-14) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.7...jwt-0.1.8) - -**Merged pull requests:** - -- Contrib and update [\#18](https://github.com/jwt/ruby-jwt/pull/18) ([threedaymonk](https://github.com/threedaymonk)) -- Verify if verify is truthy \(not just true\) [\#17](https://github.com/jwt/ruby-jwt/pull/17) ([threedaymonk](https://github.com/threedaymonk)) - -## [jwt-0.1.7](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.7) (2013-03-07) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.6...jwt-0.1.7) - -**Merged pull requests:** - -- Catch MultiJson::LoadError and reraise as JWT::DecodeError [\#16](https://github.com/jwt/ruby-jwt/pull/16) ([rwygand](https://github.com/rwygand)) - -## [jwt-0.1.6](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.6) (2013-03-05) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.5...jwt-0.1.6) - -**Merged pull requests:** - -- Fixes a theoretical timing attack [\#15](https://github.com/jwt/ruby-jwt/pull/15) ([mgates](https://github.com/mgates)) -- Use StandardError as parent for DecodeError [\#13](https://github.com/jwt/ruby-jwt/pull/13) ([Oscil8](https://github.com/Oscil8)) - -## [jwt-0.1.5](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.5) (2012-07-20) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.4...jwt-0.1.5) - -**Closed issues:** - -- Unable to specify signature header fields [\#7](https://github.com/jwt/ruby-jwt/issues/7) - -**Merged pull requests:** - -- MultiJson dependency uses ~\> but should be \>= [\#12](https://github.com/jwt/ruby-jwt/pull/12) ([sporkmonger](https://github.com/sporkmonger)) -- Oops. :-\) [\#11](https://github.com/jwt/ruby-jwt/pull/11) ([sporkmonger](https://github.com/sporkmonger)) -- Fix issue with signature verification in JRuby [\#10](https://github.com/jwt/ruby-jwt/pull/10) ([sporkmonger](https://github.com/sporkmonger)) -- Depend on MultiJson [\#9](https://github.com/jwt/ruby-jwt/pull/9) ([lautis](https://github.com/lautis)) -- Allow for custom headers on encode and decode [\#8](https://github.com/jwt/ruby-jwt/pull/8) ([dgrijalva](https://github.com/dgrijalva)) -- Missing development dependency for echoe gem. [\#6](https://github.com/jwt/ruby-jwt/pull/6) ([sporkmonger](https://github.com/sporkmonger)) - -## [jwt-0.1.4](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.4) (2011-11-11) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.3...jwt-0.1.4) - -**Merged pull requests:** - -- Fix for RSA verification [\#5](https://github.com/jwt/ruby-jwt/pull/5) ([jordan-brough](https://github.com/jordan-brough)) - -## [jwt-0.1.3](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.3) (2011-06-30) - -[Full Changelog](https://github.com/jwt/ruby-jwt/compare/10d7492ea325c65fce41191c73cd90d4de494772...jwt-0.1.3) - -**Closed issues:** - -- signatures calculated incorrectly \(hexdigest instead of digest\) [\#1](https://github.com/jwt/ruby-jwt/issues/1) - -**Merged pull requests:** - -- Bumped a version and added a .gemspec using rake build_gemspec [\#3](https://github.com/jwt/ruby-jwt/pull/3) ([zhitomirskiyi](https://github.com/zhitomirskiyi)) -- Added RSA support [\#2](https://github.com/jwt/ruby-jwt/pull/2) ([zhitomirskiyi](https://github.com/zhitomirskiyi)) diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..bd61a5912 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +ruby-jwt.org diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 1d65f7aec..000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,84 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -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. - -We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or - advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email - address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -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. - -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. - -## Scope - -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. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at . All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. - -**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. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of actions. - -**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. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. - -**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. - -### 4. Permanent Ban - -**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. - -**Consequence**: A permanent ban from any sort of public interaction within the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, -available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html). - -Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index f6a98f1a5..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,98 +0,0 @@ -# Contributing to [ruby-jwt](https://github.com/jwt/ruby-jwt) - -## Forking the project - -Fork the project on GitHub and clone your own fork. Instructions on forking can be found from the [GitHub Docs](https://docs.github.com/en/get-started/quickstart/fork-a-repo) - -```bash -git clone git@github.com:you/ruby-jwt.git -cd ruby-jwt -git remote add upstream https://github.com/jwt/ruby-jwt -``` - -## Create a branch for your implementation - -Make sure you have the latest upstream main branch of the project. - -```bash -git fetch --all -git checkout main -git rebase upstream/main -git push origin main -git checkout -b fix-a-little-problem -``` - -## Running the tests and linter - -Before you start with your implementation make sure you are able to get a successful test run with the current revision. - -The tests are written with rspec and [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features. - -[Rubocop](https://github.com/rubocop/rubocop) is used to enforce the Ruby style. - -To run the complete set of tests and linter run the following - -```bash -bundle install -bundle exec appraisal rake test -bundle exec rubocop -``` - -## Implement your feature - -Implement tests and your change. Don't be shy adding a little something in the [README](README.md). -Add a short description of the change in either the `Features` or `Fixes` section in the [CHANGELOG](CHANGELOG.md) file. - -The form of the row (You need to return to the row when you know the pull request id) - -```markdown -- Fix a little problem [#123](https://github.com/jwt/ruby-jwt/pull/123) - [@you](https://github.com/you). -``` - -## Push your branch and create a pull request - -Before pushing make sure the tests pass and RuboCop is happy. - -```bash -bundle exec appraisal rake test -bundle exec rubocop -git push origin fix-a-little-problem -``` - -Make a new pull request on the [ruby-jwt project](https://github.com/jwt/ruby-jwt/pulls) with a description what the change is about. - -## Update the CHANGELOG, again - -Update the [CHANGELOG](CHANGELOG.md) with the pull request id from the previous step. - -You can amend the previous commit with the updated changelog change and force push your branch. The PR will get automatically updated. - -```bash -git add CHANGELOG.md -git commit --amend --no-edit -git push origin fix-a-little-problem -f -``` - -## Keep an eye on your pull request - -A maintainer will review and probably merge you changes when time allows, be patient. - -## Keeping your branch up-to-date - -It's recommended that you keep your branch up-to-date by rebasing to the upstream main. - -```bash -git fetch upstream -git checkout fix-a-little-problem -git rebase upstream/main -git push origin fix-a-little-problem -f -``` - -## Releasing a new version - -The version is using the [Semantic Versioning](http://semver.org/) and the version is located in the [version.rb](lib/jwt/version.rb) file. -Also update the [CHANGELOG](CHANGELOG.md) to reflect the upcoming version release. - -```bash -rake release -``` diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 7f4f5e950..000000000 --- a/Gemfile +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -source 'https://rubygems.org' - -gemspec diff --git a/JWT.html b/JWT.html new file mode 100644 index 000000000..dac75600c --- /dev/null +++ b/JWT.html @@ -0,0 +1,831 @@ + + + + + + + Module: JWT + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT + + + +

+
+ + + + +
+
Extended by:
+
Configuration
+
+ + + + + + + + +
+
Defined in:
+
lib/jwt.rb,
+ lib/jwt/jwa.rb,
lib/jwt/jwk.rb,
lib/jwt/json.rb,
lib/jwt/error.rb,
lib/jwt/token.rb,
lib/jwt/base64.rb,
lib/jwt/claims.rb,
lib/jwt/decode.rb,
lib/jwt/encode.rb,
lib/jwt/jwa/ps.rb,
lib/jwt/jwk/ec.rb,
lib/jwt/jwa/rsa.rb,
lib/jwt/jwk/rsa.rb,
lib/jwt/jwk/set.rb,
lib/jwt/version.rb,
lib/jwt/jwa/hmac.rb,
lib/jwt/jwa/none.rb,
lib/jwt/jwk/hmac.rb,
lib/jwt/jwa/ecdsa.rb,
lib/jwt/claims/crit.rb,
lib/jwt/jwk/key_base.rb,
lib/jwt/claims/issuer.rb,
lib/jwt/claims/jwt_id.rb,
lib/jwt/configuration.rb,
lib/jwt/encoded_token.rb,
lib/jwt/claims/numeric.rb,
lib/jwt/claims/subject.rb,
lib/jwt/jwk/key_finder.rb,
lib/jwt/jwk/thumbprint.rb,
lib/jwt/x5c_key_finder.rb,
lib/jwt/claims/audience.rb,
lib/jwt/claims/required.rb,
lib/jwt/claims/verifier.rb,
lib/jwt/jwa/unsupported.rb,
lib/jwt/claims/issued_at.rb,
lib/jwt/claims/expiration.rb,
lib/jwt/claims/not_before.rb,
lib/jwt/jwa/signing_algorithm.rb,
lib/jwt/jwk/kid_as_key_digest.rb,
lib/jwt/claims/decode_verifier.rb,
lib/jwt/configuration/container.rb,
lib/jwt/configuration/jwk_configuration.rb,
lib/jwt/configuration/decode_configuration.rb
+
+
+ +
+ +

Overview

+
+ +

JSON Web Token implementation

+ +

Should be up to date with the latest spec: tools.ietf.org/html/rfc7519

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + Modules: Claims, Configuration, JWA, JWK, VERSION + + + + Classes: Base64, Base64DecodeError, Decode, DecodeError, Encode, EncodeError, EncodedToken, ExpiredSignature, ImmatureSignature, IncorrectAlgorithm, InvalidAudError, InvalidCritError, InvalidIatError, InvalidIssuerError, InvalidJtiError, InvalidPayload, InvalidSubError, JSON, JWKError, MissingRequiredClaim, Token, UnsupportedEcdsaCurve, VerificationError, X5cKeyFinder + + +

+ + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + + + + + + + + +

Methods included from Configuration

+

configuration, configure

+ + +
+

Class Method Details

+ + +
+

+ + .decode(jwt, key = nil, verify = true, options = {}, &keyfinder) ⇒ Array<Hash> + + + + + +

+
+ +

Decodes a JWT to extract the payload and header

+ + +
+
+
+

Parameters:

+
    + +
  • + + jwt + + + (String) + + + + — +
    +

    the JWT to decode.

    +
    + +
  • + +
  • + + key + + + (String) + + + (defaults to: nil) + + + — +
    +

    the key used to verify the JWT.

    +
    + +
  • + +
  • + + verify + + + (Boolean) + + + (defaults to: true) + + + — +
    +

    whether to verify the JWT signature.

    +
    + +
  • + +
  • + + options + + + (Hash) + + + (defaults to: {}) + + + — +
    +

    additional options for decoding.

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Array<Hash>) + + + + — +
    +

    the decoded payload and headers.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+45
+46
+47
+
+
# File 'lib/jwt.rb', line 45
+
+def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter
+  Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
+end
+
+
+ +
+

+ + .encode(payload, key, algorithm = 'HS256', header_fields = {}) ⇒ String + + + + + +

+
+ +

Encodes a payload into a JWT.

+ + +
+
+
+

Parameters:

+
    + +
  • + + payload + + + (Hash) + + + + — +
    +

    the payload to encode.

    +
    + +
  • + +
  • + + key + + + (String) + + + + — +
    +

    the key used to sign the JWT.

    +
    + +
  • + +
  • + + algorithm + + + (String) + + + (defaults to: 'HS256') + + + — +
    +

    the algorithm used to sign the JWT.

    +
    + +
  • + +
  • + + header_fields + + + (Hash) + + + (defaults to: {}) + + + — +
    +

    additional headers to include in the JWT.

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the encoded JWT.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+31
+32
+33
+34
+35
+36
+
+
# File 'lib/jwt.rb', line 31
+
+def encode(payload, key, algorithm = 'HS256', header_fields = {})
+  Encode.new(payload: payload,
+             key: key,
+             algorithm: algorithm,
+             headers: header_fields).segments
+end
+
+
+ +
+

+ + .gem_versionGem::Version + + + + + +

+
+ +

Returns the gem version of the JWT library.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Gem::Version) + + + + — +
    +

    the gem version.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+11
+12
+13
+
+
# File 'lib/jwt/version.rb', line 11
+
+def self.gem_version
+  Gem::Version.new(VERSION::STRING)
+end
+
+
+ +
+

+ + .openssl_3?Boolean + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ +

Checks if the OpenSSL version is 3 or greater.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    true if OpenSSL version is 3 or greater, false otherwise.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+29
+30
+31
+32
+33
+
+
# File 'lib/jwt/version.rb', line 29
+
+def self.openssl_3?
+  return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL')
+
+  true if 3 * 0x10000000 <= OpenSSL::OPENSSL_VERSION_NUMBER
+end
+
+
+ +
+

+ + .openssl_3_hmac_empty_key_regression?Boolean + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ +

Checks if there is an OpenSSL 3 HMAC empty key regression.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    true if there is an OpenSSL 3 HMAC empty key regression, false otherwise.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+39
+40
+41
+
+
# File 'lib/jwt/version.rb', line 39
+
+def self.openssl_3_hmac_empty_key_regression?
+  openssl_3? && openssl_version <= ::Gem::Version.new('3.0.0')
+end
+
+
+ +
+

+ + .openssl_versionGem::Version + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ +

Returns the OpenSSL version.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Gem::Version) + + + + — +
    +

    the OpenSSL version.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+47
+48
+49
+
+
# File 'lib/jwt/version.rb', line 47
+
+def self.openssl_version
+  @openssl_version ||= ::Gem::Version.new(OpenSSL::VERSION)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Base64.html b/JWT/Base64.html new file mode 100644 index 000000000..6df39b24b --- /dev/null +++ b/JWT/Base64.html @@ -0,0 +1,299 @@ + + + + + + + Class: JWT::Base64 + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Base64 + + + Private +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/base64.rb
+
+ +
+ +

Overview

+
+

+ This class is part of a private API. + You should avoid using this class if possible, as it may be removed or be changed in the future. +

+ +

Base64 encoding and decoding

+ + +
+
+
+ + +
+ + + + + + + +

+ Class Method Summary + collapse +

+ +
    + +
  • + + + .url_decode(str) ⇒ Object + + + + + + + + + + + private + + +
    +

    Decode a string with URL-safe Base64 complying with RFC 4648.

    +
    + +
  • + + +
  • + + + .url_encode(str) ⇒ Object + + + + + + + + + + + private + + +
    +

    Encode a string with URL-safe Base64 complying with RFC 4648 (not padded).

    +
    + +
  • + + +
+ + + + +
+

Class Method Details

+ + +
+

+ + .url_decode(str) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ +

Decode a string with URL-safe Base64 complying with RFC 4648.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+18
+19
+20
+21
+22
+23
+24
+
+
# File 'lib/jwt/base64.rb', line 18
+
+def url_decode(str)
+  ::Base64.urlsafe_decode64(str)
+rescue ArgumentError => e
+  raise unless e.message == 'invalid base64'
+
+  raise Base64DecodeError, 'Invalid base64 encoding'
+end
+
+
+ +
+

+ + .url_encode(str) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ +

Encode a string with URL-safe Base64 complying with RFC 4648 (not padded).

+ + +
+
+
+ + +
+ + + + +
+
+
+
+12
+13
+14
+
+
# File 'lib/jwt/base64.rb', line 12
+
+def url_encode(str)
+  ::Base64.urlsafe_encode64(str, padding: false)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Base64DecodeError.html b/JWT/Base64DecodeError.html new file mode 100644 index 000000000..c0b4ba0b7 --- /dev/null +++ b/JWT/Base64DecodeError.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::Base64DecodeError + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::Base64DecodeError + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The Base64DecodeError class is raised when there is an error decoding a Base64-encoded string.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims.html b/JWT/Claims.html new file mode 100644 index 000000000..891234818 --- /dev/null +++ b/JWT/Claims.html @@ -0,0 +1,500 @@ + + + + + + + Module: JWT::Claims + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT::Claims + + + +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims.rb,
+ lib/jwt/claims/crit.rb,
lib/jwt/claims/issuer.rb,
lib/jwt/claims/jwt_id.rb,
lib/jwt/claims/numeric.rb,
lib/jwt/claims/subject.rb,
lib/jwt/claims/audience.rb,
lib/jwt/claims/required.rb,
lib/jwt/claims/verifier.rb,
lib/jwt/claims/issued_at.rb,
lib/jwt/claims/expiration.rb,
lib/jwt/claims/not_before.rb,
lib/jwt/claims/decode_verifier.rb
+
+
+ +
+ +

Overview

+
+ +

JWT Claim verifications datatracker.ietf.org/doc/html/rfc7519#section-4

+ +

Verification is supported for the following claims: exp nbf iss iat jti aud sub required numeric

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + Modules: DecodeVerifier, Verifier + + + + Classes: Audience, Crit, Error, Expiration, IssuedAt, Issuer, JwtId, NotBefore, Numeric, Required, Subject, VerificationContext + + +

+ + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .payload_errors(payload, *options) ⇒ Array<JWT::Claims::Error> + + + + + +

+
+ +

Returns the errors in the claims of the JWT token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + options + + + (Array) + + + + — +
    +

    the options for verifying the claims.

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Array<JWT::Claims::Error>) + + + + — +
    +

    the errors in the claims of the JWT

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+62
+63
+64
+
+
# File 'lib/jwt/claims.rb', line 62
+
+def payload_errors(payload, *options)
+  Verifier.errors(VerificationContext.new(payload: payload), *options)
+end
+
+
+ +
+

+ + .valid_payload?(payload, *options) ⇒ Boolean + + + + + +

+
+ +

Checks if the claims in the JWT payload are valid.

+ + +
+
+
+

Parameters:

+
    + +
  • + + payload + + + (Hash) + + + + — +
    +

    the JWT payload.

    +
    + +
  • + +
  • + + options + + + (Array) + + + + — +
    +

    the options for verifying the claims.

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    true if the claims are valid, false otherwise

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+54
+55
+56
+
+
# File 'lib/jwt/claims.rb', line 54
+
+def valid_payload?(payload, *options)
+  payload_errors(payload, *options).empty?
+end
+
+
+ +
+

+ + .verify_payload!(payload, *options) ⇒ void + + + + + +

+
+

This method returns an undefined value.

+

Checks if the claims in the JWT payload are valid.

+ + +
+
+
+ +
+

Examples:

+ + +

+::JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :exp)
+::JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11})
+ +
+

Parameters:

+
    + +
  • + + payload + + + (Hash) + + + + — +
    +

    the JWT payload.

    +
    + +
  • + +
  • + + options + + + (Array) + + + + — +
    +

    the options for verifying the claims.

    +
    + +
  • + +
+ +

Raises:

+ + +
+ + + + +
+
+
+
+45
+46
+47
+
+
# File 'lib/jwt/claims.rb', line 45
+
+def verify_payload!(payload, *options)
+  Verifier.verify!(VerificationContext.new(payload: payload), *options)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/Audience.html b/JWT/Claims/Audience.html new file mode 100644 index 000000000..433639cdb --- /dev/null +++ b/JWT/Claims/Audience.html @@ -0,0 +1,374 @@ + + + + + + + Class: JWT::Claims::Audience + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::Audience + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/audience.rb
+
+ +
+ +

Overview

+
+ +

The Audience class is responsible for validating the audience claim (‘aud’) in a JWT token.

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(expected_audience:) ⇒ Audience + + + + + +

+
+ +

Initializes a new Audience instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + expected_audience + + + (String, Array<String>) + + + + — +
    +

    the expected audience(s) for the JWT token.

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+10
+11
+12
+
+
# File 'lib/jwt/claims/audience.rb', line 10
+
+def initialize(expected_audience:)
+  @expected_audience = expected_audience
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #verify!(context:, **_args) ⇒ nil + + + + + +

+
+ +

Verifies the audience claim (‘aud’) in the JWT token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + context + + + (Object) + + + + — +
    +

    the context containing the JWT payload.

    +
    + +
  • + +
  • + + _args + + + (Hash) + + + + — +
    +

    additional arguments (not used).

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+ + +
+ + + + +
+
+
+
+20
+21
+22
+23
+
+
# File 'lib/jwt/claims/audience.rb', line 20
+
+def verify!(context:, **_args)
+  aud = context.payload['aud']
+  raise JWT::InvalidAudError, "Invalid audience. Expected #{expected_audience}, received #{aud || '<none>'}" if ([*aud] & [*expected_audience]).empty?
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/Crit.html b/JWT/Claims/Crit.html new file mode 100644 index 000000000..36fc81705 --- /dev/null +++ b/JWT/Claims/Crit.html @@ -0,0 +1,384 @@ + + + + + + + Class: JWT::Claims::Crit + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::Crit + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/crit.rb
+
+ +
+ +

Overview

+
+ +

Responsible of validation the crit header

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(expected_crits:) ⇒ Crit + + + + + +

+
+ +

Initializes a new Crit instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + expected_crits + + + (String) + + + + — +
    +

    the expected crit header values for the JWT token.

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+10
+11
+12
+
+
# File 'lib/jwt/claims/crit.rb', line 10
+
+def initialize(expected_crits:)
+  @expected_crits = Array(expected_crits)
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #verify!(context:, **_args) ⇒ nil + + + + + +

+
+ +

Verifies the critical claim (‘crit’) in the JWT token header.

+ + +
+
+
+

Parameters:

+
    + +
  • + + context + + + (Object) + + + + — +
    +

    the context containing the JWT payload and header.

    +
    + +
  • + +
  • + + _args + + + (Hash) + + + + — +
    +

    additional arguments (not used).

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+ + +
+ + + + +
+
+
+
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
+
# File 'lib/jwt/claims/crit.rb', line 20
+
+def verify!(context:, **_args)
+  raise(JWT::InvalidCritError, 'Crit header missing') unless context.header['crit']
+  raise(JWT::InvalidCritError, 'Crit header should be an array') unless context.header['crit'].is_a?(Array)
+
+  missing = (expected_crits - context.header['crit'])
+  raise(JWT::InvalidCritError, "Crit header missing expected values: #{missing.join(', ')}") if missing.any?
+
+  nil
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/DecodeVerifier.html b/JWT/Claims/DecodeVerifier.html new file mode 100644 index 000000000..e31912c39 --- /dev/null +++ b/JWT/Claims/DecodeVerifier.html @@ -0,0 +1,212 @@ + + + + + + + Module: JWT::Claims::DecodeVerifier + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT::Claims::DecodeVerifier + + + Private +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/decode_verifier.rb
+
+ +
+ +

Overview

+
+

+ This module is part of a private API. + You should avoid using this module if possible, as it may be removed or be changed in the future. +

+ +

Verifiers to support the ::JWT.decode method

+ + +
+
+
+ + +
+ + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .verify!(payload, options) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+29
+30
+31
+32
+33
+34
+35
+36
+
+
# File 'lib/jwt/claims/decode_verifier.rb', line 29
+
+def verify!(payload, options)
+  VERIFIERS.each do |key, verifier_builder|
+    next unless options[key] || options[key.to_s]
+
+    verifier_builder&.call(options)&.verify!(context: VerificationContext.new(payload: payload))
+  end
+  nil
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/Error.html b/JWT/Claims/Error.html new file mode 100644 index 000000000..64bb6c0bc --- /dev/null +++ b/JWT/Claims/Error.html @@ -0,0 +1,233 @@ + + + + + + + Class: JWT::Claims::Error + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::Error + + + +

+
+ +
+
Inherits:
+
+ Struct + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims.rb
+
+ +
+ +

Overview

+
+ +

Represents a claim verification error

+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #message ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute message.

    +
    + +
  • + + +
+ + + + + + +
+

Instance Attribute Details

+ + + +
+

+ + #messageObject + + + + + +

+
+ +

Returns the value of attribute message

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Object) + + + + — +
    +

    the current value of message

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+32
+33
+34
+
+
# File 'lib/jwt/claims.rb', line 32
+
+def message
+  @message
+end
+
+
+ +
+ + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/Expiration.html b/JWT/Claims/Expiration.html new file mode 100644 index 000000000..b3dbf6573 --- /dev/null +++ b/JWT/Claims/Expiration.html @@ -0,0 +1,378 @@ + + + + + + + Class: JWT::Claims::Expiration + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::Expiration + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/expiration.rb
+
+ +
+ +

Overview

+
+ +

The Expiration class is responsible for validating the expiration claim (‘exp’) in a JWT token.

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(leeway:) ⇒ Expiration + + + + + +

+
+ +

Initializes a new Expiration instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + leeway + + + (Integer) + + + + — +
    +

    the amount of leeway (in seconds) to allow when validating the expiration time. Default: 0.

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+10
+11
+12
+
+
# File 'lib/jwt/claims/expiration.rb', line 10
+
+def initialize(leeway:)
+  @leeway = leeway || 0
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #verify!(context:, **_args) ⇒ nil + + + + + +

+
+ +

Verifies the expiration claim (‘exp’) in the JWT token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + context + + + (Object) + + + + — +
    +

    the context containing the JWT payload.

    +
    + +
  • + +
  • + + _args + + + (Hash) + + + + — +
    +

    additional arguments (not used).

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+ + +
+ + + + +
+
+
+
+20
+21
+22
+23
+24
+25
+
+
# File 'lib/jwt/claims/expiration.rb', line 20
+
+def verify!(context:, **_args)
+  return unless context.payload.is_a?(Hash)
+  return unless context.payload.key?('exp')
+
+  raise JWT::ExpiredSignature, 'Signature has expired' if context.payload['exp'].to_i <= (Time.now.to_i - leeway)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/IssuedAt.html b/JWT/Claims/IssuedAt.html new file mode 100644 index 000000000..6366e98ca --- /dev/null +++ b/JWT/Claims/IssuedAt.html @@ -0,0 +1,289 @@ + + + + + + + Class: JWT::Claims::IssuedAt + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::IssuedAt + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/issued_at.rb
+
+ +
+ +

Overview

+
+ +

The IssuedAt class is responsible for validating the issued at claim (‘iat’) in a JWT token.

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + + + +
+

Instance Method Details

+ + +
+

+ + #verify!(context:, **_args) ⇒ nil + + + + + +

+
+ +

Verifies the issued at claim (‘iat’) in the JWT token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + context + + + (Object) + + + + — +
    +

    the context containing the JWT payload.

    +
    + +
  • + +
  • + + _args + + + (Hash) + + + + — +
    +

    additional arguments (not used).

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+ + +
+ + + + +
+
+
+
+13
+14
+15
+16
+17
+18
+19
+
+
# File 'lib/jwt/claims/issued_at.rb', line 13
+
+def verify!(context:, **_args)
+  return unless context.payload.is_a?(Hash)
+  return unless context.payload.key?('iat')
+
+  iat = context.payload['iat']
+  raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(::Numeric) || iat.to_f > Time.now.to_f
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/Issuer.html b/JWT/Claims/Issuer.html new file mode 100644 index 000000000..8e8c0987d --- /dev/null +++ b/JWT/Claims/Issuer.html @@ -0,0 +1,382 @@ + + + + + + + Class: JWT::Claims::Issuer + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::Issuer + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/issuer.rb
+
+ +
+ +

Overview

+
+ +

The Issuer class is responsible for validating the issuer claim (‘iss’) in a JWT token.

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(issuers:) ⇒ Issuer + + + + + +

+
+ +

Initializes a new Issuer instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + issuers + + + (String, Symbol, Array<String, Symbol>) + + + + — +
    +

    the expected issuer(s) for the JWT token.

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+10
+11
+12
+
+
# File 'lib/jwt/claims/issuer.rb', line 10
+
+def initialize(issuers:)
+  @issuers = Array(issuers).map { |item| item.is_a?(Symbol) ? item.to_s : item }
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #verify!(context:, **_args) ⇒ nil + + + + + +

+
+ +

Verifies the issuer claim (‘iss’) in the JWT token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + context + + + (Object) + + + + — +
    +

    the context containing the JWT payload.

    +
    + +
  • + +
  • + + _args + + + (Hash) + + + + — +
    +

    additional arguments (not used).

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+ + +
+ + + + +
+
+
+
+20
+21
+22
+23
+24
+25
+26
+27
+
+
# File 'lib/jwt/claims/issuer.rb', line 20
+
+def verify!(context:, **_args)
+  case (iss = context.payload['iss'])
+  when *issuers
+    nil
+  else
+    raise JWT::InvalidIssuerError, "Invalid issuer. Expected #{issuers}, received #{iss || '<none>'}"
+  end
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/JwtId.html b/JWT/Claims/JwtId.html new file mode 100644 index 000000000..0c7760946 --- /dev/null +++ b/JWT/Claims/JwtId.html @@ -0,0 +1,384 @@ + + + + + + + Class: JWT::Claims::JwtId + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::JwtId + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/jwt_id.rb
+
+ +
+ +

Overview

+
+ +

The JwtId class is responsible for validating the JWT ID claim (‘jti’) in a JWT token.

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(validator:) ⇒ JwtId + + + + + +

+
+ +

Initializes a new JwtId instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + validator + + + (#call) + + + + — +
    +

    an object responding to ‘call` to validate the JWT ID.

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+10
+11
+12
+
+
# File 'lib/jwt/claims/jwt_id.rb', line 10
+
+def initialize(validator:)
+  @validator = validator
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #verify!(context:, **_args) ⇒ nil + + + + + +

+
+ +

Verifies the JWT ID claim (‘jti’) in the JWT token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + context + + + (Object) + + + + — +
    +

    the context containing the JWT payload.

    +
    + +
  • + +
  • + + _args + + + (Hash) + + + + — +
    +

    additional arguments (not used).

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+
    + +
  • + + + (JWT::InvalidJtiError) + + + + — +
    +

    if the JWT ID claim is invalid or missing.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
+
# File 'lib/jwt/claims/jwt_id.rb', line 20
+
+def verify!(context:, **_args)
+  jti = context.payload['jti']
+  if validator.respond_to?(:call)
+    verified = validator.arity == 2 ? validator.call(jti, context.payload) : validator.call(jti)
+    raise(JWT::InvalidJtiError, 'Invalid jti') unless verified
+  elsif jti.to_s.strip.empty?
+    raise(JWT::InvalidJtiError, 'Missing jti')
+  end
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/NotBefore.html b/JWT/Claims/NotBefore.html new file mode 100644 index 000000000..4bc6c8f21 --- /dev/null +++ b/JWT/Claims/NotBefore.html @@ -0,0 +1,378 @@ + + + + + + + Class: JWT::Claims::NotBefore + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::NotBefore + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/not_before.rb
+
+ +
+ +

Overview

+
+ +

The NotBefore class is responsible for validating the ‘nbf’ (Not Before) claim in a JWT token.

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(leeway:) ⇒ NotBefore + + + + + +

+
+ +

Initializes a new NotBefore instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + leeway + + + (Integer) + + + + — +
    +

    the amount of leeway (in seconds) to allow when validating the ‘nbf’ claim. Defaults to 0.

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+10
+11
+12
+
+
# File 'lib/jwt/claims/not_before.rb', line 10
+
+def initialize(leeway:)
+  @leeway = leeway || 0
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #verify!(context:, **_args) ⇒ nil + + + + + +

+
+ +

Verifies the ‘nbf’ (Not Before) claim in the JWT token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + context + + + (Object) + + + + — +
    +

    the context containing the JWT payload.

    +
    + +
  • + +
  • + + _args + + + (Hash) + + + + — +
    +

    additional arguments (not used).

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+ + +
+ + + + +
+
+
+
+20
+21
+22
+23
+24
+25
+
+
# File 'lib/jwt/claims/not_before.rb', line 20
+
+def verify!(context:, **_args)
+  return unless context.payload.is_a?(Hash)
+  return unless context.payload.key?('nbf')
+
+  raise JWT::ImmatureSignature, 'Signature nbf has not been reached' if context.payload['nbf'].to_i > (Time.now.to_i + leeway)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/Numeric.html b/JWT/Claims/Numeric.html new file mode 100644 index 000000000..9c436e37e --- /dev/null +++ b/JWT/Claims/Numeric.html @@ -0,0 +1,265 @@ + + + + + + + Class: JWT::Claims::Numeric + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::Numeric + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/numeric.rb
+
+ +
+ +

Overview

+
+ +

The Numeric class is responsible for validating numeric claims in a JWT token. The numeric claims are: exp, iat and nbf

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + + + +
+

Instance Method Details

+ + +
+

+ + #verify!(context:) ⇒ nil + + + + + +

+
+ +

Verifies the numeric claims in the JWT context.

+ + +
+
+
+

Parameters:

+
    + +
  • + + context + + + (Object) + + + + — +
    +

    the context containing the JWT payload.

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+
    + +
  • + + + (JWT::InvalidClaimError) + + + + — +
    +

    if any numeric claim is invalid.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+22
+23
+24
+
+
# File 'lib/jwt/claims/numeric.rb', line 22
+
+def verify!(context:)
+  validate_numeric_claims(context.payload)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/Required.html b/JWT/Claims/Required.html new file mode 100644 index 000000000..bcf8b3b82 --- /dev/null +++ b/JWT/Claims/Required.html @@ -0,0 +1,380 @@ + + + + + + + Class: JWT::Claims::Required + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::Required + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/required.rb
+
+ +
+ +

Overview

+
+ +

The Required class is responsible for validating that all required claims are present in a JWT token.

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(required_claims:) ⇒ Required + + + + + +

+
+ +

Initializes a new Required instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + required_claims + + + (Array<String>) + + + + — +
    +

    the list of required claims.

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+10
+11
+12
+
+
# File 'lib/jwt/claims/required.rb', line 10
+
+def initialize(required_claims:)
+  @required_claims = required_claims
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #verify!(context:, **_args) ⇒ nil + + + + + +

+
+ +

Verifies that all required claims are present in the JWT payload.

+ + +
+
+
+

Parameters:

+
    + +
  • + + context + + + (Object) + + + + — +
    +

    the context containing the JWT payload.

    +
    + +
  • + +
  • + + _args + + + (Hash) + + + + — +
    +

    additional arguments (not used).

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+ + +
+ + + + +
+
+
+
+20
+21
+22
+23
+24
+25
+26
+
+
# File 'lib/jwt/claims/required.rb', line 20
+
+def verify!(context:, **_args)
+  required_claims.each do |required_claim|
+    next if context.payload.is_a?(Hash) && context.payload.key?(required_claim)
+
+    raise JWT::MissingRequiredClaim, "Missing required claim #{required_claim}"
+  end
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/Subject.html b/JWT/Claims/Subject.html new file mode 100644 index 000000000..8f32894dc --- /dev/null +++ b/JWT/Claims/Subject.html @@ -0,0 +1,374 @@ + + + + + + + Class: JWT::Claims::Subject + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::Subject + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/subject.rb
+
+ +
+ +

Overview

+
+ +

The Subject class is responsible for validating the subject claim (‘sub’) in a JWT token.

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(expected_subject:) ⇒ Subject + + + + + +

+
+ +

Initializes a new Subject instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + expected_subject + + + (String) + + + + — +
    +

    the expected subject for the JWT token.

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+10
+11
+12
+
+
# File 'lib/jwt/claims/subject.rb', line 10
+
+def initialize(expected_subject:)
+  @expected_subject = expected_subject.to_s
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #verify!(context:, **_args) ⇒ nil + + + + + +

+
+ +

Verifies the subject claim (‘sub’) in the JWT token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + context + + + (Object) + + + + — +
    +

    the context containing the JWT payload.

    +
    + +
  • + +
  • + + _args + + + (Hash) + + + + — +
    +

    additional arguments (not used).

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+ + +
+ + + + +
+
+
+
+20
+21
+22
+23
+
+
# File 'lib/jwt/claims/subject.rb', line 20
+
+def verify!(context:, **_args)
+  sub = context.payload['sub']
+  raise(JWT::InvalidSubError, "Invalid subject. Expected #{expected_subject}, received #{sub || '<none>'}") unless sub.to_s == expected_subject
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/VerificationContext.html b/JWT/Claims/VerificationContext.html new file mode 100644 index 000000000..2c59c02a5 --- /dev/null +++ b/JWT/Claims/VerificationContext.html @@ -0,0 +1,237 @@ + + + + + + + Class: JWT::Claims::VerificationContext + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Claims::VerificationContext + + + Private +

+
+ +
+
Inherits:
+
+ Struct + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/decode_verifier.rb
+
+ +
+ +

Overview

+
+

+ This class is part of a private API. + You should avoid using this class if possible, as it may be removed or be changed in the future. +

+ +

Context class to contain the data passed to individual claim validators

+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #payload ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute payload.

    +
    + +
  • + + +
+ + + + + + +
+

Instance Attribute Details

+ + + +
+

+ + #payloadObject + + + + + +

+
+ +

Returns the value of attribute payload

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Object) + + + + — +
    +

    the current value of payload

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+8
+9
+10
+
+
# File 'lib/jwt/claims/decode_verifier.rb', line 8
+
+def payload
+  @payload
+end
+
+
+ +
+ + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Claims/Verifier.html b/JWT/Claims/Verifier.html new file mode 100644 index 000000000..3d939369c --- /dev/null +++ b/JWT/Claims/Verifier.html @@ -0,0 +1,283 @@ + + + + + + + Module: JWT::Claims::Verifier + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT::Claims::Verifier + + + Private +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/claims/verifier.rb
+
+ +
+ +
+
+

+ This module is part of a private API. + You should avoid using this module if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .errors(context, *options) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+32
+33
+34
+35
+36
+37
+38
+39
+40
+
+
# File 'lib/jwt/claims/verifier.rb', line 32
+
+def errors(context, *options)
+  errors = []
+  iterate_verifiers(*options) do |verifier, verifier_options|
+    verify_one!(context, verifier, verifier_options)
+  rescue ::JWT::DecodeError => e
+    errors << Error.new(message: e.message)
+  end
+  errors
+end
+
+
+ +
+

+ + .verify!(context, *options) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+24
+25
+26
+27
+28
+29
+
+
# File 'lib/jwt/claims/verifier.rb', line 24
+
+def verify!(context, *options)
+  iterate_verifiers(*options) do |verifier, verifier_options|
+    verify_one!(context, verifier, verifier_options)
+  end
+  nil
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Configuration.html b/JWT/Configuration.html new file mode 100644 index 000000000..2c79e715d --- /dev/null +++ b/JWT/Configuration.html @@ -0,0 +1,336 @@ + + + + + + + Module: JWT::Configuration + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT::Configuration + + + +

+
+ + + + + + + + + +
+
Included in:
+
JWT
+
+ + + +
+
Defined in:
+
lib/jwt/configuration.rb,
+ lib/jwt/configuration/container.rb,
lib/jwt/configuration/jwk_configuration.rb,
lib/jwt/configuration/decode_configuration.rb
+
+
+ +
+ +

Overview

+
+ +

The Configuration module provides methods to configure JWT settings.

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + + + Classes: Container, DecodeConfiguration, JwkConfiguration + + +

+ + + + + + + + +

+ Instance Method Summary + collapse +

+ + + + + + +
+

Instance Method Details

+ + +
+

+ + #configurationJWT::Configuration::Container + + + + + +

+
+ +

Returns the JWT configuration container.

+ + +
+
+
+ +

Returns:

+ + +
+ + + + +
+
+
+
+19
+20
+21
+
+
# File 'lib/jwt/configuration.rb', line 19
+
+def configuration
+  @configuration ||= ::JWT::Configuration::Container.new
+end
+
+
+ +
+

+ + #configure {|config| ... } ⇒ Object + + + + + +

+
+ +

Configures the JWT settings.

+ + +
+
+
+ +

Yields:

+
    + +
  • + + + (config) + + + + — +
    +

    Gives the current configuration to the block.

    +
    + +
  • + +
+

Yield Parameters:

+ + +
+ + + + +
+
+
+
+12
+13
+14
+
+
# File 'lib/jwt/configuration.rb', line 12
+
+def configure
+  yield(configuration)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Configuration/Container.html b/JWT/Configuration/Container.html new file mode 100644 index 000000000..b73a66e82 --- /dev/null +++ b/JWT/Configuration/Container.html @@ -0,0 +1,627 @@ + + + + + + + Class: JWT::Configuration::Container + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Configuration::Container + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/configuration/container.rb
+
+ +
+ +

Overview

+
+ +

The Container class holds the configuration settings for JWT.

+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+ + + + + + +

+ Instance Method Summary + collapse +

+ +
    + +
  • + + + #initialize ⇒ Container + + + + + + + constructor + + + + + + + + +
    +

    Initializes a new Container instance and resets the configuration.

    +
    + +
  • + + +
  • + + + #reset! ⇒ void + + + + + + + + + + + + + +
    +

    Resets the configuration to default values.

    +
    + +
  • + + +
+ + +
+

Constructor Details

+ +
+

+ + #initializeContainer + + + + + +

+
+ +

Initializes a new Container instance and resets the configuration.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+23
+24
+25
+
+
# File 'lib/jwt/configuration/container.rb', line 23
+
+def initialize
+  reset!
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #decodeDecodeConfiguration + + + + + +

+
+ +

Returns the decode configuration.

+ + +
+
+
+ +

Returns:

+ + +
+ + + + +
+
+
+
+16
+17
+18
+
+
# File 'lib/jwt/configuration/container.rb', line 16
+
+def decode
+  @decode
+end
+
+
+ + + +
+

+ + #deprecation_warningsObject + + + + + +

+
+ +

Returns the value of attribute deprecation_warnings.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+20
+21
+22
+
+
# File 'lib/jwt/configuration/container.rb', line 20
+
+def deprecation_warnings
+  @deprecation_warnings
+end
+
+
+ + + +
+

+ + #jwkJwkConfiguration + + + + + +

+
+ +

Returns the JWK configuration.

+ + +
+
+
+ +

Returns:

+ + +
+ + + + +
+
+
+
+16
+
+
# File 'lib/jwt/configuration/container.rb', line 16
+
+attr_accessor :decode, :jwk, :strict_base64_decoding
+
+
+ + + +
+

+ + #strict_base64_decodingBoolean + + + + + +

+
+ +

Returns whether strict Base64 decoding is enabled.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    whether strict Base64 decoding is enabled.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+16
+
+
# File 'lib/jwt/configuration/container.rb', line 16
+
+attr_accessor :decode, :jwk, :strict_base64_decoding
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #reset!void + + + + + +

+
+

This method returns an undefined value.

+

Resets the configuration to default values.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+30
+31
+32
+33
+34
+35
+
+
# File 'lib/jwt/configuration/container.rb', line 30
+
+def reset!
+  @decode                 = DecodeConfiguration.new
+  @jwk                    = JwkConfiguration.new
+
+  self.deprecation_warnings = :once
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Configuration/DecodeConfiguration.html b/JWT/Configuration/DecodeConfiguration.html new file mode 100644 index 000000000..479bc5a6d --- /dev/null +++ b/JWT/Configuration/DecodeConfiguration.html @@ -0,0 +1,1341 @@ + + + + + + + Class: JWT::Configuration::DecodeConfiguration + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Configuration::DecodeConfiguration + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/configuration/decode_configuration.rb
+
+ +
+ +

Overview

+
+ +

The DecodeConfiguration class holds the configuration settings for decoding JWT tokens.

+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+ + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initializeDecodeConfiguration + + + + + +

+
+ +

Initializes a new DecodeConfiguration instance with default settings.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 40
+
+def initialize
+  @verify_expiration = true
+  @verify_not_before = true
+  @verify_iss = false
+  @verify_iat = false
+  @verify_jti = false
+  @verify_aud = false
+  @verify_sub = false
+  @leeway = 0
+  @algorithms = ['HS256']
+  @required_claims = []
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #algorithmsArray<String> + + + + + +

+
+ +

Returns the list of acceptable algorithms.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Array<String>) + + + + — +
    +

    the list of acceptable algorithms.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 28
+
+attr_accessor :verify_expiration,
+:verify_not_before,
+:verify_iss,
+:verify_iat,
+:verify_jti,
+:verify_aud,
+:verify_sub,
+:leeway,
+:algorithms,
+:required_claims
+
+
+ + + +
+

+ + #leewayInteger + + + + + +

+
+ +

Returns the leeway in seconds for time-based claims.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Integer) + + + + — +
    +

    the leeway in seconds for time-based claims.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 28
+
+attr_accessor :verify_expiration,
+:verify_not_before,
+:verify_iss,
+:verify_iat,
+:verify_jti,
+:verify_aud,
+:verify_sub,
+:leeway,
+:algorithms,
+:required_claims
+
+
+ + + +
+

+ + #required_claimsArray<String> + + + + + +

+
+ +

Returns the list of required claims.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Array<String>) + + + + — +
    +

    the list of required claims.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 28
+
+attr_accessor :verify_expiration,
+:verify_not_before,
+:verify_iss,
+:verify_iat,
+:verify_jti,
+:verify_aud,
+:verify_sub,
+:leeway,
+:algorithms,
+:required_claims
+
+
+ + + +
+

+ + #verify_audBoolean + + + + + +

+
+ +

Returns whether to verify the audience claim.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    whether to verify the audience claim.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 28
+
+attr_accessor :verify_expiration,
+:verify_not_before,
+:verify_iss,
+:verify_iat,
+:verify_jti,
+:verify_aud,
+:verify_sub,
+:leeway,
+:algorithms,
+:required_claims
+
+
+ + + +
+

+ + #verify_expirationBoolean + + + + + +

+
+ +

Returns whether to verify the expiration claim.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    whether to verify the expiration claim.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+28
+29
+30
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 28
+
+def verify_expiration
+  @verify_expiration
+end
+
+
+ + + +
+

+ + #verify_iatBoolean + + + + + +

+
+ +

Returns whether to verify the issued at claim.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    whether to verify the issued at claim.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 28
+
+attr_accessor :verify_expiration,
+:verify_not_before,
+:verify_iss,
+:verify_iat,
+:verify_jti,
+:verify_aud,
+:verify_sub,
+:leeway,
+:algorithms,
+:required_claims
+
+
+ + + +
+

+ + #verify_issBoolean + + + + + +

+
+ +

Returns whether to verify the issuer claim.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    whether to verify the issuer claim.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 28
+
+attr_accessor :verify_expiration,
+:verify_not_before,
+:verify_iss,
+:verify_iat,
+:verify_jti,
+:verify_aud,
+:verify_sub,
+:leeway,
+:algorithms,
+:required_claims
+
+
+ + + +
+

+ + #verify_jtiBoolean + + + + + +

+
+ +

Returns whether to verify the JWT ID claim.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    whether to verify the JWT ID claim.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 28
+
+attr_accessor :verify_expiration,
+:verify_not_before,
+:verify_iss,
+:verify_iat,
+:verify_jti,
+:verify_aud,
+:verify_sub,
+:leeway,
+:algorithms,
+:required_claims
+
+
+ + + +
+

+ + #verify_not_beforeBoolean + + + + + +

+
+ +

Returns whether to verify the not before claim.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    whether to verify the not before claim.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 28
+
+attr_accessor :verify_expiration,
+:verify_not_before,
+:verify_iss,
+:verify_iat,
+:verify_jti,
+:verify_aud,
+:verify_sub,
+:leeway,
+:algorithms,
+:required_claims
+
+
+ + + +
+

+ + #verify_subBoolean + + + + + +

+
+ +

Returns whether to verify the subject claim.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    whether to verify the subject claim.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 28
+
+attr_accessor :verify_expiration,
+:verify_not_before,
+:verify_iss,
+:verify_iat,
+:verify_jti,
+:verify_aud,
+:verify_sub,
+:leeway,
+:algorithms,
+:required_claims
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #to_hObject + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+
+
# File 'lib/jwt/configuration/decode_configuration.rb', line 54
+
+def to_h
+  {
+    verify_expiration: verify_expiration,
+    verify_not_before: verify_not_before,
+    verify_iss: verify_iss,
+    verify_iat: verify_iat,
+    verify_jti: verify_jti,
+    verify_aud: verify_aud,
+    verify_sub: verify_sub,
+    leeway: leeway,
+    algorithms: algorithms,
+    required_claims: required_claims
+  }
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Configuration/JwkConfiguration.html b/JWT/Configuration/JwkConfiguration.html new file mode 100644 index 000000000..f62a863ff --- /dev/null +++ b/JWT/Configuration/JwkConfiguration.html @@ -0,0 +1,385 @@ + + + + + + + Class: JWT::Configuration::JwkConfiguration + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Configuration::JwkConfiguration + + + Private +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/configuration/jwk_configuration.rb
+
+ +
+ +
+
+

+ This class is part of a private API. + You should avoid using this class if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+ + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initializeJwkConfiguration + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ +

Returns a new instance of JwkConfiguration.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+10
+11
+12
+
+
# File 'lib/jwt/configuration/jwk_configuration.rb', line 10
+
+def initialize
+  self.kid_generator_type = :key_digest
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #kid_generatorObject + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+25
+26
+27
+
+
# File 'lib/jwt/configuration/jwk_configuration.rb', line 25
+
+def kid_generator
+  @kid_generator
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #kid_generator_type=(value) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
+
# File 'lib/jwt/configuration/jwk_configuration.rb', line 14
+
+def kid_generator_type=(value)
+  self.kid_generator = case value
+                       when :key_digest
+                         JWT::JWK::KidAsKeyDigest
+                       when :rfc7638_thumbprint
+                         JWT::JWK::Thumbprint
+                       else
+                         raise ArgumentError, "#{value} is not a valid kid generator type."
+                       end
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Decode.html b/JWT/Decode.html new file mode 100644 index 000000000..3ce65651d --- /dev/null +++ b/JWT/Decode.html @@ -0,0 +1,463 @@ + + + + + + + Class: JWT::Decode + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Decode + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/decode.rb
+
+ +
+ +

Overview

+
+ +

The Decode class is responsible for decoding and verifying JWT tokens.

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
ALGORITHM_KEYS = +
+
+ +

Order is very important - first check for string keys, next for symbols

+ + +
+
+
+ + +
+
+
['algorithm',
+:algorithm,
+'algorithms',
+:algorithms].freeze
+ +
+ + + + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(jwt, key, verify, options, &keyfinder) ⇒ Decode + + + + + +

+
+ +

Initializes a new Decode instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + jwt + + + (String) + + + + — +
    +

    the JWT to decode.

    +
    + +
  • + +
  • + + key + + + (String, Array<String>) + + + + — +
    +

    the key(s) to use for verification.

    +
    + +
  • + +
  • + + verify + + + (Boolean) + + + + — +
    +

    whether to verify the token’s signature.

    +
    + +
  • + +
  • + + options + + + (Hash) + + + + — +
    +

    additional options for decoding and verification.

    +
    + +
  • + +
  • + + keyfinder + + + (Proc) + + + + — +
    +

    an optional key finder block to dynamically find the key for verification.

    +
    + +
  • + +
+ +

Raises:

+
    + +
  • + + + (JWT::DecodeError) + + + + — +
    +

    if decoding or verification fails.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+22
+23
+24
+25
+26
+27
+28
+29
+30
+
+
# File 'lib/jwt/decode.rb', line 22
+
+def initialize(jwt, key, verify, options, &keyfinder)
+  raise JWT::DecodeError, 'Nil JSON web token' unless jwt
+
+  @token = EncodedToken.new(jwt)
+  @key = key
+  @options = options
+  @verify = verify
+  @keyfinder = keyfinder
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #decode_segmentsArray<Hash> + + + + + +

+
+ +

Decodes the JWT token and verifies its segments if verification is enabled.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Array<Hash>) + + + + — +
    +

    an array containing the decoded payload and header.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+
+
# File 'lib/jwt/decode.rb', line 35
+
+def decode_segments
+  validate_segment_count!
+  if @verify
+    verify_algo
+    set_key
+    verify_signature
+    Claims::DecodeVerifier.verify!(token.unverified_payload, @options)
+  end
+
+  [token.unverified_payload, token.header]
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/DecodeError.html b/JWT/DecodeError.html new file mode 100644 index 000000000..1670c47e7 --- /dev/null +++ b/JWT/DecodeError.html @@ -0,0 +1,139 @@ + + + + + + + Exception: JWT::DecodeError + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::DecodeError + + + +

+
+ +
+
Inherits:
+
+ StandardError + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The DecodeError class is raised when there is an error decoding a JWT.

+ + +
+
+
+ + +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Encode.html b/JWT/Encode.html new file mode 100644 index 000000000..fe9b60701 --- /dev/null +++ b/JWT/Encode.html @@ -0,0 +1,392 @@ + + + + + + + Class: JWT::Encode + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Encode + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/encode.rb
+
+ +
+ +

Overview

+
+ +

The Encode class is responsible for encoding JWT tokens.

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(options) ⇒ Encode + + + + + +

+
+ +

Initializes a new Encode instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + options + + + (Hash) + + + + — +
    +

    the options for encoding the JWT token.

    +
    + +
  • + +
+ + + + +

Options Hash (options):

+
    + +
  • + :payload + (Hash) + + + + + —
    +

    the payload of the JWT token.

    +
    + +
  • + +
  • + :headers + (Hash) + + + + + —
    +

    the headers of the JWT token.

    +
    + +
  • + +
  • + :key + (String) + + + + + —
    +

    the key used to sign the JWT token.

    +
    + +
  • + +
  • + :algorithm + (String) + + + + + —
    +

    the algorithm used to sign the JWT token.

    +
    + +
  • + +
+ + + +
+ + + + +
+
+
+
+15
+16
+17
+18
+19
+
+
# File 'lib/jwt/encode.rb', line 15
+
+def initialize(options)
+  @token     = Token.new(payload: options[:payload], header: options[:headers])
+  @key       = options[:key]
+  @algorithm = options[:algorithm]
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #segmentsString + + + + + +

+
+ +

Encodes the JWT token and returns its segments.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the encoded JWT token.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+24
+25
+26
+27
+28
+
+
# File 'lib/jwt/encode.rb', line 24
+
+def segments
+  @token.verify_claims!(:numeric)
+  @token.sign!(algorithm: @algorithm, key: @key)
+  @token.jwt
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/EncodeError.html b/JWT/EncodeError.html new file mode 100644 index 000000000..7ef5970ff --- /dev/null +++ b/JWT/EncodeError.html @@ -0,0 +1,135 @@ + + + + + + + Exception: JWT::EncodeError + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::EncodeError + + + +

+
+ +
+
Inherits:
+
+ StandardError + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The EncodeError class is raised when there is an error encoding a JWT.

+ + +
+
+
+ + +
+ + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/EncodedToken.html b/JWT/EncodedToken.html new file mode 100644 index 000000000..e071d7031 --- /dev/null +++ b/JWT/EncodedToken.html @@ -0,0 +1,2025 @@ + + + + + + + Class: JWT::EncodedToken + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::EncodedToken + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/encoded_token.rb
+
+ +
+ +

Overview

+
+ +

Represents an encoded JWT token

+ +

Processing an encoded and signed token:

+ +
token = JWT::Token.new(payload: {pay: 'load'})
+token.sign!(algorithm: 'HS256', key: 'secret')
+
+encoded_token = JWT::EncodedToken.new(token.jwt)
+encoded_token.verify_signature!(algorithm: 'HS256', key: 'secret')
+encoded_token.payload # => {'pay' => 'load'}
+
+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #encoded_header ⇒ String + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the encoded header of the JWT token.

    +
    + +
  • + + +
  • + + + #encoded_payload ⇒ String + + + + + + + + + + + + + + + + +
    +

    Sets or returns the encoded payload of the JWT token.

    +
    + +
  • + + +
  • + + + #encoded_signature ⇒ String + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the encoded signature of the JWT token.

    +
    + +
  • + + +
  • + + + #jwt ⇒ String + + + + (also: #to_s) + + + + + + + readonly + + + + + + + + + +
    +

    Returns the original token provided to the class.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(jwt) ⇒ EncodedToken + + + + + +

+
+ +

Initializes a new EncodedToken instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + jwt + + + (String) + + + + — +
    +

    the encoded JWT token.

    +
    + +
  • + +
+ +

Raises:

+
    + +
  • + + + (ArgumentError) + + + + — +
    +

    if the provided JWT is not a String.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+43
+44
+45
+46
+47
+48
+49
+50
+51
+
+
# File 'lib/jwt/encoded_token.rb', line 43
+
+def initialize(jwt)
+  raise ArgumentError, 'Provided JWT must be a String' unless jwt.is_a?(String)
+
+  @jwt = jwt
+  @signature_verified = false
+  @claims_verified    = false
+
+  @encoded_header, @encoded_payload, @encoded_signature = jwt.split('.')
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #encoded_headerString (readonly) + + + + + +

+
+ +

Returns the encoded header of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the encoded header.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+75
+76
+77
+
+
# File 'lib/jwt/encoded_token.rb', line 75
+
+def encoded_header
+  @encoded_header
+end
+
+
+ + + +
+

+ + #encoded_payloadString + + + + + +

+
+ +

Sets or returns the encoded payload of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the encoded payload.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+97
+98
+99
+
+
# File 'lib/jwt/encoded_token.rb', line 97
+
+def encoded_payload
+  @encoded_payload
+end
+
+
+ + + +
+

+ + #encoded_signatureString (readonly) + + + + + +

+
+ +

Returns the encoded signature of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the encoded signature.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+63
+64
+65
+
+
# File 'lib/jwt/encoded_token.rb', line 63
+
+def encoded_signature
+  @encoded_signature
+end
+
+
+ + + +
+

+ + #jwtString (readonly) + + + + Also known as: + to_s + + + + +

+
+ +

Returns the original token provided to the class.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    The JWT token.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+37
+38
+39
+
+
# File 'lib/jwt/encoded_token.rb', line 37
+
+def jwt
+  @jwt
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #claim_errors(*options) ⇒ Array<Symbol> + + + + + +

+
+ +

Returns the errors of the claims of the token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + options + + + (Array<Symbol>, Hash) + + + + — +
    +

    the claims to verify. By default, it checks the ‘exp’ claim.

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Array<Symbol>) + + + + — +
    +

    the errors of the claims.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+182
+183
+184
+
+
# File 'lib/jwt/encoded_token.rb', line 182
+
+def claim_errors(*options)
+  Claims::Verifier.errors(ClaimsContext.new(self), *claims_options(options))
+end
+
+
+ +
+

+ + #headerHash + + + + + +

+
+ +

Returns the decoded header of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Hash) + + + + — +
    +

    the header.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+68
+69
+70
+
+
# File 'lib/jwt/encoded_token.rb', line 68
+
+def header
+  @header ||= parse_and_decode(@encoded_header)
+end
+
+
+ +
+

+ + #payloadHash + + + + + +

+
+ +

Returns the payload of the JWT token. Access requires the signature and claims to have been verified.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Hash) + + + + — +
    +

    the payload.

    +
    + +
  • + +
+

Raises:

+
    + +
  • + + + (JWT::DecodeError) + + + + — +
    +

    if the signature has not been verified.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+81
+82
+83
+84
+85
+86
+
+
# File 'lib/jwt/encoded_token.rb', line 81
+
+def payload
+  raise JWT::DecodeError, 'Verify the token signature before accessing the payload' unless @signature_verified
+  raise JWT::DecodeError, 'Verify the token claims before accessing the payload' unless @claims_verified
+
+  decoded_payload
+end
+
+
+ +
+

+ + #signatureString + + + + + +

+
+ +

Returns the decoded signature of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the decoded signature.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+56
+57
+58
+
+
# File 'lib/jwt/encoded_token.rb', line 56
+
+def signature
+  @signature ||= ::JWT::Base64.url_decode(encoded_signature || '')
+end
+
+
+ +
+

+ + #signing_inputString + + + + + +

+
+ +

Returns the signing input of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the signing input.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+102
+103
+104
+
+
# File 'lib/jwt/encoded_token.rb', line 102
+
+def signing_input
+  [encoded_header, encoded_payload].join('.')
+end
+
+
+ +
+

+ + #unverified_payloadHash + + + + + +

+
+ +

Returns the payload of the JWT token without requiring the signature to have been verified.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Hash) + + + + — +
    +

    the payload.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+90
+91
+92
+
+
# File 'lib/jwt/encoded_token.rb', line 90
+
+def unverified_payload
+  decoded_payload
+end
+
+
+ +
+

+ + #valid?(signature:, claims: nil) ⇒ Boolean + + + + + +

+
+ +

Returns true if the signature and claims are valid, false otherwise.

+ + +
+
+
+

Parameters:

+
    + +
  • + + signature + + + (Hash) + + + + — +
    +

    the parameters for signature verification (see #verify_signature!).

    +
    + +
  • + +
  • + + claims + + + (Array<Symbol>, Hash) + + + (defaults to: nil) + + + — +
    +

    the claims to verify (see #verify_claims!).

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    true if the signature and claims are valid, false otherwise.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+128
+129
+130
+131
+
+
# File 'lib/jwt/encoded_token.rb', line 128
+
+def valid?(signature:, claims: nil)
+  valid_signature?(**signature) &&
+    (claims.is_a?(Array) ? valid_claims?(*claims) : valid_claims?(claims))
+end
+
+
+ +
+

+ + #valid_claims?(*options) ⇒ Boolean + + + + + +

+
+ +

Returns whether the claims of the token are valid.

+ + +
+
+
+

Parameters:

+
    + +
  • + + options + + + (Array<Symbol>, Hash) + + + + — +
    +

    the claims to verify. By default, it checks the ‘exp’ claim.

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    whether the claims are valid.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+189
+190
+191
+
+
# File 'lib/jwt/encoded_token.rb', line 189
+
+def valid_claims?(*options)
+  claim_errors(*claims_options(options)).empty?.tap { |verified| @claims_verified = verified }
+end
+
+
+ +
+

+ + #valid_signature?(algorithm: nil, key: nil, key_finder: nil) ⇒ Boolean + + + + + +

+
+ +

Checks if the signature of the JWT token is valid.

+ + +
+
+
+

Parameters:

+
    + +
  • + + algorithm + + + (String, Array<String>, Object, Array<Object>) + + + (defaults to: nil) + + + — +
    +

    the algorithm(s) to use for verification.

    +
    + +
  • + +
  • + + key + + + (String, Array<String>, JWT::JWK::KeyBase, Array<JWT::JWK::KeyBase>) + + + (defaults to: nil) + + + — +
    +

    the key(s) to use for verification.

    +
    + +
  • + +
  • + + key_finder + + + (#call) + + + (defaults to: nil) + + + — +
    +

    an object responding to ‘call` to find the key for verification.

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    true if the signature is valid, false otherwise.

    +
    + +
  • + +
+

Raises:

+
    + +
  • + + + (ArgumentError) + + + +
  • + +
+ +
+ + + + +
+
+
+
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+
+
# File 'lib/jwt/encoded_token.rb', line 153
+
+def valid_signature?(algorithm: nil, key: nil, key_finder: nil)
+  raise ArgumentError, 'Provide either key or key_finder, not both or neither' if key.nil? == key_finder.nil?
+
+  keys      = Array(key || key_finder.call(self))
+  verifiers = JWA.create_verifiers(algorithms: algorithm, keys: keys, preferred_algorithm: header['alg'])
+
+  raise JWT::VerificationError, 'No algorithm provided' if verifiers.empty?
+
+  valid = verifiers.any? do |jwa|
+    jwa.verify(data: signing_input, signature: signature)
+  end
+  valid.tap { |verified| @signature_verified = verified }
+end
+
+
+ +
+

+ + #verify!(signature:, claims: nil) ⇒ nil + + + + + +

+
+ +

Verifies the token signature and claims. By default it verifies the ‘exp’ claim.

+ + +
+
+
+ +
+

Examples:

+ + +
encoded_token.verify!(signature: { algorithm: 'HS256', key: 'secret' }, claims: [:exp])
+ +
+

Parameters:

+
    + +
  • + + signature + + + (Hash) + + + + — +
    +

    the parameters for signature verification (see #verify_signature!).

    +
    + +
  • + +
  • + + claims + + + (Array<Symbol>, Hash) + + + (defaults to: nil) + + + — +
    +

    the claims to verify (see #verify_claims!).

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+
    + +
  • + + + (JWT::DecodeError) + + + + — +
    +

    if the signature or claim verification fails.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+116
+117
+118
+119
+120
+
+
# File 'lib/jwt/encoded_token.rb', line 116
+
+def verify!(signature:, claims: nil)
+  verify_signature!(**signature)
+  claims.is_a?(Array) ? verify_claims!(*claims) : verify_claims!(claims)
+  nil
+end
+
+
+ +
+

+ + #verify_claims!(*options) ⇒ Object + + + + + +

+
+ +

Verifies the claims of the token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + options + + + (Array<Symbol>, Hash) + + + + — +
    +

    the claims to verify. By default, it checks the ‘exp’ claim.

    +
    + +
  • + +
+ +

Raises:

+
    + +
  • + + + (JWT::DecodeError) + + + + — +
    +

    if the claims are invalid.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+170
+171
+172
+173
+174
+175
+176
+177
+
+
# File 'lib/jwt/encoded_token.rb', line 170
+
+def verify_claims!(*options)
+  Claims::Verifier.verify!(ClaimsContext.new(self), *claims_options(options)).tap do
+    @claims_verified = true
+  end
+rescue StandardError
+  @claims_verified = false
+  raise
+end
+
+
+ +
+

+ + #verify_signature!(algorithm:, key: nil, key_finder: nil) ⇒ nil + + + + + +

+
+ +

Verifies the signature of the JWT token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + algorithm + + + (String, Array<String>, Object, Array<Object>) + + + + — +
    +

    the algorithm(s) to use for verification.

    +
    + +
  • + +
  • + + key + + + (String, Array<String>) + + + (defaults to: nil) + + + — +
    +

    the key(s) to use for verification.

    +
    + +
  • + +
  • + + key_finder + + + (#call) + + + (defaults to: nil) + + + — +
    +

    an object responding to ‘call` to find the key for verification.

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+

Raises:

+
    + +
  • + + + (JWT::VerificationError) + + + + — +
    +

    if the signature verification fails.

    +
    + +
  • + +
  • + + + (ArgumentError) + + + + — +
    +

    if neither key nor key_finder is provided, or if both are provided.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+141
+142
+143
+144
+145
+
+
# File 'lib/jwt/encoded_token.rb', line 141
+
+def verify_signature!(algorithm:, key: nil, key_finder: nil)
+  return if valid_signature?(algorithm: algorithm, key: key, key_finder: key_finder)
+
+  raise JWT::VerificationError, 'Signature verification failed'
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/ExpiredSignature.html b/JWT/ExpiredSignature.html new file mode 100644 index 000000000..e132e12e7 --- /dev/null +++ b/JWT/ExpiredSignature.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::ExpiredSignature + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::ExpiredSignature + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The ExpiredSignature class is raised when the JWT signature has expired.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/ImmatureSignature.html b/JWT/ImmatureSignature.html new file mode 100644 index 000000000..69a70cd37 --- /dev/null +++ b/JWT/ImmatureSignature.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::ImmatureSignature + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::ImmatureSignature + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The ImmatureSignature class is raised when the JWT signature is immature.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/IncorrectAlgorithm.html b/JWT/IncorrectAlgorithm.html new file mode 100644 index 000000000..a01ec71fc --- /dev/null +++ b/JWT/IncorrectAlgorithm.html @@ -0,0 +1,147 @@ + + + + + + + Exception: JWT::IncorrectAlgorithm + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::IncorrectAlgorithm + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The IncorrectAlgorithm class is raised when the JWT algorithm is incorrect.

+ + +
+
+
+ + +
+

Direct Known Subclasses

+

UnsupportedEcdsaCurve

+
+ + + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/InvalidAudError.html b/JWT/InvalidAudError.html new file mode 100644 index 000000000..5a45cc2a5 --- /dev/null +++ b/JWT/InvalidAudError.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::InvalidAudError + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::InvalidAudError + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The InvalidAudError class is raised when the JWT audience (aud) claim is invalid.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/InvalidCritError.html b/JWT/InvalidCritError.html new file mode 100644 index 000000000..e10938fdb --- /dev/null +++ b/JWT/InvalidCritError.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::InvalidCritError + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::InvalidCritError + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The InvalidCritError class is raised when the JWT crit header is invalid.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/InvalidIatError.html b/JWT/InvalidIatError.html new file mode 100644 index 000000000..88c70c181 --- /dev/null +++ b/JWT/InvalidIatError.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::InvalidIatError + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::InvalidIatError + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The InvalidIatError class is raised when the JWT issued at (iat) claim is invalid.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/InvalidIssuerError.html b/JWT/InvalidIssuerError.html new file mode 100644 index 000000000..1b4b8bf13 --- /dev/null +++ b/JWT/InvalidIssuerError.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::InvalidIssuerError + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::InvalidIssuerError + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The InvalidIssuerError class is raised when the JWT issuer is invalid.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/InvalidJtiError.html b/JWT/InvalidJtiError.html new file mode 100644 index 000000000..2bbc4adaf --- /dev/null +++ b/JWT/InvalidJtiError.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::InvalidJtiError + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::InvalidJtiError + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The InvalidJtiError class is raised when the JWT ID (jti) claim is invalid.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/InvalidPayload.html b/JWT/InvalidPayload.html new file mode 100644 index 000000000..8731490bd --- /dev/null +++ b/JWT/InvalidPayload.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::InvalidPayload + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::InvalidPayload + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The InvalidPayload class is raised when the JWT payload is invalid.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/InvalidSubError.html b/JWT/InvalidSubError.html new file mode 100644 index 000000000..09bea19f9 --- /dev/null +++ b/JWT/InvalidSubError.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::InvalidSubError + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::InvalidSubError + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The InvalidSubError class is raised when the JWT subject (sub) claim is invalid.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JSON.html b/JWT/JSON.html new file mode 100644 index 000000000..834e53d65 --- /dev/null +++ b/JWT/JSON.html @@ -0,0 +1,281 @@ + + + + + + + Class: JWT::JSON + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JSON + + + Private +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/json.rb
+
+ +
+ +
+
+

+ This class is part of a private API. + You should avoid using this class if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .generate(data) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/jwt/json.rb', line 9
+
+def generate(data)
+  ::JSON.generate(data)
+end
+
+
+ +
+

+ + .parse(data) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+13
+14
+15
+
+
# File 'lib/jwt/json.rb', line 13
+
+def parse(data)
+  ::JSON.parse(data)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA.html b/JWT/JWA.html new file mode 100644 index 000000000..ebacb870d --- /dev/null +++ b/JWT/JWA.html @@ -0,0 +1,662 @@ + + + + + + + Module: JWT::JWA + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT::JWA + + + +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwa.rb,
+ lib/jwt/jwa/ps.rb,
lib/jwt/jwa/rsa.rb,
lib/jwt/jwa/hmac.rb,
lib/jwt/jwa/none.rb,
lib/jwt/jwa/ecdsa.rb,
lib/jwt/jwa/unsupported.rb,
lib/jwt/jwa/signing_algorithm.rb
+
+
+ +
+ +

Overview

+
+ +

JSON Web Algorithms

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + Modules: SigningAlgorithm, Unsupported + + + + Classes: Ecdsa, Hmac, None, Ps, Rsa, SignerContext, VerifierContext + + +

+ + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .create_signer(algorithm:, key:) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+66
+67
+68
+69
+70
+71
+72
+73
+74
+
+
# File 'lib/jwt/jwa.rb', line 66
+
+def create_signer(algorithm:, key:)
+  if key.is_a?(JWK::KeyBase)
+    validate_jwk_algorithms!(key, algorithm, DecodeError)
+
+    return key
+  end
+
+  SignerContext.new(jwa: resolve(algorithm), key: key)
+end
+
+
+ +
+

+ + .create_verifiers(algorithms:, keys:, preferred_algorithm:) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+77
+78
+79
+80
+81
+82
+83
+84
+85
+
+
# File 'lib/jwt/jwa.rb', line 77
+
+def create_verifiers(algorithms:, keys:, preferred_algorithm:)
+  jwks, other_keys = keys.partition { |key| key.is_a?(JWK::KeyBase) }
+
+  validate_jwk_algorithms!(jwks, algorithms, VerificationError)
+
+  jwks + resolve_and_sort(algorithms: algorithms,
+                          preferred_algorithm: preferred_algorithm)
+         .map { |jwa| VerifierContext.new(jwa: jwa, keys: other_keys) }
+end
+
+
+ +
+

+ + .find(algo) ⇒ Object + + + + + +

+ + + + +
+
+
+
+51
+52
+53
+
+
# File 'lib/jwt/jwa/signing_algorithm.rb', line 51
+
+def find(algo)
+  algorithms.fetch(algo.to_s.downcase, Unsupported)
+end
+
+
+ +
+

+ + .register_algorithm(algo) ⇒ Object + + + + + +

+ + + + +
+
+
+
+47
+48
+49
+
+
# File 'lib/jwt/jwa/signing_algorithm.rb', line 47
+
+def register_algorithm(algo)
+  algorithms[algo.alg.to_s.downcase] = algo
+end
+
+
+ +
+

+ + .resolve(algorithm) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ +

Raises:

+
    + +
  • + + + (ArgumentError) + + + +
  • + +
+ +
+ + + + +
+
+
+
+48
+49
+50
+51
+52
+53
+54
+55
+56
+
+
# File 'lib/jwt/jwa.rb', line 48
+
+def resolve(algorithm)
+  return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol)
+
+  raise ArgumentError, 'Algorithm must be provided' if algorithm.nil?
+
+  raise ArgumentError, 'Custom algorithms are required to include JWT::JWA::SigningAlgorithm' unless algorithm.is_a?(SigningAlgorithm)
+
+  algorithm
+end
+
+
+ +
+

+ + .resolve_and_sort(algorithms:, preferred_algorithm:) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+59
+60
+61
+62
+63
+
+
# File 'lib/jwt/jwa.rb', line 59
+
+def resolve_and_sort(algorithms:, preferred_algorithm:)
+  Array(algorithms).map { |alg| JWA.resolve(alg) }
+                   .partition { |alg| alg.valid_alg?(preferred_algorithm) }
+                   .flatten
+end
+
+
+ +
+

+ + .validate_jwk_algorithms!(jwks, algorithms, error_class) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ +

Raises:

+
    + +
  • + + + (error_class) + + + +
  • + +
+ +
+ + + + +
+
+
+
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+
+
# File 'lib/jwt/jwa.rb', line 88
+
+def validate_jwk_algorithms!(jwks, algorithms, error_class)
+  algorithms = Array(algorithms)
+
+  return if algorithms.empty?
+
+  return if Array(jwks).all? do |jwk|
+    algorithms.any? do |alg|
+      jwk.jwa.valid_alg?(alg)
+    end
+  end
+
+  raise error_class, "Provided JWKs do not support one of the specified algorithms: #{algorithms.join(', ')}"
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA/Ecdsa.html b/JWT/JWA/Ecdsa.html new file mode 100644 index 000000000..9d13d3885 --- /dev/null +++ b/JWT/JWA/Ecdsa.html @@ -0,0 +1,586 @@ + + + + + + + Class: JWT::JWA::Ecdsa + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWA::Ecdsa + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + +
+
Includes:
+
SigningAlgorithm
+
+ + + + + + +
+
Defined in:
+
lib/jwt/jwa/ecdsa.rb
+
+ +
+ +

Overview

+
+ +

ECDSA signing algorithm

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
NAMED_CURVES = + +
+
{
+  'prime256v1' => {
+    algorithm: 'ES256',
+    digest: 'sha256'
+  },
+  'secp256r1' => { # alias for prime256v1
+    algorithm: 'ES256',
+    digest: 'sha256'
+  },
+  'secp384r1' => {
+    algorithm: 'ES384',
+    digest: 'sha384'
+  },
+  'secp521r1' => {
+    algorithm: 'ES512',
+    digest: 'sha512'
+  },
+  'secp256k1' => {
+    algorithm: 'ES256K',
+    digest: 'sha256'
+  }
+}.freeze
+ +
+ + + + + + + +

Instance Attribute Summary

+ +

Attributes included from SigningAlgorithm

+

#alg

+ + + +

+ Class Method Summary + collapse +

+ + + +

+ Instance Method Summary + collapse +

+ + + + + + + + + + + + + +

Methods included from SigningAlgorithm

+

#header, #raise_sign_error!, #raise_verify_error!, #valid_alg?

+
+

Constructor Details

+ +
+

+ + #initialize(alg, digest) ⇒ Ecdsa + + + + + +

+
+ +

Returns a new instance of Ecdsa.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+12
+
+
# File 'lib/jwt/jwa/ecdsa.rb', line 9
+
+def initialize(alg, digest)
+  @alg = alg
+  @digest = digest
+end
+
+
+ +
+ + +
+

Class Method Details

+ + +
+

+ + .create_public_key_from_point(point) ⇒ Object + + + + + +

+ + + + +
+
+
+
+75
+76
+77
+78
+79
+80
+81
+
+
# File 'lib/jwt/jwa/ecdsa.rb', line 75
+
+def self.create_public_key_from_point(point)
+  sequence = OpenSSL::ASN1::Sequence([
+                                       OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(point.group.curve_name)]),
+                                       OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
+                                     ])
+  OpenSSL::PKey::EC.new(sequence.to_der)
+end
+
+
+ +
+

+ + .curve_by_name(name) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+68
+69
+70
+71
+72
+
+
# File 'lib/jwt/jwa/ecdsa.rb', line 68
+
+def self.curve_by_name(name)
+  NAMED_CURVES.fetch(name) do
+    raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
+  end
+end
+
+
+ +
+ +
+

Instance Method Details

+ + +
+

+ + #sign(data:, signing_key:) ⇒ Object + + + + + +

+
+ + +
+
+
+ +

Raises:

+ + +
+ + + + +
+
+
+
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
+
# File 'lib/jwt/jwa/ecdsa.rb', line 14
+
+def sign(data:, signing_key:)
+  raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::EC instance") unless signing_key.is_a?(::OpenSSL::PKey::EC)
+  raise_sign_error!('The given key is not a private key') unless signing_key.private?
+
+  curve_definition = curve_by_name(signing_key.group.curve_name)
+  key_algorithm = curve_definition[:algorithm]
+
+  raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided" if alg != key_algorithm
+
+  asn1_to_raw(signing_key.dsa_sign_asn1(OpenSSL::Digest.new(digest).digest(data)), signing_key)
+end
+
+
+ +
+

+ + #verify(data:, signature:, verification_key:) ⇒ Object + + + + + +

+ + + + +
+
+
+
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+
+
# File 'lib/jwt/jwa/ecdsa.rb', line 26
+
+def verify(data:, signature:, verification_key:)
+  verification_key = self.class.create_public_key_from_point(verification_key) if verification_key.is_a?(::OpenSSL::PKey::EC::Point)
+
+  raise_verify_error!("The given key is a #{verification_key.class}. It has to be an OpenSSL::PKey::EC instance") unless verification_key.is_a?(::OpenSSL::PKey::EC)
+
+  curve_definition = curve_by_name(verification_key.group.curve_name)
+  key_algorithm = curve_definition[:algorithm]
+  raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided" if alg != key_algorithm
+
+  verification_key.dsa_verify_asn1(OpenSSL::Digest.new(digest).digest(data), raw_to_asn1(signature, verification_key))
+rescue OpenSSL::PKey::PKeyError
+  raise JWT::VerificationError, 'Signature verification raised'
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA/Hmac.html b/JWT/JWA/Hmac.html new file mode 100644 index 000000000..5b031d29d --- /dev/null +++ b/JWT/JWA/Hmac.html @@ -0,0 +1,371 @@ + + + + + + + Class: JWT::JWA::Hmac + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWA::Hmac + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + +
+
Includes:
+
SigningAlgorithm
+
+ + + + + + +
+
Defined in:
+
lib/jwt/jwa/hmac.rb
+
+ +
+ +

Overview

+
+ +

Implementation of the HMAC family of algorithms

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + Modules: SecurityUtils + + + + +

+ + + + + + +

Instance Attribute Summary

+ +

Attributes included from SigningAlgorithm

+

#alg

+ + + +

+ Instance Method Summary + collapse +

+ + + + + + + + + + + + + +

Methods included from SigningAlgorithm

+

#header, #raise_sign_error!, #raise_verify_error!, #valid_alg?

+
+

Constructor Details

+ +
+

+ + #initialize(alg, digest) ⇒ Hmac + + + + + +

+
+ +

Returns a new instance of Hmac.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+12
+
+
# File 'lib/jwt/jwa/hmac.rb', line 9
+
+def initialize(alg, digest)
+  @alg = alg
+  @digest = digest
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #sign(data:, signing_key:) ⇒ Object + + + + + +

+ + + + +
+
+
+
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
+
# File 'lib/jwt/jwa/hmac.rb', line 14
+
+def sign(data:, signing_key:)
+  signing_key ||= ''
+  raise_verify_error!('HMAC key expected to be a String') unless signing_key.is_a?(String)
+
+  OpenSSL::HMAC.digest(digest.new, signing_key, data)
+rescue OpenSSL::HMACError => e
+  raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret') if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure'
+
+  raise e
+end
+
+
+ +
+

+ + #verify(data:, signature:, verification_key:) ⇒ Object + + + + + +

+ + + + +
+
+
+
+25
+26
+27
+
+
# File 'lib/jwt/jwa/hmac.rb', line 25
+
+def verify(data:, signature:, verification_key:)
+  SecurityUtils.secure_compare(signature, sign(data: data, signing_key: verification_key))
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA/Hmac/SecurityUtils.html b/JWT/JWA/Hmac/SecurityUtils.html new file mode 100644 index 000000000..20b0efbf9 --- /dev/null +++ b/JWT/JWA/Hmac/SecurityUtils.html @@ -0,0 +1,278 @@ + + + + + + + Module: JWT::JWA::Hmac::SecurityUtils + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT::JWA::Hmac::SecurityUtils + + + +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwa/hmac.rb
+
+ +
+ +

Overview

+
+ +

Copy of github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate

+ + +
+
+
+ + +
+ + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .fixed_length_secure_compare(a, b) ⇒ Object + + + + + +

+
+ +

:nocov:

+ + +
+
+
+ +

Raises:

+
    + +
  • + + + (ArgumentError) + + + +
  • + +
+ +
+ + + + +
+
+
+
+51
+52
+53
+
+
# File 'lib/jwt/jwa/hmac.rb', line 51
+
+def fixed_length_secure_compare(a, b)
+  OpenSSL.fixed_length_secure_compare(a, b)
+end
+
+
+ +
+

+ + .secure_compare(a, b) ⇒ Object + + + + + +

+
+ +

Secure string comparison for strings of variable length.

+ +

While a timing attack would not be able to discern the content of a secret compared via secure_compare, it is possible to determine the secret length. This should be considered when using secure_compare to compare weak, short secrets to user input.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+70
+71
+72
+
+
# File 'lib/jwt/jwa/hmac.rb', line 70
+
+def secure_compare(a, b)
+  a.bytesize == b.bytesize && fixed_length_secure_compare(a, b)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA/None.html b/JWT/JWA/None.html new file mode 100644 index 000000000..27a1437e7 --- /dev/null +++ b/JWT/JWA/None.html @@ -0,0 +1,345 @@ + + + + + + + Class: JWT::JWA::None + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWA::None + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + +
+
Includes:
+
SigningAlgorithm
+
+ + + + + + +
+
Defined in:
+
lib/jwt/jwa/none.rb
+
+ +
+ +

Overview

+
+ +

Implementation of the none algorithm

+ + +
+
+
+ + +
+ + + + + +

Instance Attribute Summary

+ +

Attributes included from SigningAlgorithm

+

#alg

+ + + +

+ Instance Method Summary + collapse +

+ + + + + + + + + + + + + +

Methods included from SigningAlgorithm

+

#header, #raise_sign_error!, #raise_verify_error!, #valid_alg?

+
+

Constructor Details

+ +
+

+ + #initializeNone + + + + + +

+
+ +

Returns a new instance of None.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/jwt/jwa/none.rb', line 9
+
+def initialize
+  @alg = 'none'
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #signObject + + + + + +

+ + + + +
+
+
+
+13
+14
+15
+
+
# File 'lib/jwt/jwa/none.rb', line 13
+
+def sign(*)
+  ''
+end
+
+
+ +
+

+ + #verifyObject + + + + + +

+ + + + +
+
+
+
+17
+18
+19
+
+
# File 'lib/jwt/jwa/none.rb', line 17
+
+def verify(*)
+  true
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA/Ps.html b/JWT/JWA/Ps.html new file mode 100644 index 000000000..9be9ee1ba --- /dev/null +++ b/JWT/JWA/Ps.html @@ -0,0 +1,357 @@ + + + + + + + Class: JWT::JWA::Ps + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWA::Ps + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + +
+
Includes:
+
SigningAlgorithm
+
+ + + + + + +
+
Defined in:
+
lib/jwt/jwa/ps.rb
+
+ +
+ +

Overview

+
+ +

Implementation of the RSASSA-PSS family of algorithms

+ + +
+
+
+ + +
+ + + + + +

Instance Attribute Summary

+ +

Attributes included from SigningAlgorithm

+

#alg

+ + + +

+ Instance Method Summary + collapse +

+ + + + + + + + + + + + + +

Methods included from SigningAlgorithm

+

#header, #raise_sign_error!, #raise_verify_error!, #valid_alg?

+
+

Constructor Details

+ +
+

+ + #initialize(alg) ⇒ Ps + + + + + +

+
+ +

Returns a new instance of Ps.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+12
+
+
# File 'lib/jwt/jwa/ps.rb', line 9
+
+def initialize(alg)
+  @alg = alg
+  @digest_algorithm = alg.sub('PS', 'sha')
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #sign(data:, signing_key:) ⇒ Object + + + + + +

+ + + + +
+
+
+
+14
+15
+16
+17
+18
+19
+
+
# File 'lib/jwt/jwa/ps.rb', line 14
+
+def sign(data:, signing_key:)
+  raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance.") unless signing_key.is_a?(::OpenSSL::PKey::RSA)
+  raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048
+
+  signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm)
+end
+
+
+ +
+

+ + #verify(data:, signature:, verification_key:) ⇒ Object + + + + + +

+ + + + +
+
+
+
+21
+22
+23
+24
+25
+
+
# File 'lib/jwt/jwa/ps.rb', line 21
+
+def verify(data:, signature:, verification_key:)
+  verification_key.verify_pss(digest_algorithm, signature, data, salt_length: :auto, mgf1_hash: digest_algorithm)
+rescue OpenSSL::PKey::PKeyError
+  raise JWT::VerificationError, 'Signature verification raised'
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA/Rsa.html b/JWT/JWA/Rsa.html new file mode 100644 index 000000000..9e669e4d7 --- /dev/null +++ b/JWT/JWA/Rsa.html @@ -0,0 +1,357 @@ + + + + + + + Class: JWT::JWA::Rsa + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWA::Rsa + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + +
+
Includes:
+
SigningAlgorithm
+
+ + + + + + +
+
Defined in:
+
lib/jwt/jwa/rsa.rb
+
+ +
+ +

Overview

+
+ +

Implementation of the RSA family of algorithms

+ + +
+
+
+ + +
+ + + + + +

Instance Attribute Summary

+ +

Attributes included from SigningAlgorithm

+

#alg

+ + + +

+ Instance Method Summary + collapse +

+ + + + + + + + + + + + + +

Methods included from SigningAlgorithm

+

#header, #raise_sign_error!, #raise_verify_error!, #valid_alg?

+
+

Constructor Details

+ +
+

+ + #initialize(alg) ⇒ Rsa + + + + + +

+
+ +

Returns a new instance of Rsa.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+12
+
+
# File 'lib/jwt/jwa/rsa.rb', line 9
+
+def initialize(alg)
+  @alg = alg
+  @digest = alg.sub('RS', 'SHA')
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #sign(data:, signing_key:) ⇒ Object + + + + + +

+ + + + +
+
+
+
+14
+15
+16
+17
+18
+19
+
+
# File 'lib/jwt/jwa/rsa.rb', line 14
+
+def sign(data:, signing_key:)
+  raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance") unless signing_key.is_a?(OpenSSL::PKey::RSA)
+  raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048
+
+  signing_key.sign(OpenSSL::Digest.new(digest), data)
+end
+
+
+ +
+

+ + #verify(data:, signature:, verification_key:) ⇒ Object + + + + + +

+ + + + +
+
+
+
+21
+22
+23
+24
+25
+
+
# File 'lib/jwt/jwa/rsa.rb', line 21
+
+def verify(data:, signature:, verification_key:)
+  verification_key.verify(OpenSSL::Digest.new(digest), signature, data)
+rescue OpenSSL::PKey::PKeyError
+  raise JWT::VerificationError, 'Signature verification raised'
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA/SignerContext.html b/JWT/JWA/SignerContext.html new file mode 100644 index 000000000..3d137e270 --- /dev/null +++ b/JWT/JWA/SignerContext.html @@ -0,0 +1,375 @@ + + + + + + + Class: JWT::JWA::SignerContext + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWA::SignerContext + + + Private +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwa.rb
+
+ +
+ +
+
+

+ This class is part of a private API. + You should avoid using this class if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #jwa ⇒ Object + + + + + + + + + readonly + + + + + + + private + + +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(jwa:, key:) ⇒ SignerContext + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ +

Returns a new instance of SignerContext.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+36
+37
+38
+39
+
+
# File 'lib/jwt/jwa.rb', line 36
+
+def initialize(jwa:, key:)
+  @jwa = jwa
+  @key = key
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #jwaObject (readonly) + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+34
+35
+36
+
+
# File 'lib/jwt/jwa.rb', line 34
+
+def jwa
+  @jwa
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #sign(*args, **kwargs) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+41
+42
+43
+
+
# File 'lib/jwt/jwa.rb', line 41
+
+def sign(*args, **kwargs)
+  @jwa.sign(*args, **kwargs, signing_key: @key)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA/SigningAlgorithm.html b/JWT/JWA/SigningAlgorithm.html new file mode 100644 index 000000000..36d3ff1d5 --- /dev/null +++ b/JWT/JWA/SigningAlgorithm.html @@ -0,0 +1,564 @@ + + + + + + + Module: JWT::JWA::SigningAlgorithm + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT::JWA::SigningAlgorithm + + + +

+
+ + + + + + + + + +
+
Included in:
+
Ecdsa, Hmac, None, Ps, Rsa, Unsupported
+
+ + + +
+
Defined in:
+
lib/jwt/jwa/signing_algorithm.rb
+
+ +
+ +

Overview

+
+ +

Base functionality for signing algorithms

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + Modules: ClassMethods + + + + +

+ + + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #alg ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute alg.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + + +
+

Instance Attribute Details

+ + + +
+

+ + #algObject (readonly) + + + + + +

+
+ +

Returns the value of attribute alg.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+19
+20
+21
+
+
# File 'lib/jwt/jwa/signing_algorithm.rb', line 19
+
+def alg
+  @alg
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #headerObject + + + + + +

+ + + + +
+
+
+
+25
+26
+27
+
+
# File 'lib/jwt/jwa/signing_algorithm.rb', line 25
+
+def header(*)
+  { 'alg' => alg }
+end
+
+
+ +
+

+ + #raise_sign_error!(message) ⇒ Object + + + + + +

+ + + + +
+
+
+
+41
+42
+43
+
+
# File 'lib/jwt/jwa/signing_algorithm.rb', line 41
+
+def raise_sign_error!(message)
+  raise(EncodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
+end
+
+
+ +
+

+ + #raise_verify_error!(message) ⇒ Object + + + + + +

+ + + + +
+
+
+
+37
+38
+39
+
+
# File 'lib/jwt/jwa/signing_algorithm.rb', line 37
+
+def raise_verify_error!(message)
+  raise(DecodeError.new(message).tap { |e| e.set_backtrace(caller(1)) })
+end
+
+
+ +
+

+ + #signObject + + + + + +

+ + + + +
+
+
+
+29
+30
+31
+
+
# File 'lib/jwt/jwa/signing_algorithm.rb', line 29
+
+def sign(*)
+  raise_sign_error!('Algorithm implementation is missing the sign method')
+end
+
+
+ +
+

+ + #valid_alg?(alg_to_check) ⇒ Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+21
+22
+23
+
+
# File 'lib/jwt/jwa/signing_algorithm.rb', line 21
+
+def valid_alg?(alg_to_check)
+  alg&.casecmp(alg_to_check)&.zero? == true
+end
+
+
+ +
+

+ + #verifyObject + + + + + +

+ + + + +
+
+
+
+33
+34
+35
+
+
# File 'lib/jwt/jwa/signing_algorithm.rb', line 33
+
+def verify(*)
+  raise_verify_error!('Algorithm implementation is missing the verify method')
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA/SigningAlgorithm/ClassMethods.html b/JWT/JWA/SigningAlgorithm/ClassMethods.html new file mode 100644 index 000000000..73e447c40 --- /dev/null +++ b/JWT/JWA/SigningAlgorithm/ClassMethods.html @@ -0,0 +1,185 @@ + + + + + + + Module: JWT::JWA::SigningAlgorithm::ClassMethods + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT::JWA::SigningAlgorithm::ClassMethods + + + +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwa/signing_algorithm.rb
+
+ +
+ +

Overview

+
+ +

Class methods for the SigningAlgorithm module

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + + + +
+

Instance Method Details

+ + +
+

+ + #register_algorithm(algo) ⇒ Object + + + + + +

+ + + + +
+
+
+
+10
+11
+12
+
+
# File 'lib/jwt/jwa/signing_algorithm.rb', line 10
+
+def register_algorithm(algo)
+  ::JWT::JWA.register_algorithm(algo)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA/Unsupported.html b/JWT/JWA/Unsupported.html new file mode 100644 index 000000000..cf948380b --- /dev/null +++ b/JWT/JWA/Unsupported.html @@ -0,0 +1,280 @@ + + + + + + + Module: JWT::JWA::Unsupported + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT::JWA::Unsupported + + + +

+
+ + + + +
+
Extended by:
+
SigningAlgorithm
+
+ + + + + + + + +
+
Defined in:
+
lib/jwt/jwa/unsupported.rb
+
+ +
+ +

Overview

+
+ +

Represents an unsupported algorithm

+ + +
+
+
+ + +
+ + + + + +

Instance Attribute Summary

+ +

Attributes included from SigningAlgorithm

+

#alg

+ + + +

+ Class Method Summary + collapse +

+ + + + + + + + + + + + + +

Methods included from SigningAlgorithm

+

header, raise_sign_error!, raise_verify_error!, sign, valid_alg?, verify

+ + +
+

Class Method Details

+ + +
+

+ + .signObject + + + + + +

+ + + + +
+
+
+
+10
+11
+12
+
+
# File 'lib/jwt/jwa/unsupported.rb', line 10
+
+def sign(*)
+  raise_sign_error!('Unsupported signing method')
+end
+
+
+ +
+

+ + .verifyObject + + + + + +

+
+ + +
+
+
+ +

Raises:

+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/jwt/jwa/unsupported.rb', line 14
+
+def verify(*)
+  raise JWT::VerificationError, 'Algorithm not supported'
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWA/VerifierContext.html b/JWT/JWA/VerifierContext.html new file mode 100644 index 000000000..46d5bad2e --- /dev/null +++ b/JWT/JWA/VerifierContext.html @@ -0,0 +1,379 @@ + + + + + + + Class: JWT::JWA::VerifierContext + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWA::VerifierContext + + + Private +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwa.rb
+
+ +
+ +
+
+

+ This class is part of a private API. + You should avoid using this class if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #jwa ⇒ Object + + + + + + + + + readonly + + + + + + + private + + +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(jwa:, keys:) ⇒ VerifierContext + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ +

Returns a new instance of VerifierContext.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+20
+21
+22
+23
+
+
# File 'lib/jwt/jwa.rb', line 20
+
+def initialize(jwa:, keys:)
+  @jwa = jwa
+  @keys = Array(keys)
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #jwaObject (readonly) + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+18
+19
+20
+
+
# File 'lib/jwt/jwa.rb', line 18
+
+def jwa
+  @jwa
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #verify(*args, **kwargs) ⇒ Object + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+25
+26
+27
+28
+29
+
+
# File 'lib/jwt/jwa.rb', line 25
+
+def verify(*args, **kwargs)
+  @keys.any? do |key|
+    @jwa.verify(*args, **kwargs, verification_key: key)
+  end
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWK.html b/JWT/JWK.html new file mode 100644 index 000000000..c3f9eac8c --- /dev/null +++ b/JWT/JWK.html @@ -0,0 +1,279 @@ + + + + + + + Module: JWT::JWK + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT::JWK + + + +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwk.rb,
+ lib/jwt/jwk/ec.rb,
lib/jwt/jwk/rsa.rb,
lib/jwt/jwk/set.rb,
lib/jwt/jwk/hmac.rb,
lib/jwt/jwk/key_base.rb,
lib/jwt/jwk/key_finder.rb,
lib/jwt/jwk/thumbprint.rb,
lib/jwt/jwk/kid_as_key_digest.rb
+
+
+ +
+ +

Overview

+
+ +

JSON Web Key (JWK)

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + + + Classes: EC, HMAC, KeyBase, KeyFinder, KidAsKeyDigest, RSA, Set, Thumbprint + + +

+ + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .classesObject + + + + + +

+ + + + +
+
+
+
+25
+26
+27
+28
+
+
# File 'lib/jwt/jwk.rb', line 25
+
+def classes
+  @mappings = nil # reset the cached mappings
+  @classes ||= []
+end
+
+
+ +
+

+ + .create_from(key, params = nil, options = {}) ⇒ Object + + + + Also known as: + new, import + + + + +

+ + + + +
+
+
+
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
+
# File 'lib/jwt/jwk.rb', line 10
+
+def create_from(key, params = nil, options = {})
+  if key.is_a?(Hash)
+    jwk_kty = key[:kty] || key['kty']
+    raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty
+
+    return mappings.fetch(jwk_kty.to_s) do |kty|
+      raise JWT::JWKError, "Key type #{kty} not supported"
+    end.new(key, params, options)
+  end
+
+  mappings.fetch(key.class) do |klass|
+    raise JWT::JWKError, "Cannot create JWK from a #{klass.name}"
+  end.new(key, params, options)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWK/EC.html b/JWT/JWK/EC.html new file mode 100644 index 000000000..2b2f02d1a --- /dev/null +++ b/JWT/JWK/EC.html @@ -0,0 +1,1036 @@ + + + + + + + Class: JWT::JWK::EC + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWK::EC + + + +

+
+ +
+
Inherits:
+
+ KeyBase + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwk/ec.rb
+
+ +
+ +

Overview

+
+ +

JWK representation for Elliptic Curve (EC) keys

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
KTY = +
+
+ +

rubocop:disable Metrics/ClassLength

+ + +
+
+
+ + +
+
+
'EC'
+ +
KTYS = + +
+
[KTY, OpenSSL::PKey::EC, JWT::JWK::EC].freeze
+ +
BINARY = + +
+
2
+ +
EC_PUBLIC_KEY_ELEMENTS = + +
+
%i[kty crv x y].freeze
+ +
EC_PRIVATE_KEY_ELEMENTS = + +
+
%i[d].freeze
+ +
EC_KEY_ELEMENTS = + +
+
(EC_PRIVATE_KEY_ELEMENTS + EC_PUBLIC_KEY_ELEMENTS).freeze
+ +
ZERO_BYTE = + +
+
"\0".b.freeze
+ +
+ + + + + + + +

Instance Attribute Summary

+ +

Attributes inherited from KeyBase

+

#parameters

+ + + +

+ Class Method Summary + collapse +

+ + + +

+ Instance Method Summary + collapse +

+ + + + + + + + + + + + + +

Methods inherited from KeyBase

+

#<=>, #==, #[], #hash, #kid, #sign, #verify

+
+

Constructor Details

+ +
+

+ + #initialize(key, params = nil, options = {}) ⇒ EC + + + + + +

+
+ +

Returns a new instance of EC.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
+
# File 'lib/jwt/jwk/ec.rb', line 17
+
+def initialize(key, params = nil, options = {})
+  params ||= {}
+
+  # For backwards compatibility when kid was a String
+  params = { kid: params } if params.is_a?(String)
+
+  key_params = extract_key_params(key)
+
+  params = params.transform_keys(&:to_sym)
+  check_jwk_params!(key_params, params)
+
+  super(options, key_params.merge(params))
+end
+
+
+ +
+ + +
+

Class Method Details

+ + +
+

+ + .import(jwk_data) ⇒ Object + + + + + +

+ + + + +
+
+
+
+221
+222
+223
+
+
# File 'lib/jwt/jwk/ec.rb', line 221
+
+def import(jwk_data)
+  new(jwk_data)
+end
+
+
+ +
+

+ + .to_openssl_curve(crv) ⇒ Object + + + + + +

+ + + + +
+
+
+
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+
+
# File 'lib/jwt/jwk/ec.rb', line 225
+
+def to_openssl_curve(crv)
+  # The JWK specs and OpenSSL use different names for the same curves.
+  # See https://tools.ietf.org/html/rfc5480#section-2.1.1.1 for some
+  # pointers on different names for common curves.
+  case crv
+  when 'P-256' then 'prime256v1'
+  when 'P-384' then 'secp384r1'
+  when 'P-521' then 'secp521r1'
+  when 'P-256K' then 'secp256k1'
+  else raise JWT::JWKError, 'Invalid curve provided'
+  end
+end
+
+
+ +
+ +
+

Instance Method Details

+ + +
+

+ + #[]=(key, value) ⇒ Object + + + + + +

+
+ + +
+
+
+ +

Raises:

+
    + +
  • + + + (ArgumentError) + + + +
  • + +
+ +
+ + + + +
+
+
+
+68
+69
+70
+71
+72
+
+
# File 'lib/jwt/jwk/ec.rb', line 68
+
+def []=(key, value)
+  raise ArgumentError, 'cannot overwrite cryptographic key attributes' if EC_KEY_ELEMENTS.include?(key.to_sym)
+
+  super
+end
+
+
+ +
+

+ + #export(options = {}) ⇒ Object + + + + + +

+ + + + +
+
+
+
+55
+56
+57
+58
+59
+
+
# File 'lib/jwt/jwk/ec.rb', line 55
+
+def export(options = {})
+  exported = parameters.clone
+  exported.reject! { |k, _| EC_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
+  exported
+end
+
+
+ +
+

+ + #jwaObject + + + + + +

+ + + + +
+
+
+
+74
+75
+76
+77
+78
+79
+
+
# File 'lib/jwt/jwk/ec.rb', line 74
+
+def jwa
+  return super if self[:alg]
+
+  curve_name = self.class.to_openssl_curve(self[:crv])
+  JWA.resolve(JWA::Ecdsa.curve_by_name(curve_name)[:algorithm])
+end
+
+
+ +
+

+ + #key_digestObject + + + + + +

+ + + + +
+
+
+
+61
+62
+63
+64
+65
+66
+
+
# File 'lib/jwt/jwk/ec.rb', line 61
+
+def key_digest
+  _crv, x_octets, y_octets = keypair_components(ec_key)
+  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
+                                      OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
+  OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
+end
+
+
+ +
+

+ + #keypairObject + + + + + +

+ + + + +
+
+
+
+31
+32
+33
+
+
# File 'lib/jwt/jwk/ec.rb', line 31
+
+def keypair
+  ec_key
+end
+
+
+ +
+

+ + #membersObject + + + + + +

+ + + + +
+
+
+
+51
+52
+53
+
+
# File 'lib/jwt/jwk/ec.rb', line 51
+
+def members
+  EC_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
+end
+
+
+ +
+

+ + #private?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+35
+36
+37
+
+
# File 'lib/jwt/jwk/ec.rb', line 35
+
+def private?
+  ec_key.private_key?
+end
+
+
+ +
+

+ + #public_keyObject + + + + + +

+ + + + +
+
+
+
+47
+48
+49
+
+
# File 'lib/jwt/jwk/ec.rb', line 47
+
+def public_key
+  ec_key
+end
+
+
+ +
+

+ + #signing_keyObject + + + + + +

+ + + + +
+
+
+
+39
+40
+41
+
+
# File 'lib/jwt/jwk/ec.rb', line 39
+
+def signing_key
+  ec_key
+end
+
+
+ +
+

+ + #verify_keyObject + + + + + +

+ + + + +
+
+
+
+43
+44
+45
+
+
# File 'lib/jwt/jwk/ec.rb', line 43
+
+def verify_key
+  ec_key
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWK/HMAC.html b/JWT/JWK/HMAC.html new file mode 100644 index 000000000..f8dfabc09 --- /dev/null +++ b/JWT/JWK/HMAC.html @@ -0,0 +1,898 @@ + + + + + + + Class: JWT::JWK::HMAC + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWK::HMAC + + + +

+
+ +
+
Inherits:
+
+ KeyBase + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwk/hmac.rb
+
+ +
+ +

Overview

+
+ +

JWK for HMAC keys

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
KTY = + +
+
'oct'
+ +
KTYS = + +
+
[KTY, String, JWT::JWK::HMAC].freeze
+ +
HMAC_PUBLIC_KEY_ELEMENTS = + +
+
%i[kty].freeze
+ +
HMAC_PRIVATE_KEY_ELEMENTS = + +
+
%i[k].freeze
+ +
HMAC_KEY_ELEMENTS = + +
+
(HMAC_PRIVATE_KEY_ELEMENTS + HMAC_PUBLIC_KEY_ELEMENTS).freeze
+ +
+ + + + + + + +

Instance Attribute Summary

+ +

Attributes inherited from KeyBase

+

#parameters

+ + + +

+ Class Method Summary + collapse +

+ + + +

+ Instance Method Summary + collapse +

+ + + + + + + + + + + + + +

Methods inherited from KeyBase

+

#<=>, #==, #[], #hash, #jwa, #kid, #sign, #verify

+
+

Constructor Details

+ +
+

+ + #initialize(key, params = nil, options = {}) ⇒ HMAC + + + + + +

+
+ +

Returns a new instance of HMAC.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
+
# File 'lib/jwt/jwk/hmac.rb', line 13
+
+def initialize(key, params = nil, options = {})
+  params ||= {}
+
+  # For backwards compatibility when kid was a String
+  params = { kid: params } if params.is_a?(String)
+
+  key_params = extract_key_params(key)
+
+  params = params.transform_keys(&:to_sym)
+  check_jwk(key_params, params)
+
+  super(options, key_params.merge(params))
+end
+
+
+ +
+ + +
+

Class Method Details

+ + +
+

+ + .import(jwk_data) ⇒ Object + + + + + +

+ + + + +
+
+
+
+96
+97
+98
+
+
# File 'lib/jwt/jwk/hmac.rb', line 96
+
+def import(jwk_data)
+  new(jwk_data)
+end
+
+
+ +
+ +
+

Instance Method Details

+ + +
+

+ + #[]=(key, value) ⇒ Object + + + + + +

+
+ + +
+
+
+ +

Raises:

+
    + +
  • + + + (ArgumentError) + + + +
  • + +
+ +
+ + + + +
+
+
+
+64
+65
+66
+67
+68
+
+
# File 'lib/jwt/jwk/hmac.rb', line 64
+
+def []=(key, value)
+  raise ArgumentError, 'cannot overwrite cryptographic key attributes' if HMAC_KEY_ELEMENTS.include?(key.to_sym)
+
+  super
+end
+
+
+ +
+

+ + #export(options = {}) ⇒ Object + + + + + +

+
+ + +
+ + + + +
+
+
+
+48
+49
+50
+51
+52
+
+
# File 'lib/jwt/jwk/hmac.rb', line 48
+
+def export(options = {})
+  exported = parameters.clone
+  exported.reject! { |k, _| HMAC_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
+  exported
+end
+
+
+ +
+

+ + #key_digestObject + + + + + +

+ + + + +
+
+
+
+58
+59
+60
+61
+62
+
+
# File 'lib/jwt/jwk/hmac.rb', line 58
+
+def key_digest
+  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::UTF8String.new(signing_key),
+                                      OpenSSL::ASN1::UTF8String.new(KTY)])
+  OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
+end
+
+
+ +
+

+ + #keypairObject + + + + + +

+ + + + +
+
+
+
+27
+28
+29
+
+
# File 'lib/jwt/jwk/hmac.rb', line 27
+
+def keypair
+  secret
+end
+
+
+ +
+

+ + #membersObject + + + + + +

+ + + + +
+
+
+
+54
+55
+56
+
+
# File 'lib/jwt/jwk/hmac.rb', line 54
+
+def members
+  HMAC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
+end
+
+
+ +
+

+ + #private?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+31
+32
+33
+
+
# File 'lib/jwt/jwk/hmac.rb', line 31
+
+def private?
+  true
+end
+
+
+ +
+

+ + #public_keyObject + + + + + +

+ + + + +
+
+
+
+35
+36
+37
+
+
# File 'lib/jwt/jwk/hmac.rb', line 35
+
+def public_key
+  nil
+end
+
+
+ +
+

+ + #signing_keyObject + + + + + +

+ + + + +
+
+
+
+43
+44
+45
+
+
# File 'lib/jwt/jwk/hmac.rb', line 43
+
+def signing_key
+  secret
+end
+
+
+ +
+

+ + #verify_keyObject + + + + + +

+ + + + +
+
+
+
+39
+40
+41
+
+
# File 'lib/jwt/jwk/hmac.rb', line 39
+
+def verify_key
+  secret
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWK/KeyBase.html b/JWT/JWK/KeyBase.html new file mode 100644 index 000000000..ac479d64b --- /dev/null +++ b/JWT/JWK/KeyBase.html @@ -0,0 +1,832 @@ + + + + + + + Class: JWT::JWK::KeyBase + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWK::KeyBase + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwk/key_base.rb
+
+ +
+ +

Overview

+
+ +

Base for JWK implementations

+ + +
+
+
+ + +
+

Direct Known Subclasses

+

EC, HMAC, RSA

+
+ + + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #parameters ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute parameters.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(options, params = {}) ⇒ KeyBase + + + + + +

+
+ +

Returns a new instance of KeyBase.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
+
# File 'lib/jwt/jwk/key_base.rb', line 12
+
+def initialize(options, params = {})
+  options ||= {}
+
+  @parameters = params.transform_keys(&:to_sym) # Uniform interface
+
+  # For backwards compatibility, kid_generator may be specified in the parameters
+  options[:kid_generator] ||= @parameters.delete(:kid_generator)
+
+  # Make sure the key has a kid
+  kid_generator = options[:kid_generator] || ::JWT.configuration.jwk.kid_generator
+  self[:kid] ||= kid_generator.new(self).generate
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #parametersObject (readonly) + + + + + +

+
+ +

Returns the value of attribute parameters.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+69
+70
+71
+
+
# File 'lib/jwt/jwk/key_base.rb', line 69
+
+def parameters
+  @parameters
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #<=>(other) ⇒ Object + + + + + +

+ + + + +
+
+
+
+55
+56
+57
+58
+59
+
+
# File 'lib/jwt/jwk/key_base.rb', line 55
+
+def <=>(other)
+  return nil unless other.is_a?(::JWT::JWK::KeyBase)
+
+  self[:kid] <=> other[:kid]
+end
+
+
+ +
+

+ + #==(other) ⇒ Object + + + + Also known as: + eql? + + + + +

+ + + + +
+
+
+
+41
+42
+43
+
+
# File 'lib/jwt/jwk/key_base.rb', line 41
+
+def ==(other)
+  other.is_a?(::JWT::JWK::KeyBase) && self[:kid] == other[:kid]
+end
+
+
+ +
+

+ + #[](key) ⇒ Object + + + + + +

+ + + + +
+
+
+
+33
+34
+35
+
+
# File 'lib/jwt/jwk/key_base.rb', line 33
+
+def [](key)
+  @parameters[key.to_sym]
+end
+
+
+ +
+

+ + #[]=(key, value) ⇒ Object + + + + + +

+ + + + +
+
+
+
+37
+38
+39
+
+
# File 'lib/jwt/jwk/key_base.rb', line 37
+
+def []=(key, value)
+  @parameters[key.to_sym] = value
+end
+
+
+ +
+

+ + #hashObject + + + + + +

+ + + + +
+
+
+
+29
+30
+31
+
+
# File 'lib/jwt/jwk/key_base.rb', line 29
+
+def hash
+  self[:kid].hash
+end
+
+
+ +
+

+ + #jwaObject + + + + + +

+
+ + +
+
+
+ +

Raises:

+ + +
+ + + + +
+
+
+
+61
+62
+63
+64
+65
+66
+67
+
+
# File 'lib/jwt/jwk/key_base.rb', line 61
+
+def jwa
+  raise JWT::JWKError, 'Could not resolve the JWA, the "alg" parameter is missing' unless self[:alg]
+
+  JWA.resolve(self[:alg]).tap do |jwa|
+    raise JWT::JWKError, 'none algorithm usage not supported via JWK' if jwa.is_a?(JWA::None)
+  end
+end
+
+
+ +
+

+ + #kidObject + + + + + +

+ + + + +
+
+
+
+25
+26
+27
+
+
# File 'lib/jwt/jwk/key_base.rb', line 25
+
+def kid
+  self[:kid]
+end
+
+
+ +
+

+ + #sign(**kwargs) ⇒ Object + + + + + +

+ + + + +
+
+
+
+49
+50
+51
+
+
# File 'lib/jwt/jwk/key_base.rb', line 49
+
+def sign(**kwargs)
+  jwa.sign(**kwargs, signing_key: signing_key)
+end
+
+
+ +
+

+ + #verify(**kwargs) ⇒ Object + + + + + +

+ + + + +
+
+
+
+45
+46
+47
+
+
# File 'lib/jwt/jwk/key_base.rb', line 45
+
+def verify(**kwargs)
+  jwa.verify(**kwargs, verification_key: verify_key)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWK/KeyFinder.html b/JWT/JWK/KeyFinder.html new file mode 100644 index 000000000..42000fc42 --- /dev/null +++ b/JWT/JWK/KeyFinder.html @@ -0,0 +1,534 @@ + + + + + + + Class: JWT::JWK::KeyFinder + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWK::KeyFinder + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwk/key_finder.rb
+
+ +
+ +

Overview

+
+ +

JSON Web Key keyfinder To find the key for a given kid

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(options) ⇒ KeyFinder + + + + + +

+
+ +

Initializes a new KeyFinder instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + options + + + (Hash) + + + + — +
    +

    the options to create a KeyFinder with

    +
    + +
  • + +
+ + + + +

Options Hash (options):

+
    + +
  • + :jwks + (Proc, JWT::JWK::Set) + + + + + —
    +

    the jwks or a loader proc

    +
    + +
  • + +
  • + :allow_nil_kid + (Boolean) + + + + + —
    +

    whether to allow nil kid

    +
    + +
  • + +
  • + :key_fields + (Array) + + + + + —
    +

    the fields to use for key matching, the order of the fields are used to determine the priority of the keys.

    +
    + +
  • + +
+ + + +
+ + + + +
+
+
+
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+
+
# File 'lib/jwt/jwk/key_finder.rb', line 15
+
+def initialize(options)
+  @allow_nil_kid = options[:allow_nil_kid]
+  jwks_or_loader = options[:jwks]
+
+  @jwks_loader = if jwks_or_loader.respond_to?(:call)
+                   jwks_or_loader
+                 else
+                   ->(_options) { jwks_or_loader }
+                 end
+
+  @key_fields = options[:key_fields] || %i[kid]
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #call(token) ⇒ Object + + + + + +

+
+ +

Returns the key for the given token

+ + +
+
+
+

Parameters:

+ + +

Raises:

+ + +
+ + + + +
+
+
+
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+
+
# File 'lib/jwt/jwk/key_finder.rb', line 43
+
+def call(token)
+  @key_fields.each do |key_field|
+    field_value = token.header[key_field.to_s]
+
+    return key_for(field_value, key_field) if field_value
+  end
+
+  raise ::JWT::DecodeError, 'No key id (kid) or x5t found from token headers' unless @allow_nil_kid
+
+  kid = token.header['kid']
+  key_for(kid)
+end
+
+
+ +
+

+ + #key_for(kid, key_field = :kid) ⇒ Object + + + + + +

+
+ +

Returns the verification key for the given kid

+ + +
+
+
+

Parameters:

+
    + +
  • + + kid + + + (String) + + + + — +
    +

    the key id

    +
    + +
  • + +
+ +

Raises:

+ + +
+ + + + +
+
+
+
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+
+
# File 'lib/jwt/jwk/key_finder.rb', line 30
+
+def key_for(kid, key_field = :kid)
+  raise ::JWT::DecodeError, "Invalid type for #{key_field} header parameter" unless kid.nil? || kid.is_a?(String)
+
+  jwk = resolve_key(kid, key_field)
+
+  raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any?
+  raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
+
+  jwk.verify_key
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWK/KidAsKeyDigest.html b/JWT/JWK/KidAsKeyDigest.html new file mode 100644 index 000000000..9fe0fca06 --- /dev/null +++ b/JWT/JWK/KidAsKeyDigest.html @@ -0,0 +1,291 @@ + + + + + + + Class: JWT::JWK::KidAsKeyDigest + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWK::KidAsKeyDigest + + + Private +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwk/kid_as_key_digest.rb
+
+ +
+ +
+
+

+ This class is part of a private API. + You should avoid using this class if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(jwk) ⇒ KidAsKeyDigest + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ +

Returns a new instance of KidAsKeyDigest.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/jwt/jwk/kid_as_key_digest.rb', line 7
+
+def initialize(jwk)
+  @jwk = jwk
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #generateObject + + + + + +

+
+

+ This method is part of a private API. + You should avoid using this method if possible, as it may be removed or be changed in the future. +

+ + +
+
+
+ + +
+ + + + +
+
+
+
+11
+12
+13
+
+
# File 'lib/jwt/jwk/kid_as_key_digest.rb', line 11
+
+def generate
+  @jwk.key_digest
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWK/RSA.html b/JWT/JWK/RSA.html new file mode 100644 index 000000000..dddcb9e4a --- /dev/null +++ b/JWT/JWK/RSA.html @@ -0,0 +1,1299 @@ + + + + + + + Class: JWT::JWK::RSA + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWK::RSA + + + +

+
+ +
+
Inherits:
+
+ KeyBase + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwk/rsa.rb
+
+ +
+ +

Overview

+
+ +

JSON Web Key (JWK) representation of a RSA key

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
BINARY = +
+
+ +

rubocop:disable Metrics/ClassLength

+ + +
+
+
+ + +
+
+
2
+ +
KTY = + +
+
'RSA'
+ +
KTYS = + +
+
[KTY, OpenSSL::PKey::RSA, JWT::JWK::RSA].freeze
+ +
RSA_PUBLIC_KEY_ELEMENTS = + +
+
%i[kty n e].freeze
+ +
RSA_PRIVATE_KEY_ELEMENTS = + +
+
%i[d p q dp dq qi].freeze
+ +
RSA_KEY_ELEMENTS = + +
+
(RSA_PRIVATE_KEY_ELEMENTS + RSA_PUBLIC_KEY_ELEMENTS).freeze
+ +
RSA_OPT_PARAMS = + +
+
%i[p q dp dq qi].freeze
+ +
RSA_ASN1_SEQUENCE = + +
+ + +
+
+
(%i[n e d] + RSA_OPT_PARAMS).freeze
+ +
+ + + + + + + +

Instance Attribute Summary

+ +

Attributes inherited from KeyBase

+

#parameters

+ + + +

+ Class Method Summary + collapse +

+ + + +

+ Instance Method Summary + collapse +

+ + + + + + + + + + + + + +

Methods inherited from KeyBase

+

#<=>, #==, #[], #hash, #jwa, #kid, #sign, #verify

+
+

Constructor Details

+ +
+

+ + #initialize(key, params = nil, options = {}) ⇒ RSA + + + + + +

+
+ +

Returns a new instance of RSA.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
+
# File 'lib/jwt/jwk/rsa.rb', line 17
+
+def initialize(key, params = nil, options = {})
+  params ||= {}
+
+  # For backwards compatibility when kid was a String
+  params = { kid: params } if params.is_a?(String)
+
+  key_params = extract_key_params(key)
+
+  params = params.transform_keys(&:to_sym)
+  check_jwk_params!(key_params, params)
+
+  super(options, key_params.merge(params))
+end
+
+
+ +
+ + +
+

Class Method Details

+ + +
+

+ + .create_rsa_key_using_accessors(rsa_parameters) ⇒ Object + + + + + +

+
+ +

:nocov: Before openssl 2.0, we need to use the accessors to set the key

+ + +
+
+
+ + +
+ + + + +
+
+
+
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+
+
# File 'lib/jwt/jwk/rsa.rb', line 171
+
+def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/AbcSize
+  validate_rsa_parameters!(rsa_parameters)
+
+  OpenSSL::PKey::RSA.new.tap do |rsa_key|
+    rsa_key.n = rsa_parameters[:n]
+    rsa_key.e = rsa_parameters[:e]
+    rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d]
+    rsa_key.p = rsa_parameters[:p] if rsa_parameters[:p]
+    rsa_key.q = rsa_parameters[:q] if rsa_parameters[:q]
+    rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp]
+    rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq]
+    rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
+  end
+end
+
+
+ +
+

+ + .create_rsa_key_using_der(rsa_parameters) ⇒ Object + + + + + +

+ + + + +
+
+
+
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+
+
# File 'lib/jwt/jwk/rsa.rb', line 141
+
+def create_rsa_key_using_der(rsa_parameters)
+  validate_rsa_parameters!(rsa_parameters)
+
+  sequence = RSA_ASN1_SEQUENCE.each_with_object([]) do |key, arr|
+    next if rsa_parameters[key].nil?
+
+    arr << OpenSSL::ASN1::Integer.new(rsa_parameters[key])
+  end
+
+  if sequence.size > 2 # Append "two-prime" version for private key
+    sequence.unshift(OpenSSL::ASN1::Integer.new(0))
+
+    raise JWT::JWKError, 'Creating a RSA key with a private key requires the CRT parameters to be defined' if sequence.size < RSA_ASN1_SEQUENCE.size
+  end
+
+  OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der)
+end
+
+
+ +
+

+ + .create_rsa_key_using_sets(rsa_parameters) ⇒ Object + + + + + +

+ + + + +
+
+
+
+159
+160
+161
+162
+163
+164
+165
+166
+167
+
+
# File 'lib/jwt/jwk/rsa.rb', line 159
+
+def create_rsa_key_using_sets(rsa_parameters)
+  validate_rsa_parameters!(rsa_parameters)
+
+  OpenSSL::PKey::RSA.new.tap do |rsa_key|
+    rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d])
+    rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q]
+    rsa_key.set_crt_params(rsa_parameters[:dp], rsa_parameters[:dq], rsa_parameters[:qi]) if rsa_parameters[:dp] && rsa_parameters[:dq] && rsa_parameters[:qi]
+  end
+end
+
+
+ +
+

+ + .decode_open_ssl_bn(jwk_data) ⇒ Object + + + + + +

+ + + + +
+
+
+
+135
+136
+137
+138
+139
+
+
# File 'lib/jwt/jwk/rsa.rb', line 135
+
+def decode_open_ssl_bn(jwk_data)
+  return nil unless jwk_data
+
+  OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
+end
+
+
+ +
+

+ + .import(jwk_data) ⇒ Object + + + + + +

+ + + + +
+
+
+
+131
+132
+133
+
+
# File 'lib/jwt/jwk/rsa.rb', line 131
+
+def import(jwk_data)
+  new(jwk_data)
+end
+
+
+ +
+

+ + .validate_rsa_parameters!(rsa_parameters) ⇒ Object + + + + + +

+
+ +

:nocov:

+ + +
+
+
+ +

Raises:

+ + +
+ + + + +
+
+
+
+187
+188
+189
+190
+191
+192
+193
+194
+
+
# File 'lib/jwt/jwk/rsa.rb', line 187
+
+def validate_rsa_parameters!(rsa_parameters)
+  return unless rsa_parameters.key?(:d)
+
+  parameters = RSA_OPT_PARAMS - rsa_parameters.keys
+  return if parameters.empty? || parameters.size == RSA_OPT_PARAMS.size
+
+  raise JWT::JWKError, 'When one of p, q, dp, dq or qi is given all the other optimization parameters also needs to be defined' # https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.2
+end
+
+
+ +
+ +
+

Instance Method Details

+ + +
+

+ + #[]=(key, value) ⇒ Object + + + + + +

+
+ + +
+
+
+ +

Raises:

+
    + +
  • + + + (ArgumentError) + + + +
  • + +
+ +
+ + + + +
+
+
+
+68
+69
+70
+71
+72
+
+
# File 'lib/jwt/jwk/rsa.rb', line 68
+
+def []=(key, value)
+  raise ArgumentError, 'cannot overwrite cryptographic key attributes' if RSA_KEY_ELEMENTS.include?(key.to_sym)
+
+  super
+end
+
+
+ +
+

+ + #export(options = {}) ⇒ Object + + + + + +

+ + + + +
+
+
+
+51
+52
+53
+54
+55
+56
+
+
# File 'lib/jwt/jwk/rsa.rb', line 51
+
+def export(options = {})
+  exported = parameters.clone
+  exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
+
+  exported
+end
+
+
+ +
+

+ + #key_digestObject + + + + + +

+ + + + +
+
+
+
+62
+63
+64
+65
+66
+
+
# File 'lib/jwt/jwk/rsa.rb', line 62
+
+def key_digest
+  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
+                                      OpenSSL::ASN1::Integer.new(public_key.e)])
+  OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
+end
+
+
+ +
+

+ + #keypairObject + + + + + +

+ + + + +
+
+
+
+31
+32
+33
+
+
# File 'lib/jwt/jwk/rsa.rb', line 31
+
+def keypair
+  rsa_key
+end
+
+
+ +
+

+ + #membersObject + + + + + +

+ + + + +
+
+
+
+58
+59
+60
+
+
# File 'lib/jwt/jwk/rsa.rb', line 58
+
+def members
+  RSA_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
+end
+
+
+ +
+

+ + #private?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+35
+36
+37
+
+
# File 'lib/jwt/jwk/rsa.rb', line 35
+
+def private?
+  rsa_key.private?
+end
+
+
+ +
+

+ + #public_keyObject + + + + + +

+ + + + +
+
+
+
+39
+40
+41
+
+
# File 'lib/jwt/jwk/rsa.rb', line 39
+
+def public_key
+  rsa_key.public_key
+end
+
+
+ +
+

+ + #signing_keyObject + + + + + +

+ + + + +
+
+
+
+43
+44
+45
+
+
# File 'lib/jwt/jwk/rsa.rb', line 43
+
+def signing_key
+  rsa_key if private?
+end
+
+
+ +
+

+ + #verify_keyObject + + + + + +

+ + + + +
+
+
+
+47
+48
+49
+
+
# File 'lib/jwt/jwk/rsa.rb', line 47
+
+def verify_key
+  rsa_key.public_key
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWK/Set.html b/JWT/JWK/Set.html new file mode 100644 index 000000000..cca7fd256 --- /dev/null +++ b/JWT/JWK/Set.html @@ -0,0 +1,794 @@ + + + + + + + Class: JWT::JWK::Set + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWK::Set + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + +
+
Extended by:
+
Forwardable
+
+ + + +
+
Includes:
+
Enumerable
+
+ + + + + + +
+
Defined in:
+
lib/jwt/jwk/set.rb
+
+ +
+ +

Overview

+
+ +

JSON Web Key Set (JWKS) representation tools.ietf.org/html/rfc7517

+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #keys ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute keys.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + + + +
+

Constructor Details

+ +
+

+ + #initialize(jwks = nil, options = {}) ⇒ Set + + + + + +

+
+ +

rubocop:disable Metrics/CyclomaticComplexity

+ + +
+
+
+ + +
+ + + + +
+
+
+
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
+
# File 'lib/jwt/jwk/set.rb', line 15
+
+def initialize(jwks = nil, options = {}) # rubocop:disable Metrics/CyclomaticComplexity
+  jwks ||= {}
+
+  @keys = case jwks
+          when JWT::JWK::Set # Simple duplication
+            jwks.keys
+          when JWT::JWK::KeyBase # Singleton
+            [jwks]
+          when Hash
+            jwks = jwks.transform_keys(&:to_sym)
+            [*jwks[:keys]].map { |k| JWT::JWK.new(k, nil, options) }
+          when Array
+            jwks.map { |k| JWT::JWK.new(k, nil, options) }
+          else
+            raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK'
+          end
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #keysObject (readonly) + + + + + +

+
+ +

Returns the value of attribute keys.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+13
+14
+15
+
+
# File 'lib/jwt/jwk/set.rb', line 13
+
+def keys
+  @keys
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #==(other) ⇒ Object + + + + Also known as: + eql? + + + + +

+ + + + +
+
+
+
+69
+70
+71
+
+
# File 'lib/jwt/jwk/set.rb', line 69
+
+def ==(other)
+  other.is_a?(JWT::JWK::Set) && keys.sort == other.keys.sort
+end
+
+
+ +
+

+ + #add(key) ⇒ Object + + + + Also known as: + << + + + + +

+ + + + +
+
+
+
+64
+65
+66
+67
+
+
# File 'lib/jwt/jwk/set.rb', line 64
+
+def add(key)
+  @keys << JWT::JWK.new(key)
+  self
+end
+
+
+ +
+

+ + #export(options = {}) ⇒ Object + + + + + +

+ + + + +
+
+
+
+33
+34
+35
+
+
# File 'lib/jwt/jwk/set.rb', line 33
+
+def export(options = {})
+  { keys: @keys.map { |k| k.export(options) } }
+end
+
+
+ +
+

+ + #merge(enum) ⇒ Object + + + + + +

+ + + + +
+
+
+
+55
+56
+57
+58
+
+
# File 'lib/jwt/jwk/set.rb', line 55
+
+def merge(enum)
+  @keys += JWT::JWK::Set.new(enum.to_a).keys
+  self
+end
+
+
+ +
+

+ + #reject!(&block) ⇒ Object + + + + + +

+ + + + +
+
+
+
+45
+46
+47
+48
+49
+
+
# File 'lib/jwt/jwk/set.rb', line 45
+
+def reject!(&block)
+  return @keys.reject! unless block
+
+  self if @keys.reject!(&block)
+end
+
+
+ +
+

+ + #select!(&block) ⇒ Object + + + + Also known as: + filter! + + + + +

+ + + + +
+
+
+
+39
+40
+41
+42
+43
+
+
# File 'lib/jwt/jwk/set.rb', line 39
+
+def select!(&block)
+  return @keys.select! unless block
+
+  self if @keys.select!(&block)
+end
+
+
+ +
+

+ + #union(enum) ⇒ Object + + + + Also known as: + |, + + + + + +

+ + + + +
+
+
+
+60
+61
+62
+
+
# File 'lib/jwt/jwk/set.rb', line 60
+
+def union(enum)
+  dup.merge(enum)
+end
+
+
+ +
+

+ + #uniq!(&block) ⇒ Object + + + + + +

+ + + + +
+
+
+
+51
+52
+53
+
+
# File 'lib/jwt/jwk/set.rb', line 51
+
+def uniq!(&block)
+  self if @keys.uniq!(&block)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWK/Thumbprint.html b/JWT/JWK/Thumbprint.html new file mode 100644 index 000000000..27e59ab35 --- /dev/null +++ b/JWT/JWK/Thumbprint.html @@ -0,0 +1,372 @@ + + + + + + + Class: JWT::JWK::Thumbprint + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::JWK::Thumbprint + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/jwk/thumbprint.rb
+
+ +
+ +

Overview

+
+ + +
+ + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #jwk ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute jwk.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(jwk) ⇒ Thumbprint + + + + + +

+
+ +

Returns a new instance of Thumbprint.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/jwt/jwk/thumbprint.rb', line 9
+
+def initialize(jwk)
+  @jwk = jwk
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #jwkObject (readonly) + + + + + +

+
+ +

Returns the value of attribute jwk.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/jwt/jwk/thumbprint.rb', line 7
+
+def jwk
+  @jwk
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #generateObject + + + + Also known as: + to_s + + + + +

+ + + + +
+
+
+
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
+
# File 'lib/jwt/jwk/thumbprint.rb', line 13
+
+def generate
+  ::Base64.urlsafe_encode64(
+    Digest::SHA256.digest(
+      JWT::JSON.generate(
+        jwk.members.sort.to_h
+      )
+    ), padding: false
+  )
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/JWKError.html b/JWT/JWKError.html new file mode 100644 index 000000000..208d3a9a7 --- /dev/null +++ b/JWT/JWKError.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::JWKError + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::JWKError + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The JWKError class is raised when there is an error with the JSON Web Key (JWK).

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/MissingRequiredClaim.html b/JWT/MissingRequiredClaim.html new file mode 100644 index 000000000..f9c1da0b0 --- /dev/null +++ b/JWT/MissingRequiredClaim.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::MissingRequiredClaim + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::MissingRequiredClaim + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The MissingRequiredClaim class is raised when a required claim is missing from the JWT.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/Token.html b/JWT/Token.html new file mode 100644 index 000000000..7d156bdf5 --- /dev/null +++ b/JWT/Token.html @@ -0,0 +1,1497 @@ + + + + + + + Class: JWT::Token + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::Token + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/token.rb
+
+ +
+ +

Overview

+
+ +

Represents a JWT token

+ +

Basic token signed using the HS256 algorithm:

+ +
token = JWT::Token.new(payload: {pay: 'load'})
+token.sign!(algorithm: 'HS256', key: 'secret')
+token.jwt # => eyJhb....
+
+ +

Custom headers will be combined with generated headers:

+ +
token = JWT::Token.new(payload: {pay: 'load'}, header: {custom: "value"})
+token.sign!(algorithm: 'HS256', key: 'secret')
+token.header # => {"custom"=>"value", "alg"=>"HS256"}
+
+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #header ⇒ Hash + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the decoded header of the JWT token.

    +
    + +
  • + + +
  • + + + #payload ⇒ Hash + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the payload of the JWT token.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(payload:, header: {}) ⇒ Token + + + + + +

+
+ +

Initializes a new Token instance.

+ + +
+
+
+

Parameters:

+
    + +
  • + + header + + + (Hash) + + + (defaults to: {}) + + + — +
    +

    the header of the JWT token.

    +
    + +
  • + +
  • + + payload + + + (Hash) + + + + — +
    +

    the payload of the JWT token.

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+22
+23
+24
+25
+
+
# File 'lib/jwt/token.rb', line 22
+
+def initialize(payload:, header: {})
+  @header  = header&.transform_keys(&:to_s)
+  @payload = payload
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #headerHash (readonly) + + + + + +

+
+ +

Returns the decoded header of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Hash) + + + + — +
    +

    the header of the JWT token.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+44
+45
+46
+
+
# File 'lib/jwt/token.rb', line 44
+
+def header
+  @header
+end
+
+
+ + + +
+

+ + #payloadHash (readonly) + + + + + +

+
+ +

Returns the payload of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Hash) + + + + — +
    +

    the payload of the JWT token.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+56
+57
+58
+
+
# File 'lib/jwt/token.rb', line 56
+
+def payload
+  @payload
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #claim_errors(*options) ⇒ Array<Symbol> + + + + + +

+
+ +

Returns the errors of the claims of the token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + options + + + (Array<Symbol>, Hash) + + + + — +
    +

    the claims to verify.

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Array<Symbol>) + + + + — +
    +

    the errors of the claims.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+115
+116
+117
+
+
# File 'lib/jwt/token.rb', line 115
+
+def claim_errors(*options)
+  Claims::Verifier.errors(self, *options)
+end
+
+
+ +
+

+ + #detach_payload!Object + + + + + +

+
+ +

Detaches the payload according to datatracker.ietf.org/doc/html/rfc7515#appendix-F

+ + +
+
+
+ + +
+ + + + +
+
+
+
+82
+83
+84
+85
+86
+
+
# File 'lib/jwt/token.rb', line 82
+
+def detach_payload!
+  @detached_payload = true
+
+  nil
+end
+
+
+ +
+

+ + #encoded_headerString + + + + + +

+
+ +

Returns the encoded header of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the encoded header of the JWT token.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+49
+50
+51
+
+
# File 'lib/jwt/token.rb', line 49
+
+def encoded_header
+  @encoded_header ||= ::JWT::Base64.url_encode(JWT::JSON.generate(header))
+end
+
+
+ +
+

+ + #encoded_payloadString + + + + + +

+
+ +

Returns the encoded payload of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the encoded payload of the JWT token.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+61
+62
+63
+
+
# File 'lib/jwt/token.rb', line 61
+
+def encoded_payload
+  @encoded_payload ||= ::JWT::Base64.url_encode(JWT::JSON.generate(payload))
+end
+
+
+ +
+

+ + #encoded_signatureString + + + + + +

+
+ +

Returns the encoded signature of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the encoded signature of the JWT token.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+37
+38
+39
+
+
# File 'lib/jwt/token.rb', line 37
+
+def encoded_signature
+  @encoded_signature ||= ::JWT::Base64.url_encode(signature)
+end
+
+
+ +
+

+ + #jwtString + + + + Also known as: + to_s + + + + +

+
+ +

Returns the JWT token as a string.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the JWT token as a string.

    +
    + +
  • + +
+

Raises:

+
    + +
  • + + + (JWT::EncodeError) + + + + — +
    +

    if the token is not signed or other encoding issues

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+76
+77
+78
+
+
# File 'lib/jwt/token.rb', line 76
+
+def jwt
+  @jwt ||= (@signature && [encoded_header, @detached_payload ? '' : encoded_payload, encoded_signature].join('.')) || raise(::JWT::EncodeError, 'Token is not signed')
+end
+
+
+ +
+

+ + #sign!(key:, algorithm:) ⇒ void + + + + + +

+
+

This method returns an undefined value.

+

Signs the JWT token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + key + + + (String, JWT::JWK::KeyBase) + + + + — +
    +

    the key to use for signing.

    +
    + +
  • + +
  • + + algorithm + + + (String, Object) + + + + — +
    +

    the algorithm to use for signing.

    +
    + +
  • + +
+ +

Raises:

+
    + +
  • + + + (JWT::EncodeError) + + + + — +
    +

    if the token is already signed or other problems when signing

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+
+
# File 'lib/jwt/token.rb', line 94
+
+def sign!(key:, algorithm:)
+  raise ::JWT::EncodeError, 'Token already signed' if @signature
+
+  JWA.create_signer(algorithm: algorithm, key: key).tap do |signer|
+    header.merge!(signer.jwa.header) { |_key, old, _new| old }
+    @signature = signer.sign(data: signing_input)
+  end
+
+  nil
+end
+
+
+ +
+

+ + #signatureString + + + + + +

+
+ +

Returns the decoded signature of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the decoded signature of the JWT token.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+30
+31
+32
+
+
# File 'lib/jwt/token.rb', line 30
+
+def signature
+  @signature ||= ::JWT::Base64.url_decode(encoded_signature || '')
+end
+
+
+ +
+

+ + #signing_inputString + + + + + +

+
+ +

Returns the signing input of the JWT token.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (String) + + + + — +
    +

    the signing input of the JWT token.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+68
+69
+70
+
+
# File 'lib/jwt/token.rb', line 68
+
+def signing_input
+  @signing_input ||= [encoded_header, encoded_payload].join('.')
+end
+
+
+ +
+

+ + #valid_claims?(*options) ⇒ Boolean + + + + + +

+
+ +

Returns whether the claims of the token are valid.

+ + +
+
+
+

Parameters:

+
    + +
  • + + options + + + (Array<Symbol>, Hash) + + + + — +
    +

    the claims to verify.

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    whether the claims are valid.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+122
+123
+124
+
+
# File 'lib/jwt/token.rb', line 122
+
+def valid_claims?(*options)
+  claim_errors(*options).empty?
+end
+
+
+ +
+

+ + #verify_claims!(*options) ⇒ Object + + + + + +

+
+ +

Verifies the claims of the token.

+ + +
+
+
+

Parameters:

+
    + +
  • + + options + + + (Array<Symbol>, Hash) + + + + — +
    +

    the claims to verify.

    +
    + +
  • + +
+ +

Raises:

+
    + +
  • + + + (JWT::DecodeError) + + + + — +
    +

    if the claims are invalid.

    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+108
+109
+110
+
+
# File 'lib/jwt/token.rb', line 108
+
+def verify_claims!(*options)
+  Claims::Verifier.verify!(self, *options)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/UnsupportedEcdsaCurve.html b/JWT/UnsupportedEcdsaCurve.html new file mode 100644 index 000000000..0be8e323c --- /dev/null +++ b/JWT/UnsupportedEcdsaCurve.html @@ -0,0 +1,151 @@ + + + + + + + Exception: JWT::UnsupportedEcdsaCurve + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::UnsupportedEcdsaCurve + + + +

+
+ +
+
Inherits:
+
+ IncorrectAlgorithm + + + show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The UnsupportedEcdsaCurve class is raised when the ECDSA curve is unsupported.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/VERSION.html b/JWT/VERSION.html new file mode 100644 index 000000000..432e94d02 --- /dev/null +++ b/JWT/VERSION.html @@ -0,0 +1,152 @@ + + + + + + + Module: JWT::VERSION + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: JWT::VERSION + + + +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/version.rb
+
+ +
+ +

Overview

+
+ +

Version constants

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
MAJOR = + +
+
3
+ +
MINOR = + +
+
1
+ +
TINY = + +
+
3
+ +
PRE = + +
+
nil
+ +
STRING = + +
+
[MAJOR, MINOR, TINY, PRE].compact.join('.')
+ +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/VerificationError.html b/JWT/VerificationError.html new file mode 100644 index 000000000..9e60432d4 --- /dev/null +++ b/JWT/VerificationError.html @@ -0,0 +1,143 @@ + + + + + + + Exception: JWT::VerificationError + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Exception: JWT::VerificationError + + + +

+
+ +
+
Inherits:
+
+ DecodeError + +
    +
  • Object
  • + + + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/error.rb
+
+ +
+ +

Overview

+
+ +

The VerificationError class is raised when there is an error verifying a JWT.

+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/JWT/X5cKeyFinder.html b/JWT/X5cKeyFinder.html new file mode 100644 index 000000000..fbbdc01df --- /dev/null +++ b/JWT/X5cKeyFinder.html @@ -0,0 +1,313 @@ + + + + + + + Class: JWT::X5cKeyFinder + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: JWT::X5cKeyFinder + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/jwt/x5c_key_finder.rb
+
+ +
+ +

Overview

+
+ +

If the x5c header certificate chain can be validated by trusted root certificates, and none of the certificates are revoked, returns the public key from the first certificate. See tools.ietf.org/html/rfc7515#section-4.1.6

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(root_certificates, crls = nil) ⇒ X5cKeyFinder + + + + + +

+
+ +

Returns a new instance of X5cKeyFinder.

+ + +
+
+
+ +

Raises:

+
    + +
  • + + + (ArgumentError) + + + +
  • + +
+ +
+ + + + +
+
+
+
+9
+10
+11
+12
+13
+
+
# File 'lib/jwt/x5c_key_finder.rb', line 9
+
+def initialize(root_certificates, crls = nil)
+  raise ArgumentError, 'Root certificates must be specified' unless root_certificates
+
+  @store = build_store(root_certificates, crls)
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #from(x5c_header_or_certificates) ⇒ Object + + + + + +

+ + + + +
+
+
+
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
+
# File 'lib/jwt/x5c_key_finder.rb', line 15
+
+def from(x5c_header_or_certificates)
+  signing_certificate, *certificate_chain = parse_certificates(x5c_header_or_certificates)
+  store_context = OpenSSL::X509::StoreContext.new(@store, signing_certificate, certificate_chain)
+
+  if store_context.verify
+    signing_certificate.public_key
+  else
+    error = "Certificate verification failed: #{store_context.error_string}."
+    if (current_cert = store_context.current_cert)
+      error = "#{error} Certificate subject: #{current_cert.subject}."
+    end
+
+    raise JWT::VerificationError, error
+  end
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 927c375f4..000000000 --- a/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2011 Jeff Lindsay - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 0e8122edc..000000000 --- a/README.md +++ /dev/null @@ -1,782 +0,0 @@ -# JWT - -[![Gem Version](https://badge.fury.io/rb/jwt.svg)](https://badge.fury.io/rb/jwt) -[![Build Status](https://github.com/jwt/ruby-jwt/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/jwt/ruby-jwt/actions) -[![Maintainability](https://qlty.sh/badges/6f61c5a6-6e23-41a7-8896-a3ce8b006655/maintainability.svg)](https://qlty.sh/gh/jwt/projects/ruby-jwt) -[![Code Coverage](https://qlty.sh/badges/6f61c5a6-6e23-41a7-8896-a3ce8b006655/test_coverage.svg)](https://qlty.sh/gh/jwt/projects/ruby-jwt) - -A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard. - -If you have further questions related to development or usage, join us: [ruby-jwt google group](https://groups.google.com/forum/#!forum/ruby-jwt). - -See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes and [upgrade guide](UPGRADING.md) for upgrading between major versions. - -## Sponsors - -| Logo | Message | -| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| ![auth0 logo](https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png) | If you want to quickly add secure token-based authentication to Ruby projects, feel free to check Auth0's Ruby SDK and free plan at [auth0.com/developers](https://auth0.com/developers?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=rubyjwt&utm_content=auth) | - -## Installing - -### Using Rubygems - -```bash -gem install jwt -``` - -### Using Bundler - -Add the following to your Gemfile - -```bash -gem 'jwt' -``` - -And run `bundle install` - -Finally require the gem in your application - -```ruby -require 'jwt' -``` - -## Algorithms and Usage - -The jwt gem natively supports the NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms via the openssl library. The gem can be extended with additional or alternative implementations of the algorithms via extensions. - -Additionally the EdDSA algorithm is supported via a the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa). - -For safe 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** - -See [JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1) - -### **NONE** - -- none - unsigned token - -```ruby -payload = { data: 'test' } -token = JWT.encode(payload, nil, 'none') -# => "eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9." - -decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' }) -# => [ -# {"data"=>"test"}, # payload -# {"alg"=>"none"} # header -# ] -``` - -### **HMAC** - -- HS256 - HMAC using SHA-256 hash algorithm -- HS384 - HMAC using SHA-384 hash algorithm -- HS512 - HMAC using SHA-512 hash algorithm - -```ruby -payload = { data: 'test' } -hmac_secret = 'my$ecretK3y' - -token = JWT.encode(payload, hmac_secret, 'HS256') -# => "eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y" - -decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' }) -# => [ -# {"data"=>"test"}, # payload -# {"alg"=>"HS256"} # header -# ] -``` - -### **RSA** - -- RS256 - RSA using SHA-256 hash algorithm -- RS384 - RSA using SHA-384 hash algorithm -- RS512 - RSA using SHA-512 hash algorithm - -```ruby -payload = { data: 'test' } -rsa_private = OpenSSL::PKey::RSA.generate(2048) -rsa_public = rsa_private.public_key - -token = JWT.encode(payload, rsa_private, 'RS256') -# => "eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.CCkO35qFPijW8Gwhbt8a80PB9fc9FJ19hCMnXSgoDF6Mlvlt0A4G-ah..." - -decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' }) -# => [ -# {"data"=>"test"}, # payload -# {"alg"=>"RS256"} # header -# ] -``` - -### **ECDSA** - -- ES256 - ECDSA using P-256 and SHA-256 -- ES384 - ECDSA using P-384 and SHA-384 -- ES512 - ECDSA using P-521 and SHA-512 -- ES256K - ECDSA using P-256K and SHA-256 - -```ruby -payload = { data: 'test' } -ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1') - -token = JWT.encode(payload, ecdsa_key, 'ES256') -# => "eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg" - -decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' }) -# => [ -# {"test"=>"data"}, # payload -# {"alg"=>"ES256"} # header -# ] -``` - -### **EdDSA** - -Since version 3.0, the EdDSA algorithm has been moved to the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa). - -### **RSASSA-PSS** - -- PS256 - RSASSA-PSS using SHA-256 hash algorithm -- PS384 - RSASSA-PSS using SHA-384 hash algorithm -- PS512 - RSASSA-PSS using SHA-512 hash algorithm - -```ruby -payload = { data: 'test' } -rsa_private = OpenSSL::PKey::RSA.generate(2048) -rsa_public = rsa_private.public_key - -token = JWT.encode(payload, rsa_private, 'PS256') -# => "eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.BRWizdUjD5zAWw-EDBcrl3dDpQDAePz9Ol3XKC43SggU47G8OWwveA_..." - -decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' }) -# => [ -# {"data"=>"test"}, # payload -# {"alg"=>"PS256"} # header -# ] -``` - -### **Custom algorithms** - -When encoding or decoding a token, you can pass in a custom object through the `algorithm` option to handle signing or verification. This custom object must include or extend the `JWT::JWA::SigningAlgorithm` module and implement certain methods: - -- For decoding/verifying: The object must implement the methods `alg` and `verify`. -- For encoding/signing: The object must implement the methods `alg` and `sign`. - -For customization options check the details from `JWT::JWA::SigningAlgorithm`. - -```ruby -module CustomHS512Algorithm - extend JWT::JWA::SigningAlgorithm - - def self.alg - 'HS512' - end - - def self.sign(data:, signing_key:) - OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), signing_key, data) - end - - def self.verify(data:, signature:, verification_key:) - ::OpenSSL.secure_compare(sign(data: data, signing_key: verification_key), signature) - end -end - -payload = { data: 'test' } -token = JWT.encode(payload, 'secret', CustomHS512Algorithm) -# => "eyJhbGciOiJIUzUxMiJ9.eyJkYXRhIjoidGVzdCJ9.aBNoejLEM2WMF3TxzRDKlehYdG2ATvFpGNauTI4GSD2VJseS_sC8covrVMlgslf0aJM4SKb3EIeORJBFPtZ33w" - -decoded_token = JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm) -# => [ -# {"data"=>"test"}, # payload -# {"alg"=>"HS512"} # header -# ] -``` - -### Add custom header fields - -The ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5) -To add custom header fields you need to pass `header_fields` parameter - -```ruby -payload = { data: 'test' } - -token = JWT.encode(payload, nil, 'none', { typ: 'JWT' }) -# => "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9." - -decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' }) -# => [ -# {"data"=>"test"}, # payload -# {"typ"=>"JWT", "alg"=>"none"} # header -# ] -``` - -## `JWT::Token` and `JWT::EncodedToken` - -The `JWT::Token` and `JWT::EncodedToken` classes can be used to manage your JWTs. - -### Signing and encoding a token - -```ruby -payload = { exp: Time.now.to_i + 60, jti: '1234', sub: "my-subject" } -header = { kid: 'hmac' } - -token = JWT::Token.new(payload: payload, header: header) -token.sign!(algorithm: 'HS256', key: "secret") - -token.jwt -# => "eyJraWQiOiJobWFjIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjE3NTAwMDU0NzksImp0aSI6IjEyMzQiLCJzdWIiOiJteS1zdWJqZWN0In0.NRLcK6fYr3IdNfmncJePMWLQ34M4n14EgqSYrQIjL9w" -``` - -### Verifying and decoding a token - -The `JWT::EncodedToken` can be used as a token object that allows verification of signatures and claims. - -```ruby -encoded_token = JWT::EncodedToken.new(token.jwt) - -encoded_token.verify_signature!(algorithm: 'HS256', key: "secret") -encoded_token.verify_signature!(algorithm: 'HS256', key: "wrong_secret") # raises JWT::VerificationError -encoded_token.verify_claims!(:exp, :jti) -encoded_token.verify_claims!(sub: ["not-my-subject"]) # raises JWT::InvalidSubError -encoded_token.claim_errors(sub: ["not-my-subject"]).map(&:message) # => ["Invalid subject. Expected [\"not-my-subject\"], received my-subject"] -encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' } -encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'} -``` - -The `JWT::EncodedToken#verify!` method can be used to verify signature and claim verification in one go. The `exp` claim is verified by default. - -```ruby -encoded_token = JWT::EncodedToken.new(token.jwt) -encoded_token.verify!(signature: {algorithm: 'HS256', key: "secret"}) -encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' } -encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'} -``` - -A JWK can be used to sign and verify the token if it's possible to derive the signing algorithm from the key. - -```ruby -jwk_json = '{ - "kty": "oct", - "k": "c2VjcmV0", - "alg": "HS256", - "kid": "hmac" -}' - -jwk = JWT::JWK.import(JSON.parse(jwk_json)) - -token = JWT::Token.new(payload: payload, header: header) - -token.sign!(key: jwk, algorithm: 'HS256') - -encoded_token = JWT::EncodedToken.new(token.jwt) -encoded_token.verify!(signature: { algorithm: ["HS256", "HS512"], key: jwk}) -``` - -#### Using a keyfinder - -A keyfinder can be used to verify a signature. A keyfinder is an object responding to the `#call` method. The method expects to receive one argument, which is the token to be verified. - -An example on using the built-in JWK keyfinder. - -```ruby -# Create and sign a token -jwk = JWT::JWK.new(OpenSSL::PKey::RSA.generate(2048)) -token = JWT::Token.new(payload: { pay: 'load' }, header: { kid: jwk.kid }) -token.sign!(algorithm: 'RS256', key: jwk.signing_key) - -# Create keyfinder object, verify and decode token -key_finder = JWT::JWK::KeyFinder.new(jwks: JWT::JWK::Set.new(jwk)) -encoded_token = JWT::EncodedToken.new(token.jwt) -encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: key_finder}) -encoded_token.payload # => { 'pay' => 'load' } -``` - -Using a custom keyfinder proc. - -```ruby -# Create and sign a token -key = OpenSSL::PKey::RSA.generate(2048) -token = JWT::Token.new(payload: { pay: 'load' }) -token.sign!(algorithm: 'RS256', key: key) - -# Verify and decode token -encoded_token = JWT::EncodedToken.new(token.jwt) -encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: ->(_token){ key.public_key }}) -encoded_token.payload # => { 'pay' => 'load' } -``` - -### Detached payload - -The `::JWT::Token#detach_payload!` method can be use to detach the payload from the JWT. - -```ruby -token = JWT::Token.new(payload: { pay: 'load' }) -token.sign!(algorithm: 'HS256', key: "secret") -token.detach_payload! -token.jwt # => "eyJhbGciOiJIUzI1NiJ9..UEhDY1Qlj29ammxuVRA_-gBah4qTy5FngIWg0yEAlC0" -token.encoded_payload # => "eyJwYXkiOiJsb2FkIn0" -``` - -The `JWT::EncodedToken` class can be used to decode a token with a detached payload by providing the payload to the token instance in separate. - -```ruby -encoded_token = JWT::EncodedToken.new(token.jwt) -encoded_token.encoded_payload = "eyJwYXkiOiJsb2FkIn0" -encoded_token.verify_signature!(algorithm: 'HS256', key: "secret") -encoded_token.payload # => {"pay"=>"load"} -``` - -## Claims - -JSON Web Token defines some reserved claim names and defines how they should be -used. JWT supports these reserved claim names: - -- 'exp' (Expiration Time) Claim -- 'nbf' (Not Before Time) Claim -- 'iss' (Issuer) Claim -- 'aud' (Audience) Claim -- 'jti' (JWT ID) Claim -- 'iat' (Issued At) Claim -- 'sub' (Subject) Claim - -### Expiration Time Claim - -From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.4): - -> The `exp` (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the `exp` claim requires that the current date/time MUST be before the expiration date/time listed in the `exp` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL. - -```ruby -exp = Time.now.to_i + 4 * 3600 -exp_payload = { data: 'data', exp: exp } - -token = JWT.encode(exp_payload, hmac_secret, 'HS256') - -begin - decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' }) -rescue JWT::ExpiredSignature - # Handle expired token, e.g. logout user or deny access -end -``` - -The Expiration Claim verification can be disabled. - -```ruby -# Decode token without raising JWT::ExpiredSignature error -JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' }) -``` - -Leeway and the exp claim. - -```ruby -exp = Time.now.to_i - 10 -leeway = 30 # seconds - -exp_payload = { data: 'data', exp: exp } - -# build expired token -token = JWT.encode(exp_payload, hmac_secret, 'HS256') - -begin - # add leeway to ensure the token is still accepted - decoded_token = JWT.decode(token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' }) -rescue JWT::ExpiredSignature - # Handle expired token, e.g. logout user or deny access -end -``` - -### Not Before Time Claim - -From [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.5): - -> The `nbf` (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the `nbf` claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the `nbf` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL. - -```ruby -nbf = Time.now.to_i - 3600 -nbf_payload = { data: 'data', nbf: nbf } - -token = JWT.encode(nbf_payload, hmac_secret, 'HS256') - -begin - decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' }) -rescue JWT::ImmatureSignature - # Handle invalid token, e.g. logout user or deny access -end -``` - -The Not Before Claim verification can be disabled. - -```ruby -# Decode token without raising JWT::ImmatureSignature error -JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' }) -``` - -Leeway and the nbf claim. - -```ruby -nbf = Time.now.to_i + 10 -leeway = 30 - -nbf_payload = { data: 'data', nbf: nbf } - -# build expired token -token = JWT.encode(nbf_payload, hmac_secret, 'HS256') - -begin - # add leeway to ensure the token is valid - decoded_token = JWT.decode(token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' }) -rescue JWT::ImmatureSignature - # Handle invalid token, e.g. logout user or deny access -end -``` - -### Issuer Claim - -From [Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.1): - -> The `iss` (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The `iss` value is a case-sensitive string containing a **_StringOrURI_** value. Use of this claim is OPTIONAL. - -You can pass multiple allowed issuers as an Array, verification will pass if one of them matches the `iss` value in the payload. - -```ruby -iss = 'My Awesome Company Inc. or https://my.awesome.website/' -iss_payload = { data: 'data', iss: iss } - -token = JWT.encode(iss_payload, hmac_secret, 'HS256') - -begin - # Add iss to the validation to check if the token has been manipulated - decoded_token = JWT.decode(token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' }) -rescue JWT::InvalidIssuerError - # Handle invalid token, e.g. logout user or deny access -end -``` - -You can also pass a Regexp or Proc (with arity 1), verification will pass if the regexp matches or the proc returns truthy. -On supported ruby versions (>= 2.5) you can also delegate to methods, on older versions you will have -to convert them to proc (using `to_proc`) - -```ruby -JWT.decode(token, hmac_secret, true, - iss: %r'https://my.awesome.website/', - verify_iss: true, - algorithm: 'HS256') -``` - -```ruby -JWT.decode(token, hmac_secret, true, - iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') }, - verify_iss: true, - algorithm: 'HS256') -``` - -```ruby -JWT.decode(token, hmac_secret, true, - iss: method(:valid_issuer?), - verify_iss: true, - algorithm: 'HS256') - -# somewhere in the same class: -def valid_issuer?(issuer) - # custom validation -end -``` - -### Audience Claim - -From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3): - -> The `aud` (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the `aud` claim when this claim is present, then the JWT MUST be rejected. In the general case, the `aud` value is an array of case-sensitive strings, each containing a **_StringOrURI_** value. In the special case when the JWT has one audience, the `aud` value MAY be a single case-sensitive string containing a **_StringOrURI_** value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL. - -```ruby -aud = ['Young', 'Old'] -aud_payload = { data: 'data', aud: aud } - -token = JWT.encode(aud_payload, hmac_secret, 'HS256') - -begin - # Add aud to the validation to check if the token has been manipulated - decoded_token = JWT.decode(token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' }) -rescue JWT::InvalidAudError - # Handle invalid token, e.g. logout user or deny access - puts 'Audience Error' -end -``` - -### JWT ID Claim - -From [Oauth JSON Web Token 4.1.7. "jti" (JWT ID) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.7): - -> The `jti` (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The `jti` claim can be used to prevent the JWT from being replayed. The `jti` value is a case-sensitive string. Use of this claim is OPTIONAL. - -```ruby -# Use the secret and iat to create a unique key per request to prevent replay attacks -jti_raw = [hmac_secret, iat].join(':').to_s -jti = Digest::MD5.hexdigest(jti_raw) -jti_payload = { data: 'data', iat: iat, jti: jti } - -token = JWT.encode(jti_payload, hmac_secret, 'HS256') - -begin - # If :verify_jti is true, validation will pass if a JTI is present - #decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' }) - # Alternatively, pass a proc with your own code to check if the JTI has already been used - decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' }) - # or - decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' }) -rescue JWT::InvalidJtiError - # Handle invalid token, e.g. logout user or deny access - puts 'Error' -end -``` - -### Issued At Claim - -From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.6): - -> The `iat` (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. The `leeway` option is not taken into account when verifying this claim. The `iat_leeway` option was removed in version 2.2.0. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL. - -```ruby -iat = Time.now.to_i -iat_payload = { data: 'data', iat: iat } - -token = JWT.encode(iat_payload, hmac_secret, 'HS256') - -begin - # Add iat to the validation to check if the token has been manipulated - decoded_token = JWT.decode(token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' }) -rescue JWT::InvalidIatError - # Handle invalid token, e.g. logout user or deny access -end -``` - -### Subject Claim - -From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.2): - -> The `sub` (subject) claim identifies the principal that is the subject of the JWT. The Claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The sub value is a case-sensitive string containing a **_StringOrURI_** value. Use of this claim is OPTIONAL. - -```ruby -sub = 'Subject' -sub_payload = { data: 'data', sub: sub } - -token = JWT.encode(sub_payload, hmac_secret, 'HS256') - -begin - # Add sub to the validation to check if the token has been manipulated - decoded_token = JWT.decode(token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' }) -rescue JWT::InvalidSubError - # Handle invalid token, e.g. logout user or deny access -end -``` - -### Standalone claim verification - -The JWT claim verifications can be used to verify any Hash to include expected keys and values. - -A few example on verifying the claims for a payload: - -```ruby -JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :numeric, :exp) -JWT::Claims.valid_payload?({"exp" => Time.now.to_i + 10}, :exp) -# => true -JWT::Claims.payload_errors({"exp" => Time.now.to_i - 10}, :exp) -# => [#] -JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11}) - -JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10, "sub" => "subject"}, :exp, sub: "subject") -``` - -### Finding a Key - -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. - -```ruby -issuers = %w[My_Awesome_Company1 My_Awesome_Company2] -iss_payload = { data: 'data', iss: issuers.first } - -secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' } - -token = JWT.encode(iss_payload, hmac_secret, 'HS256') - -begin - # Add iss to the validation to check if the token has been manipulated - decoded_token = JWT.decode(token, nil, true, { iss: issuers, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload| - secrets[payload['iss']] - end -rescue JWT::InvalidIssuerError - # Handle invalid token, e.g. logout user or deny access -end -``` - -### Required Claims - -You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing - -```ruby -# Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent -JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' }) -``` - -### X.509 certificates in x5c header - -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). - -```ruby -root_certificates = [] # trusted `OpenSSL::X509::Certificate` objects -crl_uris = root_certificates.map(&:crl_uris) -crls = crl_uris.map do |uri| - # look up cached CRL by `uri` and return it if found, otherwise continue - crl = Net::HTTP.get(uri) - crl = OpenSSL::X509::CRL.new(crl) - # cache `crl` using `uri` as the key, expiry set to `crl.next_update` timestamp -end - -begin - JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls } }) -rescue JWT::DecodeError - # Handle error, e.g. x5c header certificate revoked or expired -end -``` - -## JSON Web Key (JWK) - -JWK is a JSON structure representing a cryptographic key. This gem currently supports RSA, EC, OKP and HMAC keys. OKP support requires [RbNaCl](https://github.com/RubyCrypto/rbnacl) and currently only supports the Ed25519 curve. - -To encode a JWT using your JWK: - -```ruby -optional_parameters = { kid: 'my-kid', use: 'sig', alg: 'RS512' } -jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters) - -# Encoding -payload = { data: 'data' } -token = JWT.encode(payload, jwk.signing_key, jwk[:alg], kid: jwk[:kid]) - -# JSON Web Key Set for advertising your signing keys -jwks_hash = JWT::JWK::Set.new(jwk).export -``` - -To decode a JWT using a trusted entity's JSON Web Key Set (JWKS): - -```ruby -jwks = JWT::JWK::Set.new(jwks_hash) -jwks.filter! {|key| key[:use] == 'sig' } # Signing keys only! -algorithms = jwks.map { |key| key[:alg] }.compact.uniq -JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks) -``` - -The `jwks` option can also be given as a lambda that evaluates every time a key identifier is resolved. -This can be used to implement caching of remotely fetched JWK Sets. - -Key identifiers can be specified using `kid`, `x5t` header parameters. -If the requested identifier 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. - -Tokens without a specified key identifier (`kid` or `x5t`) are rejected by default. -This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`. - -```ruby -jwks_loader = ->(options) do - # The jwk loader would fetch the set of JWKs from a trusted source. - # 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. - # This example only allows cache invalidations every 5 minutes. - if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300 - logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache") - @cached_keys = nil - end - @cached_keys ||= begin - @cache_last_update = Time.now.to_i - # Replace with your own JWKS fetching routine - jwks = JWT::JWK::Set.new(jwks_hash) - jwks.select! { |key| key[:use] == 'sig' } # Signing Keys only - jwks - end -end - -begin - JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks_loader }) -rescue JWT::JWKError - # Handle problems with the provided JWKs -rescue JWT::DecodeError - # Handle other decode related issues e.g. no kid in header, no matching public key found etc. -end -``` - -### Importing and exporting JSON Web Keys - -The ::JWT::JWK class can be used to import both JSON Web Keys and OpenSSL keys -and export to either format with and without the private key included. - -To include the private key in the export pass the `include_private` parameter to the export method. - -```ruby -# Import a JWK Hash (showing an HMAC example) -jwk = JWT::JWK.new({ kty: 'oct', k: 'my-secret', kid: 'my-kid' }) - -# Import an OpenSSL key -# You can optionally add descriptive parameters to the JWK -desc_params = { kid: 'my-kid', use: 'sig' } -jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), desc_params) - -# Export as JWK Hash (public key only by default) -jwk_hash = jwk.export -jwk_hash_with_private_key = jwk.export(include_private: true) - -# Export as OpenSSL key -public_key = jwk.verify_key -private_key = jwk.signing_key if jwk.private? - -# You can also import and export entire JSON Web Key Sets -jwks_hash = { keys: [{ kty: 'oct', k: 'my-secret', kid: 'my-kid' }] } -jwks = JWT::JWK::Set.new(jwks_hash) -jwks_hash = jwks.export -``` - -### Key ID (kid) and JWKs - -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. - -```ruby -JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint -# OR -JWT.configuration.jwk.kid_generator = ::JWT::JWK::Thumbprint -# OR -jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), nil, kid_generator: ::JWT::JWK::Thumbprint) - -jwk_hash = jwk.export - -thumbprint_as_the_kid = jwk_hash[:kid] -``` - -## Development and testing - -The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features. - -```bash -bundle install -bundle exec appraisal rake test -``` - -## Releasing - -To cut a new release adjust the [version.rb](lib/jwt/version.rb) and [CHANGELOG](CHANGELOG.md) with desired version numbers and dates and commit the changes. Tag the release with the version number using the following command: - -```bash -rake release:source_control_push -``` - -This will tag a new version an trigger a [GitHub action](.github/workflows/push_gem.yml) that eventually will push the gem to rubygems.org. - -## How to contribute - -See [CONTRIBUTING](CONTRIBUTING.md). - -## Contributors - -See [AUTHORS](AUTHORS). - -## License - -See [LICENSE](LICENSE). diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 3fdf1d38a..000000000 --- a/Rakefile +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require 'bundler/setup' -require 'bundler/gem_tasks' - -require 'rspec/core/rake_task' -require 'rubocop/rake_task' - -RSpec::Core::RakeTask.new(:test) -RuboCop::RakeTask.new(:rubocop) - -task default: %i[rubocop test] diff --git a/UPGRADING.md b/UPGRADING.md deleted file mode 100644 index 10c95d1ea..000000000 --- a/UPGRADING.md +++ /dev/null @@ -1,47 +0,0 @@ -# Upgrading ruby-jwt to >= 3.0.0 - -## Removal of the indirect [RbNaCl](https://github.com/RubyCrypto/rbnacl) dependency - -Historically, the set of supported algorithms was extended by including the `rbnacl` gem in the application's Gemfile. On load, ruby-jwt tried to load the gem and, if available, extend the algorithms to those provided by the `rbnacl/libsodium` libraries. This indirect dependency has caused some maintenance pain and confusion about which versions of the gem are supported. - -Some work to ease the way alternative algorithms can be implemented has been done. This enables the extraction of the algorithm provided by `rbnacl`. - -The extracted algorithms now live in the [jwt-eddsa](https://rubygems.org/gems/jwt-eddsa) gem. - -### Dropped support for HS512256 - -The algorithm HS512256 (HMAC-SHA-512 truncated to 256-bits) is not part of any JWA/JWT RFC and therefore will not be supported anymore. It was part of the HMAC algorithms provided by the indirect [RbNaCl](https://github.com/RubyCrypto/rbnacl) dependency. Currently, there are no direct substitutes for the algorithm. - -### `JWT::EncodedToken#payload` will raise before token is verified - -To avoid accidental use of unverified tokens, the `JWT::EncodedToken#payload` method will raise an error if accessed before the token signature has been verified. - -To access the payload before verification, use the method `JWT::EncodedToken#unverified_payload`. - -## Stricter requirements on Base64 encoded data - -Base64 decoding will no longer fallback on the looser RFC 2045. The biggest difference is that the looser version was ignoring whitespaces and newlines, whereas the stricter version raises errors in such cases. - -If you, for example, read tokens from files, there could be problems with trailing newlines. Make sure you trim your input before passing it to the decoding mechanisms. - -## Claim verification revamp - -Claim verification has been [split into separate classes](https://github.com/jwt/ruby-jwt/pull/605) and has [a new API](https://github.com/jwt/ruby-jwt/pull/626), leading to the following deprecations: - -- The `::JWT::ClaimsValidator` class will be removed in favor of the functionality provided by `::JWT::Claims`. -- The `::JWT::Claims::verify!` method will be removed in favor of `::JWT::Claims::verify_payload!`. -- The `::JWT::JWA.create` method will be removed. -- The `::JWT::Verify` class will be removed in favor of the functionality provided by `::JWT::Claims`. -- Calling `::JWT::Claims::Numeric.new` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`. -- Calling `::JWT::Claims::Numeric.verify!` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`. - -## Algorithm restructuring - -The internal algorithms were [restructured](https://github.com/jwt/ruby-jwt/pull/607) to support extensions from separate libraries. The changes led to a few deprecations and new requirements: - -- The `sign` and `verify` static methods on all the algorithms (`::JWT::JWA`) will be removed. -- Custom algorithms are expected to include the `JWT::JWA::SigningAlgorithm` module. - -## Base64 the `k´ value for HMAC JWKs - -The gem was missing the Base64 encoding and decoding when representing and parsing a HMAC key as a JWK. This issue is now addressed. The added encoding will break compatibility with JWKs produced by older versions of the gem. diff --git a/_index.html b/_index.html new file mode 100644 index 000000000..d542b8879 --- /dev/null +++ b/_index.html @@ -0,0 +1,716 @@ + + + + + + + Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Documentation by YARD 0.9.37

+
+

Alphabetic Index

+ +

File Listing

+ + +
+

Namespace Listing A-Z

+ + + + + + + + +
+ + +
    +
  • A
  • +
      + +
    • + Audience + + (JWT::Claims) + +
    • + +
    +
+ + + + + + + + + + + + + + +
    +
  • H
  • +
      + +
    • + HMAC + + (JWT::JWK) + +
    • + +
    • + Hmac + + (JWT::JWA) + +
    • + +
    +
+ + + + + +
+ + +
    +
  • J
  • + +
+ + + + + + + + +
    +
  • N
  • +
      + +
    • + None + + (JWT::JWA) + +
    • + +
    • + NotBefore + + (JWT::Claims) + +
    • + +
    • + Numeric + + (JWT::Claims) + +
    • + +
    +
+ + +
    +
  • P
  • +
      + +
    • + Ps + + (JWT::JWA) + +
    • + +
    +
+ + +
    +
  • R
  • +
      + +
    • + RSA + + (JWT::JWK) + +
    • + +
    • + Required + + (JWT::Claims) + +
    • + +
    • + Rsa + + (JWT::JWA) + +
    • + +
    +
+ + + + + +
    +
  • T
  • + +
+ + +
+ + + + + + + + + + +
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/bin/console.rb b/bin/console.rb deleted file mode 100755 index 1851a2a11..000000000 --- a/bin/console.rb +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'bundler/setup' -require 'jwt' -require 'irb' - -IRB.start(__FILE__) diff --git a/bin/smoke.rb b/bin/smoke.rb deleted file mode 100755 index 588c6b6a3..000000000 --- a/bin/smoke.rb +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'jwt' - -puts "Running simple encode/decode test for #{JWT.gem_version}" -secret = 'secretkeyforsigning' -token = JWT.encode({ con: 'tent' }, secret, 'HS256') -JWT.decode(token, secret, true, algorithm: 'HS256') diff --git a/class_list.html b/class_list.html new file mode 100644 index 000000000..551121b1b --- /dev/null +++ b/class_list.html @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + Class List + + + +
+
+

Class List

+ + + +
+ + +
+ + diff --git a/css/common.css b/css/common.css new file mode 100644 index 000000000..cf25c4523 --- /dev/null +++ b/css/common.css @@ -0,0 +1 @@ +/* Override this file with custom rules */ \ No newline at end of file diff --git a/css/full_list.css b/css/full_list.css new file mode 100644 index 000000000..6eef5e4a0 --- /dev/null +++ b/css/full_list.css @@ -0,0 +1,58 @@ +body { + margin: 0; + font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; + font-size: 13px; + height: 101%; + overflow-x: hidden; + background: #fafafa; +} + +h1 { padding: 12px 10px; padding-bottom: 0; margin: 0; font-size: 1.4em; } +.clear { clear: both; } +.fixed_header { position: fixed; background: #fff; width: 100%; padding-bottom: 10px; margin-top: 0; top: 0; z-index: 9999; height: 70px; } +#search { position: absolute; right: 5px; top: 9px; padding-left: 24px; } +#content.insearch #search, #content.insearch #noresults { background: url(data:image/gif;base64,R0lGODlhEAAQAPYAAP///wAAAPr6+pKSkoiIiO7u7sjIyNjY2J6engAAAI6OjsbGxjIyMlJSUuzs7KamppSUlPLy8oKCghwcHLKysqSkpJqamvT09Pj4+KioqM7OzkRERAwMDGBgYN7e3ujo6Ly8vCoqKjY2NkZGRtTU1MTExDw8PE5OTj4+PkhISNDQ0MrKylpaWrS0tOrq6nBwcKysrLi4uLq6ul5eXlxcXGJiYoaGhuDg4H5+fvz8/KKiohgYGCwsLFZWVgQEBFBQUMzMzDg4OFhYWBoaGvDw8NbW1pycnOLi4ubm5kBAQKqqqiQkJCAgIK6urnJyckpKSjQ0NGpqatLS0sDAwCYmJnx8fEJCQlRUVAoKCggICLCwsOTk5ExMTPb29ra2tmZmZmhoaNzc3KCgoBISEiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCAAAACwAAAAAEAAQAAAHaIAAgoMgIiYlg4kACxIaACEJCSiKggYMCRselwkpghGJBJEcFgsjJyoAGBmfggcNEx0flBiKDhQFlIoCCA+5lAORFb4AJIihCRbDxQAFChAXw9HSqb60iREZ1omqrIPdJCTe0SWI09GBACH5BAkIAAAALAAAAAAQABAAAAdrgACCgwc0NTeDiYozCQkvOTo9GTmDKy8aFy+NOBA7CTswgywJDTIuEjYFIY0JNYMtKTEFiRU8Pjwygy4ws4owPyCKwsMAJSTEgiQlgsbIAMrO0dKDGMTViREZ14kYGRGK38nHguHEJcvTyIEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDAggPg4iJAAMJCRUAJRIqiRGCBI0WQEEJJkWDERkYAAUKEBc4Po1GiKKJHkJDNEeKig4URLS0ICImJZAkuQAhjSi/wQyNKcGDCyMnk8u5rYrTgqDVghgZlYjcACTA1sslvtHRgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCQARAtOUoQRGRiFD0kJUYWZhUhKT1OLhR8wBaaFBzQ1NwAlkIszCQkvsbOHL7Y4q4IuEjaqq0ZQD5+GEEsJTDCMmIUhtgk1lo6QFUwJVDKLiYJNUd6/hoEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4uen4ICCA+IkIsDCQkVACWmhwSpFqAABQoQF6ALTkWFnYMrVlhWvIKTlSAiJiVVPqlGhJkhqShHV1lCW4cMqSkAR1ofiwsjJyqGgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCSMhREZGIYYGY2ElYebi56fhyWQniSKAKKfpaCLFlAPhl0gXYNGEwkhGYREUywag1wJwSkHNDU3D0kJYIMZQwk8MjPBLx9eXwuETVEyAC/BOKsuEjYFhoEAIfkECQgAAAAsAAAAABAAEAAAB2eAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4ueICImip6CIQkJKJ4kigynKaqKCyMnKqSEK05StgAGQRxPYZaENqccFgIID4KXmQBhXFkzDgOnFYLNgltaSAAEpxa7BQoQF4aBACH5BAkIAAAALAAAAAAQABAAAAdogACCg4SFggJiPUqCJSWGgkZjCUwZACQkgxGEXAmdT4UYGZqCGWQ+IjKGGIUwPzGPhAc0NTewhDOdL7Ykji+dOLuOLhI2BbaFETICx4MlQitdqoUsCQ2vhKGjglNfU0SWmILaj43M5oEAOwAAAAAAAAAAAA==) no-repeat center left; } +#full_list { padding: 0; list-style: none; margin-left: 0; margin-top: 80px; font-size: 1.1em; } +#full_list ul { padding: 0; } +#full_list li { padding: 0; margin: 0; list-style: none; } +#full_list li .item { padding: 5px 5px 5px 12px; } +#noresults { padding: 7px 12px; background: #fff; } +#content.insearch #noresults { margin-left: 7px; } +li.collapsed ul { display: none; } +li a.toggle { cursor: default; position: relative; left: -5px; top: 4px; text-indent: -999px; width: 10px; height: 9px; margin-left: -10px; display: block; float: left; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK8AAACvABQqw0mAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTM5jWRgMAAAAVdEVYdENyZWF0aW9uIFRpbWUAMy8xNC8wOeNZPpQAAAE2SURBVDiNrZTBccIwEEXfelIAHUA6CZ24BGaWO+FuzZAK4k6gg5QAdGAq+Bxs2Yqx7BzyL7Llp/VfzZeQhCTc/ezuGzKKnKSzpCxXJM8fwNXda3df5RZETlIt6YUzSQDs93sl8w3wBZxCCE10GM1OcWbWjB2mWgEH4Mfdyxm3PSepBHibgQE2wLe7r4HjEidpnXMYdQPKEMJcsZ4zs2POYQOcaPfwMVOo58zsAdMt18BuoVDPxUJRacELbXv3hUIX2vYmOUvi8C8ydz/ThjXrqKqqLbDIAdsCKBd+Wo7GWa7o9qzOQHVVVXeAbs+yHHCH4aTsaCOQqunmUy1yBUAXkdMIfMlgF5EXLo2OpV/c/Up7jG4hhHcYLgWzAZXUc2b2ixsfvc/RmNNfOXD3Q/oeL9axJE1yT9IOoUu6MGUkAAAAAElFTkSuQmCC) no-repeat bottom left; } +li.collapsed a.toggle { cursor: default; background-position: top left; } +li { color: #666; cursor: pointer; } +li.deprecated { text-decoration: line-through; font-style: italic; } +li.odd { background: #f0f0f0; } +li.even { background: #fafafa; } +.item:hover { background: #ddd; } +li small:before { content: "("; } +li small:after { content: ")"; } +li small.search_info { display: none; } +a, a:visited { text-decoration: none; color: #05a; } +li.clicked > .item { background: #05a; color: #ccc; } +li.clicked > .item a, li.clicked > .item a:visited { color: #eee; } +li.clicked > .item a.toggle { opacity: 0.5; background-position: bottom right; } +li.collapsed.clicked a.toggle { background-position: top right; } +#search input { border: 1px solid #bbb; border-radius: 3px; } +#full_list_nav { margin-left: 10px; font-size: 0.9em; display: block; color: #aaa; } +#full_list_nav a, #nav a:visited { color: #358; } +#full_list_nav a:hover { background: transparent; color: #5af; } +#full_list_nav span:after { content: ' | '; } +#full_list_nav span:last-child:after { content: ''; } + +#content h1 { margin-top: 0; } +li { white-space: nowrap; cursor: normal; } +li small { display: block; font-size: 0.8em; } +li small:before { content: ""; } +li small:after { content: ""; } +li small.search_info { display: none; } +#search { width: 170px; position: static; margin: 3px; margin-left: 10px; font-size: 0.9em; color: #666; padding-left: 0; padding-right: 24px; } +#content.insearch #search { background-position: center right; } +#search input { width: 110px; } + +#full_list.insearch ul { display: block; } +#full_list.insearch .item { display: none; } +#full_list.insearch .found { display: block; padding-left: 11px !important; } +#full_list.insearch li a.toggle { display: none; } +#full_list.insearch li small.search_info { display: block; } diff --git a/css/style.css b/css/style.css new file mode 100644 index 000000000..f169a6518 --- /dev/null +++ b/css/style.css @@ -0,0 +1,503 @@ +html { + width: 100%; + height: 100%; +} +body { + font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; + font-size: 13px; + width: 100%; + margin: 0; + padding: 0; + display: flex; + display: -webkit-flex; + display: -ms-flexbox; +} + +#nav { + position: relative; + width: 100%; + height: 100%; + border: 0; + border-right: 1px dotted #eee; + overflow: auto; +} +.nav_wrap { + margin: 0; + padding: 0; + width: 20%; + height: 100%; + position: relative; + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + flex-shrink: 0; + -webkit-flex-shrink: 0; + -ms-flex: 1 0; +} +#resizer { + position: absolute; + right: -5px; + top: 0; + width: 10px; + height: 100%; + cursor: col-resize; + z-index: 9999; +} +#main { + flex: 5 1; + -webkit-flex: 5 1; + -ms-flex: 5 1; + outline: none; + position: relative; + background: #fff; + padding: 1.2em; + padding-top: 0.2em; + box-sizing: border-box; +} + +@media (max-width: 920px) { + .nav_wrap { width: 100%; top: 0; right: 0; overflow: visible; position: absolute; } + #resizer { display: none; } + #nav { + z-index: 9999; + background: #fff; + display: none; + position: absolute; + top: 40px; + right: 12px; + width: 500px; + max-width: 80%; + height: 80%; + overflow-y: scroll; + border: 1px solid #999; + border-collapse: collapse; + box-shadow: -7px 5px 25px #aaa; + border-radius: 2px; + } +} + +@media (min-width: 920px) { + body { height: 100%; overflow: hidden; } + #main { height: 100%; overflow: auto; } + #search { display: none; } +} + +@media (max-width: 320px) { + body { height: 100%; overflow: hidden; overflow-wrap: break-word; } + #main { height: 100%; overflow: auto; } +} + +#main img { max-width: 100%; } +h1 { font-size: 25px; margin: 1em 0 0.5em; padding-top: 4px; border-top: 1px dotted #d5d5d5; } +h1.noborder { border-top: 0px; margin-top: 0; padding-top: 4px; } +h1.title { margin-bottom: 10px; } +h1.alphaindex { margin-top: 0; font-size: 22px; } +h2 { + padding: 0; + padding-bottom: 3px; + border-bottom: 1px #aaa solid; + font-size: 1.4em; + margin: 1.8em 0 0.5em; + position: relative; +} +h2 small { font-weight: normal; font-size: 0.7em; display: inline; position: absolute; right: 0; } +h2 small a { + display: block; + height: 20px; + border: 1px solid #aaa; + border-bottom: 0; + border-top-left-radius: 5px; + background: #f8f8f8; + position: relative; + padding: 2px 7px; +} +a { font-weight: 550; } +.clear { clear: both; } +.inline { display: inline; } +.inline p:first-child { display: inline; } +.docstring, .tags, #filecontents { font-size: 15px; line-height: 1.5145em; } +.docstring p > code, .docstring p > tt, .tags p > code, .tags p > tt { + color: #c7254e; background: #f9f2f4; padding: 2px 4px; font-size: 1em; + border-radius: 4px; +} +.docstring h1, .docstring h2, .docstring h3, .docstring h4 { padding: 0; border: 0; border-bottom: 1px dotted #bbb; } +.docstring h1 { font-size: 1.2em; } +.docstring h2 { font-size: 1.1em; } +.docstring h3, .docstring h4 { font-size: 1em; border-bottom: 0; padding-top: 10px; } +.summary_desc .object_link a, .docstring .object_link a { + font-family: monospace; font-size: 1.05em; + color: #05a; background: #EDF4FA; padding: 2px 4px; font-size: 1em; + border-radius: 4px; +} +.rdoc-term { padding-right: 25px; font-weight: bold; } +.rdoc-list p { margin: 0; padding: 0; margin-bottom: 4px; } +.summary_desc pre.code .object_link a, .docstring pre.code .object_link a { + padding: 0px; background: inherit; color: inherit; border-radius: inherit; +} + +/* style for */ +#filecontents table, .docstring table { border-collapse: collapse; } +#filecontents table th, #filecontents table td, +.docstring table th, .docstring table td { border: 1px solid #ccc; padding: 8px; padding-right: 17px; } +#filecontents table tr:nth-child(odd), +.docstring table tr:nth-child(odd) { background: #eee; } +#filecontents table tr:nth-child(even), +.docstring table tr:nth-child(even) { background: #fff; } +#filecontents table th, .docstring table th { background: #fff; } + +/* style for
    */ +#filecontents li > p, .docstring li > p { margin: 0px; } +#filecontents ul, .docstring ul { padding-left: 20px; } +/* style for
    */ +#filecontents dl, .docstring dl { border: 1px solid #ccc; } +#filecontents dt, .docstring dt { background: #ddd; font-weight: bold; padding: 3px 5px; } +#filecontents dd, .docstring dd { padding: 5px 0px; margin-left: 18px; } +#filecontents dd > p, .docstring dd > p { margin: 0px; } + +.note { + color: #222; + margin: 20px 0; + padding: 10px; + border: 1px solid #eee; + border-radius: 3px; + display: block; +} +.docstring .note { + border-left-color: #ccc; + border-left-width: 5px; +} +.note.todo { background: #ffffc5; border-color: #ececaa; } +.note.returns_void { background: #efefef; } +.note.deprecated { background: #ffe5e5; border-color: #e9dada; } +.note.title.deprecated { background: #ffe5e5; border-color: #e9dada; } +.note.private { background: #ffffc5; border-color: #ececaa; } +.note.title { padding: 3px 6px; font-size: 0.9em; font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; display: inline; } +.summary_signature + .note.title { margin-left: 7px; } +h1 .note.title { font-size: 0.5em; font-weight: normal; padding: 3px 5px; position: relative; top: -3px; text-transform: capitalize; } +.note.title { background: #efefef; } +.note.title.constructor { color: #fff; background: #6a98d6; border-color: #6689d6; } +.note.title.writeonly { color: #fff; background: #45a638; border-color: #2da31d; } +.note.title.readonly { color: #fff; background: #6a98d6; border-color: #6689d6; } +.note.title.private { background: #d5d5d5; border-color: #c5c5c5; } +.note.title.not_defined_here { background: transparent; border: none; font-style: italic; } +.discussion .note { margin-top: 6px; } +.discussion .note:first-child { margin-top: 0; } + +h3.inherited { + font-style: italic; + font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; + font-weight: normal; + padding: 0; + margin: 0; + margin-top: 12px; + margin-bottom: 3px; + font-size: 13px; +} +p.inherited { + padding: 0; + margin: 0; + margin-left: 25px; +} + +.box_info dl { + margin: 0; + border: 0; + width: 100%; + font-size: 1em; + display: flex; + display: -webkit-flex; + display: -ms-flexbox; +} +.box_info dl dt { + flex-shrink: 0; + -webkit-flex-shrink: 1; + -ms-flex-shrink: 1; + width: 100px; + text-align: right; + font-weight: bold; + border: 1px solid #aaa; + border-width: 1px 0px 0px 1px; + padding: 6px 0; + padding-right: 10px; +} +.box_info dl dd { + flex-grow: 1; + -webkit-flex-grow: 1; + -ms-flex: 1; + max-width: 420px; + padding: 6px 0; + padding-right: 20px; + border: 1px solid #aaa; + border-width: 1px 1px 0 0; + overflow: hidden; + position: relative; +} +.box_info dl:last-child > * { + border-bottom: 1px solid #aaa; +} +.box_info dl:nth-child(odd) > * { background: #eee; } +.box_info dl:nth-child(even) > * { background: #fff; } +.box_info dl > * { margin: 0; } + +ul.toplevel { list-style: none; padding-left: 0; font-size: 1.1em; } +.index_inline_list { padding-left: 0; font-size: 1.1em; } + +.index_inline_list li { + list-style: none; + display: inline-block; + padding: 0 12px; + line-height: 30px; + margin-bottom: 5px; +} + +dl.constants { margin-left: 10px; } +dl.constants dt { font-weight: bold; font-size: 1.1em; margin-bottom: 5px; } +dl.constants.compact dt { display: inline-block; font-weight: normal } +dl.constants dd { width: 75%; white-space: pre; font-family: monospace; margin-bottom: 18px; } +dl.constants .docstring .note:first-child { margin-top: 5px; } + +.summary_desc { + margin-left: 32px; + display: block; + font-family: sans-serif; + font-size: 1.1em; + margin-top: 8px; + line-height: 1.5145em; + margin-bottom: 0.8em; +} +.summary_desc tt { font-size: 0.9em; } +dl.constants .note { padding: 2px 6px; padding-right: 12px; margin-top: 6px; } +dl.constants .docstring { margin-left: 32px; font-size: 0.9em; font-weight: normal; } +dl.constants .tags { padding-left: 32px; font-size: 0.9em; line-height: 0.8em; } +dl.constants .discussion *:first-child { margin-top: 0; } +dl.constants .discussion *:last-child { margin-bottom: 0; } + +.method_details { border-top: 1px dotted #ccc; margin-top: 25px; padding-top: 0; } +.method_details.first { border: 0; margin-top: 5px; } +.method_details.first h3.signature { margin-top: 1em; } +p.signature, h3.signature { + font-size: 1.1em; font-weight: normal; font-family: Monaco, Consolas, Courier, monospace; + padding: 6px 10px; margin-top: 1em; + background: #E8F4FF; border: 1px solid #d8d8e5; border-radius: 5px; +} +p.signature tt, +h3.signature tt { font-family: Monaco, Consolas, Courier, monospace; } +p.signature .overload, +h3.signature .overload { display: block; } +p.signature .extras, +h3.signature .extras { font-weight: normal; font-family: sans-serif; color: #444; font-size: 1em; } +p.signature .not_defined_here, +h3.signature .not_defined_here, +p.signature .aliases, +h3.signature .aliases { display: block; font-weight: normal; font-size: 0.9em; font-family: sans-serif; margin-top: 0px; color: #555; } +p.signature .aliases .names, +h3.signature .aliases .names { font-family: Monaco, Consolas, Courier, monospace; font-weight: bold; color: #000; font-size: 1.2em; } + +.tags .tag_title { font-size: 1.05em; margin-bottom: 0; font-weight: bold; } +.tags .tag_title tt { color: initial; padding: initial; background: initial; } +.tags ul { margin-top: 5px; padding-left: 30px; list-style: square; } +.tags ul li { margin-bottom: 3px; } +.tags ul .name { font-family: monospace; font-weight: bold; } +.tags ul .note { padding: 3px 6px; } +.tags { margin-bottom: 12px; } + +.tags .examples .tag_title { margin-bottom: 10px; font-weight: bold; } +.tags .examples .inline p { padding: 0; margin: 0; font-weight: bold; font-size: 1em; } +.tags .examples .inline p:before { content: "▸"; font-size: 1em; margin-right: 5px; } + +.tags .overload .overload_item { list-style: none; margin-bottom: 25px; } +.tags .overload .overload_item .signature { + padding: 2px 8px; + background: #F1F8FF; border: 1px solid #d8d8e5; border-radius: 3px; +} +.tags .overload .signature { margin-left: -15px; font-family: monospace; display: block; font-size: 1.1em; } +.tags .overload .docstring { margin-top: 15px; } + +.defines { display: none; } + +#method_missing_details .notice.this { position: relative; top: -8px; color: #888; padding: 0; margin: 0; } + +.showSource { font-size: 0.9em; } +.showSource a, .showSource a:visited { text-decoration: none; color: #666; } + +#content a, #content a:visited { text-decoration: none; color: #05a; } +#content a:hover { background: #ffffa5; } + +ul.summary { + list-style: none; + font-family: monospace; + font-size: 1em; + line-height: 1.5em; + padding-left: 0px; +} +ul.summary a, ul.summary a:visited { + text-decoration: none; font-size: 1.1em; +} +ul.summary li { margin-bottom: 5px; } +.summary_signature { padding: 4px 8px; background: #f8f8f8; border: 1px solid #f0f0f0; border-radius: 5px; } +.summary_signature:hover { background: #CFEBFF; border-color: #A4CCDA; cursor: pointer; } +.summary_signature.deprecated { background: #ffe5e5; border-color: #e9dada; } +ul.summary.compact li { display: inline-block; margin: 0px 5px 0px 0px; line-height: 2.6em;} +ul.summary.compact .summary_signature { padding: 5px 7px; padding-right: 4px; } +#content .summary_signature:hover a, +#content .summary_signature:hover a:visited { + background: transparent; + color: #049; +} + +p.inherited a { font-family: monospace; font-size: 0.9em; } +p.inherited { word-spacing: 5px; font-size: 1.2em; } + +p.children { font-size: 1.2em; } +p.children a { font-size: 0.9em; } +p.children strong { font-size: 0.8em; } +p.children strong.modules { padding-left: 5px; } + +ul.fullTree { display: none; padding-left: 0; list-style: none; margin-left: 0; margin-bottom: 10px; } +ul.fullTree ul { margin-left: 0; padding-left: 0; list-style: none; } +ul.fullTree li { text-align: center; padding-top: 18px; padding-bottom: 12px; background: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAHtJREFUeNqMzrEJAkEURdGzuhgZbSoYWcAWoBVsB4JgZAGmphsZCZYzTQgWNCYrDN9RvMmHx+X916SUBFbo8CzD1idXrLErw1mQttgXtyrOcQ/Ny5p4Qh+2XqLYYazsPWNTiuMkRxa4vcV+evuNAUOLIx5+c2hyzv7hNQC67Q+/HHmlEwAAAABJRU5ErkJggg==) no-repeat top center; } +ul.fullTree li:first-child { padding-top: 0; background: transparent; } +ul.fullTree li:last-child { padding-bottom: 0; } +.showAll ul.fullTree { display: block; } +.showAll .inheritName { display: none; } + +#search { position: absolute; right: 12px; top: 0px; z-index: 9000; } +#search a { + display: block; float: left; + padding: 4px 8px; text-decoration: none; color: #05a; fill: #05a; + border: 1px solid #d8d8e5; + border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; + background: #F1F8FF; + box-shadow: -1px 1px 3px #ddd; +} +#search a:hover { background: #f5faff; color: #06b; fill: #06b; } +#search a.active { + background: #568; padding-bottom: 20px; color: #fff; fill: #fff; + border: 1px solid #457; + border-top-left-radius: 5px; border-top-right-radius: 5px; +} +#search a.inactive { color: #999; fill: #999; } +.inheritanceTree, .toggleDefines { + float: right; + border-left: 1px solid #aaa; + position: absolute; top: 0; right: 0; + height: 100%; + background: #f6f6f6; + padding: 5px; + min-width: 55px; + text-align: center; +} + +#menu { font-size: 1.3em; color: #bbb; } +#menu .title, #menu a { font-size: 0.7em; } +#menu .title a { font-size: 1em; } +#menu .title { color: #555; } +#menu a, #menu a:visited { color: #333; text-decoration: none; border-bottom: 1px dotted #bbd; } +#menu a:hover { color: #05a; } + +#footer { margin-top: 15px; border-top: 1px solid #ccc; text-align: center; padding: 7px 0; color: #999; } +#footer a, #footer a:visited { color: #444; text-decoration: none; border-bottom: 1px dotted #bbd; } +#footer a:hover { color: #05a; } + +#listing ul.alpha { font-size: 1.1em; } +#listing ul.alpha { margin: 0; padding: 0; padding-bottom: 10px; list-style: none; } +#listing ul.alpha li.letter { font-size: 1.4em; padding-bottom: 10px; } +#listing ul.alpha ul { margin: 0; padding-left: 15px; } +#listing ul small { color: #666; font-size: 0.7em; } + +li.r1 { background: #f0f0f0; } +li.r2 { background: #fafafa; } + +#content ul.summary li.deprecated .summary_signature a, +#content ul.summary li.deprecated .summary_signature a:visited { text-decoration: line-through; font-style: italic; } + +#toc { + position: relative; + float: right; + overflow-x: auto; + right: -3px; + margin-left: 20px; + margin-bottom: 20px; + padding: 20px; padding-right: 30px; + max-width: 300px; + z-index: 5000; + background: #fefefe; + border: 1px solid #ddd; + box-shadow: -2px 2px 6px #bbb; +} +#toc .title { margin: 0; } +#toc ol { padding-left: 1.8em; } +#toc li { font-size: 1.1em; line-height: 1.7em; } +#toc > ol > li { font-size: 1.1em; font-weight: bold; } +#toc ol > li > ol { font-size: 0.9em; } +#toc ol ol > li > ol { padding-left: 2.3em; } +#toc ol + li { margin-top: 0.3em; } +#toc.hidden { padding: 10px; background: #fefefe; box-shadow: none; } +#toc.hidden:hover { background: #fafafa; } +#filecontents h1 + #toc.nofloat { margin-top: 0; } +@media (max-width: 560px) { + #toc { + margin-left: 0; + margin-top: 16px; + float: none; + max-width: none; + } +} + +/* syntax highlighting */ +.source_code { display: none; padding: 3px 8px; border-left: 8px solid #ddd; margin-top: 5px; } +#filecontents pre.code, .docstring pre.code, .source_code pre { font-family: monospace; } +#filecontents pre.code, .docstring pre.code { display: block; } +.source_code .lines { padding-right: 12px; color: #555; text-align: right; } +#filecontents pre.code, .docstring pre.code, +.tags pre.example { + padding: 9px 14px; + margin-top: 4px; + border: 1px solid #e1e1e8; + background: #f7f7f9; + border-radius: 4px; + font-size: 1em; + overflow-x: auto; + line-height: 1.2em; +} +pre.code { color: #000; tab-size: 2; } +pre.code .info.file { color: #555; } +pre.code .val { color: #036A07; } +pre.code .tstring_content, +pre.code .heredoc_beg, pre.code .heredoc_end, +pre.code .qwords_beg, pre.code .qwords_end, pre.code .qwords_sep, +pre.code .words_beg, pre.code .words_end, pre.code .words_sep, +pre.code .qsymbols_beg, pre.code .qsymbols_end, pre.code .qsymbols_sep, +pre.code .symbols_beg, pre.code .symbols_end, pre.code .symbols_sep, +pre.code .tstring, pre.code .dstring { color: #036A07; } +pre.code .fid, pre.code .rubyid_new, pre.code .rubyid_to_s, +pre.code .rubyid_to_sym, pre.code .rubyid_to_f, +pre.code .dot + pre.code .id, +pre.code .rubyid_to_i pre.code .rubyid_each { color: #0085FF; } +pre.code .comment { color: #0066FF; } +pre.code .const, pre.code .constant { color: #585CF6; } +pre.code .label, +pre.code .symbol { color: #C5060B; } +pre.code .kw, +pre.code .rubyid_require, +pre.code .rubyid_extend, +pre.code .rubyid_include { color: #0000FF; } +pre.code .ivar { color: #318495; } +pre.code .gvar, +pre.code .rubyid_backref, +pre.code .rubyid_nth_ref { color: #6D79DE; } +pre.code .regexp, .dregexp { color: #036A07; } +pre.code a { border-bottom: 1px dotted #bbf; } +/* inline code */ +*:not(pre) > code { + padding: 1px 3px 1px 3px; + border: 1px solid #E1E1E8; + background: #F7F7F9; + border-radius: 4px; +} + +/* Color fix for links */ +#content .summary_desc pre.code .id > .object_link a, /* identifier */ +#content .docstring pre.code .id > .object_link a { color: #0085FF; } +#content .summary_desc pre.code .const > .object_link a, /* constant */ +#content .docstring pre.code .const > .object_link a { color: #585CF6; } diff --git a/file.CHANGELOG.html b/file.CHANGELOG.html new file mode 100644 index 000000000..f28ebaf74 --- /dev/null +++ b/file.CHANGELOG.html @@ -0,0 +1,1294 @@ + + + + + + + File: CHANGELOG + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
    + + +

    Changelog

    + +

    v3.1.3 (NEXT)

    + +

    Full Changelog

    + +

    Features:

    + +
      +
    • Your contribution here
    • +
    + +

    Fixes and enhancements:

    + +
      +
    • Fix compatibility with the openssl 4.0 gem #706
    • +
    • Your contribution here
    • +
    + +

    v3.1.2 (2025-06-28)

    + +

    Full Changelog

    + +

    Fixes and enhancements:

    + +
      +
    • Avoid using the same digest across calls in JWT::JWA::Ecdsa and JWT::JWA::Rsa #697
    • +
    • Fix signing with a EC JWK #699 (@anakinj)
    • +
    + +

    v3.1.1 (2025-06-24)

    + +

    Full Changelog

    + +

    Fixes and enhancements:

    + +
      +
    • Require the algorithm to be provided when signing and verifying tokens using JWKs #695 (@anakinj)
    • +
    + +

    v3.1.0 (2025-06-23)

    + +

    Full Changelog

    + +

    Features:

    + +
      +
    • Add support for x5t header parameter for X.509 certificate thumbprint verification #669 (@hieuk09)
    • +
    • Raise an error if the ECDSA signing or verification key is not an instance of OpenSSL::PKey::EC #688 (@anakinj)
    • +
    • Allow OpenSSL::PKey::EC::Point to be used as the verification key in ECDSA #689 (@anakinj)
    • +
    • Require claims to have been verified before accessing the JWT::EncodedToken#payload #690 (@anakinj)
    • +
    • Support signing and verifying tokens using a JWK #692 (@anakinj)
    • +
    + +

    v3.0.0 (2025-06-14)

    + +

    Full Changelog

    + +

    Breaking changes:

    + +
      +
    • Require token signature to be verified before accessing payload #648 (@anakinj)
    • +
    • Drop support for the HS512256 algorithm #650 (@anakinj)
    • +
    • Remove deprecated claim verification methods #654 (@anakinj)
    • +
    • Remove dependency to rbnacl #655 (@anakinj)
    • +
    • Support only stricter base64 decoding (RFC 4648) #658 (@anakinj)
    • +
    • Custom algorithms are required to include JWT::JWA::SigningAlgorithm #660 (@anakinj)
    • +
    • Require RSA keys to be at least 2048 bits #661 (@anakinj)
    • +
    • Base64 encode and decode the k value for HMAC JWKs #662 (@anakinj)
    • +
    + +

    Take a look at the upgrade guide for more details.

    + +

    Features:

    + +
      +
    • JWT::EncodedToken#verify! method that bundles signature and claim validation #647 (@anakinj)
    • +
    • Do not override the alg header if already given #659 (@anakinj)
    • +
    • Make JWK::KeyFinder compatible with JWT::EncodedToken #663 (@anakinj)
    • +
    + +

    Fixes and enhancements:

    + + + +

    v2.10.1 (2024-12-26)

    + +

    Full Changelog

    + +

    Fixes and enhancements:

    + + + +

    v2.10.0 (2024-12-25)

    + +

    Full Changelog

    + +

    Features:

    + +
      +
    • JWT::Token and JWT::EncodedToken for signing and verifying tokens #621 (@anakinj)
    • +
    • Detached payload support for JWT::Token and JWT::EncodedToken #630 (@anakinj)
    • +
    • Skip decoding payload if b64 header is present and false #631 (@anakinj)
    • +
    • Remove a few custom Rubocop configs #638 (@anakinj)
    • +
    + +

    Fixes and enhancements:

    + +
      +
    • Deprecation warnings for deprecated methods and classes #629 (@anakinj)
    • +
    • Improved documentation for public apis #629 (@anakinj)
    • +
    • Use correct methods when raising error during signing/verification with EdDSA #633
    • +
    • Fix JWT::EncodedToken behavior with empty string as token #640 (@ragalie)
    • +
    • Deprecation warnings for rbnacl backed functionality #641 (@anakinj)
    • +
    + +

    v2.9.3 (2024-10-03)

    + +

    Full Changelog

    + +

    Fixes and enhancements:

    + +
      +
    • Return truthy value for ::JWT::ClaimsValidator#validate! and ::JWT::Verify.verify_claims #628 (@anakinj)
    • +
    + +

    v2.9.2 (2024-10-03)

    + +

    Full Changelog

    + +

    Features:

    + + + +

    Fixes and enhancements:

    + +
      +
    • Updated README to correctly document OpenSSL::HMAC documentation #617 (@aedryan)
    • +
    • Verify JWT header format #622 (@304)
    • +
    • Bring back ::JWT::ClaimsValidator, ::JWT::Verify and a few other removed interfaces for preserved backwards compatibility #624 (@anakinj)
    • +
    + +

    v2.9.1 (2024-09-23)

    + +

    Full Changelog

    + +

    Fixes and enhancements:

    + +
      +
    • Fix regression in iss and aud claim validation #619 (@anakinj)
    • +
    + +

    v2.9.0 (2024-09-15)

    + +

    Full Changelog

    + +

    Features:

    + + + +

    Fixes and enhancements:

    + + + +

    v2.8.2 (2024-06-18)

    + +

    Full Changelog

    + +

    Fixes and enhancements:

    + + + +

    v2.8.1 (2024-02-29)

    + +

    Full Changelog

    + +

    Features:

    + + + +

    Fixes and enhancements:

    + + + +

    v2.8.0 (2024-02-17)

    + +

    Full Changelog

    + +

    Features:

    + +
      +
    • Updated rubocop to 1.56 #573 (@anakinj)
    • +
    • Run CI on Ruby 3.3 #577 (@anakinj)
    • +
    • Deprecation warning added for the HMAC algorithm HS512256 (HMAC-SHA-512 truncated to 256-bits) #575 (@anakinj)
    • +
    • Stop using RbNaCl for standard HMAC algorithms #575 (@anakinj)
    • +
    + +

    Fixes and enhancements:

    + +
      +
    • Fix signature has expired error if payload is a string #555 (@GobinathAL)
    • +
    • Fix key base equality and spaceship operators #569 (@magneland)
    • +
    • Remove explicit base64 require from x5c_key_finder #580 (@anakinj)
    • +
    • Performance improvements and cleanup of tests #581 (@anakinj)
    • +
    • Repair EC x/y coordinates when importing JWK #585 (@julik)
    • +
    • Explicit dependency to the base64 gem #582 (@anakinj)
    • +
    • Deprecation warning for decoding content not compliant with RFC 4648 #582 (@anakinj)
    • +
    • Algorithms moved under the ::JWT::JWA module (@anakinj)
    • +
    + +

    v2.7.1 (2023-06-09)

    + +

    Full Changelog

    + +

    Fixes and enhancements:

    + + + +

    v2.7.0 (2023-02-01)

    + +

    Full Changelog

    + +

    Features:

    + + + +

    Fixes and enhancements:

    + +
      +
    • Fix issue with multiple keys returned by keyfinder and multiple allowed algorithms #545 (@mpospelov)
    • +
    • Non-string kid header values are now rejected #543 (@bellebaum)
    • +
    + +

    v2.6.0 (2022-12-22)

    + +

    Full Changelog

    + +

    Features:

    + +
      +
    • Support custom algorithms by passing algorithm objects #512 (@anakinj)
    • +
    • Support descriptive (not key related) JWK parameters #520 (@bellebaum)
    • +
    • Support for JSON Web Key Sets #525 (@bellebaum)
    • +
    • Support HMAC keys over 32 chars when using RbNaCl #521 (@anakinj)
    • +
    + +

    Fixes and enhancements:

    + +
      +
    • Raise descriptive error on empty hmac_secret and OpenSSL 3.0/openssl gem <3.0.1 #530 (@jonmchan)
    • +
    + +

    v2.5.0 (2022-08-25)

    + +

    Full Changelog

    + +

    Features:

    + + + +

    Fixes and enhancements:

    + +
      +
    • Bring back the old Base64 (RFC2045) deocode mechanisms #488 (@anakinj)
    • +
    • Rescue RbNaCl exception for EdDSA wrong key #491 (@n-studio)
    • +
    • New parameter name for cases when kid is not found using JWK key loader proc #501 (@anakinj)
    • +
    • Fix NoMethodError when a 2 segment token is missing 'alg' header #502 (@cmrd-senya)
    • +
    + +

    v2.4.1 (2022-06-07)

    + +

    Full Changelog

    + +

    Fixes and enhancements:

    + + + +

    v2.4.0 (2022-06-06)

    + +

    Full Changelog

    + +

    Features:

    + + + +

    Fixes and enhancements:

    + + + +

    v2.3.0 (2021-10-03)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • [SECURITY] Algorithm Confusion Through kid Header #440
    • +
    • JWT to memory #436
    • +
    • ArgumentError: wrong number of arguments (given 2, expected 1) #429
    • +
    • HMAC section of README outdated #421
    • +
    • NoMethodError: undefined method `zero?' for nil:NilClass if JWT has no 'alg' field #410
    • +
    • Release new version #409
    • +
    • NameError: uninitialized constant JWT::JWK #403
    • +
    + +

    Merged pull requests:

    + + + +

    v2.2.3 (2021-04-19)

    + +

    Full Changelog

    + +

    Implemented enhancements:

    + +
      +
    • Verify algorithm before evaluating keyfinder #343
    • +
    • Why jwt depends on json < 2.0 ? #179
    • +
    • Support for JWK in-lieu of rsa_public #158
    • +
    • Fix rspec raise_error warning #413 (excpt)
    • +
    • Add support for JWKs with HMAC key type. #372 (phlegx)
    • +
    • Improve 'none' algorithm handling #365 (danleyden)
    • +
    • Handle parsed JSON JWKS input with string keys #348 (martinemde)
    • +
    • Allow Numeric values during encoding #327 (fanfilmu)
    • +
    + +

    Closed issues:

    + +
      +
    • "Signature verification raised", yet jwt.io says "Signature Verified" #401
    • +
    • truffleruby-head build is failing #396
    • +
    • JWT::JWK::EC needs require 'forwardable' #392
    • +
    • How to use a 'signing key' as used by next-auth #389
    • +
    • undefined method `verify' for nil:NilClass when validate a JWT with JWK #383
    • +
    • Make specifying "algorithm" optional on decode #380
    • +
    • ADFS created access tokens can't be validated due to missing 'kid' header #370
    • +
    • new version? #355
    • +
    • JWT gitlab OmniAuth provider setup support #354
    • +
    • Release with support for RSA.import for ruby < 2.4 hasn't been released #347
    • +
    • cannot load such file -- jwt #339
    • +
    + +

    Merged pull requests:

    + + + +

    v2.2.2 (2020-08-18)

    + +

    Full Changelog

    + +

    Implemented enhancements:

    + +
      +
    • JWK does not decode. #332
    • +
    • Inconsistent use of symbol and string keys in args (exp and algorithm). #331
    • +
    • Pin simplecov to < 0.18 #356 (anakinj)
    • +
    • verifies algorithm before evaluating keyfinder #346 (jb08)
    • +
    • Update Rails 6 appraisal to use actual release version #336 (smudge)
    • +
    • Update Travis #326 (berkos)
    • +
    • Improvement/encode hmac without key #312 (JotaSe)
    • +
    + +

    Fixed bugs:

    + +
      +
    • v2.2.1 warning: already initialized constant JWT Error #335
    • +
    • 2.2.1 is no longer raising JWT::DecodeError on nil verification key #328
    • +
    • Fix algorithm picking from decode options #359 (excpt)
    • +
    • Raise error when verification key is empty #358 (anakinj)
    • +
    + +

    Closed issues:

    + +
      +
    • JWT RSA: is it possible to encrypt using the public key? #366
    • +
    • Example unsigned token that bypasses verification #364
    • +
    • Verify exp claim/field even if it's not present #363
    • +
    • Decode any token #360
    • +
    • [question] example of using a pub/priv keys for signing? #351
    • +
    • JWT::ExpiredSignature raised for non-JSON payloads #350
    • +
    • verify_aud only verifies that at least one aud is expected #345
    • +
    • Sinatra 4.90s TTFB #344
    • +
    • How to Logout #342
    • +
    • jwt token decoding even when wrong token is provided for some letters #337
    • +
    • Need to use symbolize_keys everywhere! #330
    • +
    • eval() used in Forwardable limits usage in iOS App Store #324
    • +
    • HS512256 OpenSSL Exception: First num too large #322
    • +
    • Can we change the separator character? #321
    • +
    • Verifying iat without leeway may break with poorly synced clocks #319
    • +
    • Adding support for 'hd' hosted domain string #314
    • +
    • There is no "typ" header in version 2.0.0 #233
    • +
    + +

    Merged pull requests:

    + + + +

    v2.2.1 (2019-05-24)

    + +

    Full Changelog

    + +

    Fixed bugs:

    + +
      +
    • need to require 'forwardable' to use Forwardable #316
    • +
    • Add forwardable dependency for JWK RSA KeyFinder #317 (excpt)
    • +
    + +

    Merged pull requests:

    + + + +

    v2.2.0 (2019-05-23)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • misspelled es512 curve name #310
    • +
    • With Base64 decode i can read the hashed content #306
    • +
    • hide post-it's for graphviz views #303
    • +
    + +

    Merged pull requests:

    + + + +

    v2.2.0.pre.beta.0 (2019-03-20)

    + +

    Full Changelog

    + +

    Implemented enhancements:

    + + + +

    Fixed bugs:

    + +
      +
    • Inconsistent handling of payload claim data types #282
    • +
    • Issued at validation #247
    • +
    • Fix bug and simplify segment validation #292 (anakinj)
    • +
    + +

    Security fixes:

    + +
      +
    • Decoding JWT with ES256 and secp256k1 curve #277
    • +
    + +

    Closed issues:

    + +
      +
    • RS256, public and private keys #291
    • +
    • Allow passing current time to decode #288
    • +
    • Verify exp claim without verifying jwt #281
    • +
    • Audience as an array - how to specify? #276
    • +
    • signature validation using decode method for JWT #271
    • +
    • JWT is easily breakable #267
    • +
    • Ruby JWT Token #265
    • +
    • ECDSA supported algorithms constant is defined as a string, not an array #264
    • +
    • NoMethodError: undefined method `group' for <xxxxx> #261
    • +
    • 'DecodeError'will replace 'ExpiredSignature' #260
    • +
    • TypeError: no implicit conversion of OpenSSL::PKey::RSA into String #259
    • +
    • NameError: uninitialized constant JWT::Algos::Eddsa::RbNaCl #258
    • +
    • Get new token if current token expired #256
    • +
    • Infer algorithm from header #254
    • +
    • Why is the result of decode is an array? #252
    • +
    • Add support for headless token #251
    • +
    • Leeway or exp_leeway #215
    • +
    • Could you describe purpose of cert fixtures and their cryptokey lengths. #185
    • +
    + +

    Merged pull requests:

    + + + +

    v2.1.0 (2017-10-06)

    + +

    Full Changelog

    + +

    Implemented enhancements:

    + + + +

    Fixed bugs:

    + +
      +
    • JWT.encode failing on encode for string #235
    • +
    • The README says it uses an algorithm by default #226
    • +
    • Fix string payload issue #236 (excpt)
    • +
    + +

    Security fixes:

    + + + +

    Closed issues:

    + +
      +
    • Change from 1.5.6 to 2.0.0 and appears a "Completed 401 Unauthorized" #240
    • +
    • Why doesn't the decode function use a default algorithm? #227
    • +
    + +

    Merged pull requests:

    + + + +

    v2.0.0 (2017-09-03)

    + +

    Full Changelog

    + +

    Fixed bugs:

    + +
      +
    • Support versions outside 2.1 #209
    • +
    • Verifying expiration without leeway throws exception #206
    • +
    • Ruby interpreter warning #200
    • +
    • TypeError: no implicit conversion of String into Integer #188
    • +
    • Fix JWT.encode(nil) #203 (tmm1)
    • +
    + +

    Closed issues:

    + +
      +
    • Possibility to disable claim verifications #222
    • +
    • Proper way to verify Firebase id tokens #216
    • +
    + +

    Merged pull requests:

    + + + +

    v2.0.0.beta1 (2017-02-27)

    + +

    Full Changelog

    + +

    Implemented enhancements:

    + + + +

    Fixed bugs:

    + +
      +
    • ruby-jwt::raw_to_asn1: Fails for signatures less than byte_size #155
    • +
    • The leeway parameter is applies to all time based verifications #129
    • +
    • Make algorithm option required to verify signature #184 (EmilioCristalli)
    • +
    • Validate audience when payload is a scalar and options is an array #183 (steti)
    • +
    + +

    Closed issues:

    + +
      +
    • Different encoded value between servers with same password #197
    • +
    • Signature is different at each run #190
    • +
    • Include custom headers with password #189
    • +
    • can't create token - 'NotImplementedError: Unsupported signing method' #186
    • +
    • Cannot verify JWT at all?? #177
    • +
    • verify_iss: true is raising JWT::DecodeError instead of JWT::InvalidIssuerError #170
    • +
    + +

    Merged pull requests:

    + + + +

    v1.5.6 (2016-09-19)

    + +

    Full Changelog

    + +

    Fixed bugs:

    + +
      +
    • Fix missing symbol handling in aud verify code #166 (excpt)
    • +
    + +

    Merged pull requests:

    + + + +

    v1.5.5 (2016-09-16)

    + +

    Full Changelog

    + +

    Implemented enhancements:

    + +
      +
    • JWT.decode always raises JWT::ExpiredSignature for tokens created with Time objects passed as the exp parameter #148
    • +
    + +

    Fixed bugs:

    + +
      +
    • expiration check does not give "Signature has expired" error for the exact time of expiration #157
    • +
    • JTI claim broken? #152
    • +
    • Audience Claim broken? #151
    • +
    • 1.5.3 breaks compatibility with 1.5.2 #133
    • +
    • Version 1.5.3 breaks 1.9.3 compatibility, but not documented as such #132
    • +
    • Fix: exp claim check #161 (excpt)
    • +
    + +

    Security fixes:

    + +
      +
    • [security] Signature verified after expiration/sub/iss checks #153
    • +
    • Signature validation before claim verification #160 (excpt)
    • +
    + +

    Closed issues:

    + +
      +
    • Rendering Json Results in JWT::DecodeError #162
    • +
    • PHP Libraries #154
    • +
    • Is ruby-jwt thread-safe? #150
    • +
    • JWT 1.5.3 #143
    • +
    • gem install v 1.5.3 returns error #141
    • +
    • Adding a CHANGELOG #140
    • +
    + +

    Merged pull requests:

    + + + +

    v1.5.4 (2016-03-24)

    + +

    Full Changelog

    + +

    Closed issues:

    + + + +

    Merged pull requests:

    + + + +

    v1.5.3 (2016-02-24)

    + +

    Full Changelog

    + +

    Implemented enhancements:

    + +
      +
    • Refactor obsolete code for ruby 1.8 support #120
    • +
    • Fix "Rubocop/Metrics/CyclomaticComplexity" issue in lib/jwt.rb #106
    • +
    • Fix "Rubocop/Metrics/CyclomaticComplexity" issue in lib/jwt.rb #105
    • +
    • Allow a proc to be passed for JTI verification #126 (yahooguntu)
    • +
    • Relax restrictions on "jti" claim verification #113 (lwe)
    • +
    + +

    Closed issues:

    + +
      +
    • Verifications not functioning in latest release #128
    • +
    • Base64 is generating invalid length base64 strings - cross language interop #127
    • +
    • Digest::Digest is deprecated; use Digest #119
    • +
    • verify_rsa no method 'verify' for class String #115
    • +
    • Add a changelog #111
    • +
    + +

    Merged pull requests:

    + + + +

    jwt-1.5.2 (2015-10-27)

    + +

    Full Changelog

    + +

    Implemented enhancements:

    + +
      +
    • Must we specify algorithm when calling decode to avoid vulnerabilities? #107
    • +
    • Code review: Rspec test refactoring #85 (excpt)
    • +
    + +

    Fixed bugs:

    + +
      +
    • aud verifies if aud is passed in, :sub does not #102
    • +
    • iat check does not use leeway so nbf could pass, but iat fail #83
    • +
    + +

    Closed issues:

    + +
      +
    • Test ticket from Code Climate #104
    • +
    • Test ticket from Code Climate #100
    • +
    • Is it possible to decode the payload without validating the signature? #97
    • +
    • What is audience? #96
    • +
    • Options hash uses both symbols and strings as keys. #95
    • +
    + +

    Merged pull requests:

    + + + +

    jwt-1.5.1 (2015-06-22)

    + +

    Full Changelog

    + +

    Implemented enhancements:

    + +
      +
    • Fix either README or source code #78
    • +
    • Validate against draft 20 #38
    • +
    + +

    Fixed bugs:

    + +
      +
    • ECDSA signature verification fails for valid tokens #84
    • +
    • Shouldn't verification of additional claims, like iss, aud etc. be enforced when in options? #81
    • +
    • decode fails with 'none' algorithm and verify #75
    • +
    + +

    Closed issues:

    + +
      +
    • Doc mismatch: uninitialized constant JWT::ExpiredSignature #79
    • +
    • TypeError when specifying a wrong algorithm #77
    • +
    • jti verification doesn't prevent replays #73
    • +
    + +

    Merged pull requests:

    + +
      +
    • Correctly sign ECDSA JWTs #87 (jurriaan)
    • +
    • fixed results of decoded tokens in readme #86 (piscolomo)
    • +
    • Force verification of "iss" and "aud" claims #82 (lwe)
    • +
    + +

    jwt-1.5.0 (2015-05-09)

    + +

    Full Changelog

    + +

    Implemented enhancements:

    + +
      +
    • Needs to support asymmetric key signatures over shared secrets #46
    • +
    • Implement Elliptic Curve Crypto Signatures #74 (jtdowney)
    • +
    • Add an option to verify the signature on decode #71 (javawizard)
    • +
    + +

    Closed issues:

    + +
      +
    • Check JWT vulnerability #76
    • +
    + +

    Merged pull requests:

    + +
      +
    • Fixed some examples to make them copy-pastable #72 (jer)
    • +
    + +

    jwt-1.4.1 (2015-03-12)

    + +

    Full Changelog

    + +

    Fixed bugs:

    + +
      +
    • jti verification not working per the spec #68
    • +
    • Verify ISS should be off by default #66
    • +
    + +

    Merged pull requests:

    + +
      +
    • Fix #66 #68 #69 (excpt)
    • +
    • When throwing errors, mention expected/received values #65 (rolodato)
    • +
    + +

    jwt-1.4.0 (2015-03-10)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • The behavior using 'json' differs from 'multi_json' #41
    • +
    + +

    Merged pull requests:

    + + + +

    jwt-1.3.0 (2015-02-24)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • Signature Verification to Return Verification Error rather than decode error #57
    • +
    • Incorrect readme for leeway #55
    • +
    • What is the reason behind stripping the = in base64 encoding? #54
    • +
    • Preparations for version 2.x #50
    • +
    • Release a new version #47
    • +
    • Catch up for ActiveWhatever 4.1.1 series #40
    • +
    + +

    Merged pull requests:

    + +
      +
    • raise verification error for signature verification #58 (punkle)
    • +
    • Added support for not before claim verification #56 (punkle)
    • +
    + +

    jwt-1.2.1 (2015-01-22)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • JWT.encode(10, "secret") #52
    • +
    • JWT.encode(10, "secret") #51
    • +
    + +

    Merged pull requests:

    + +
      +
    • Accept expiration claims as string #53 (yarmand)
    • +
    + +

    jwt-1.2.0 (2014-11-24)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • set token to expire #42
    • +
    + +

    Merged pull requests:

    + + + +

    jwt-0.1.13 (2014-05-08)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • yanking of version 0.1.12 causes issues #39
    • +
    • Semantic versioning #37
    • +
    • Update gem to get latest changes #36
    • +
    + +

    jwt-1.0.0 (2014-05-07)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • API request - JWT::decoded_header() #26
    • +
    + +

    Merged pull requests:

    + + + +

    jwt-0.1.11 (2014-01-17)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • url safe encode and decode #28
    • +
    • Release #27
    • +
    + +

    Merged pull requests:

    + + + +

    jwt-0.1.10 (2014-01-10)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • change to signature of JWT.decode method #14
    • +
    + +

    Merged pull requests:

    + +
      +
    • Fix warning: assigned but unused variable - e #25 (sferik)
    • +
    • Echoe doesn't define a license= method #24 (sferik)
    • +
    • Use OpenSSL::Digest instead of deprecated OpenSSL::Digest::Digest #23 (JuanitoFatas)
    • +
    • Handle some invalid JWTs #22 (steved)
    • +
    • Add MIT license to gemspec #21 (nycvotes-dev)
    • +
    • Tweaks and improvements #20 (threedaymonk)
    • +
    • Don't leave errors in OpenSSL.errors when there is a decoding error. #19 (lowellk)
    • +
    + +

    jwt-0.1.8 (2013-03-14)

    + +

    Full Changelog

    + +

    Merged pull requests:

    + + + +

    jwt-0.1.7 (2013-03-07)

    + +

    Full Changelog

    + +

    Merged pull requests:

    + +
      +
    • Catch MultiJson::LoadError and reraise as JWT::DecodeError #16 (rwygand)
    • +
    + +

    jwt-0.1.6 (2013-03-05)

    + +

    Full Changelog

    + +

    Merged pull requests:

    + +
      +
    • Fixes a theoretical timing attack #15 (mgates)
    • +
    • Use StandardError as parent for DecodeError #13 (Oscil8)
    • +
    + +

    jwt-0.1.5 (2012-07-20)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • Unable to specify signature header fields #7
    • +
    + +

    Merged pull requests:

    + + + +

    jwt-0.1.4 (2011-11-11)

    + +

    Full Changelog

    + +

    Merged pull requests:

    + + + +

    jwt-0.1.3 (2011-06-30)

    + +

    Full Changelog

    + +

    Closed issues:

    + +
      +
    • signatures calculated incorrectly (hexdigest instead of digest) #1
    • +
    + +

    Merged pull requests:

    + + +
    + + + +
    + + \ No newline at end of file diff --git a/file.CONTRIBUTING.html b/file.CONTRIBUTING.html new file mode 100644 index 000000000..f356d4bae --- /dev/null +++ b/file.CONTRIBUTING.html @@ -0,0 +1,160 @@ + + + + + + + File: CONTRIBUTING + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
    + + +

    Contributing to ruby-jwt

    + +

    Forking the project

    + +

    Fork the project on GitHub and clone your own fork. Instructions on forking can be found from the GitHub Docs

    + +
    git clone git@github.com:you/ruby-jwt.git
    +cd ruby-jwt
    +git remote add upstream https://github.com/jwt/ruby-jwt
    +
    + +

    Create a branch for your implementation

    + +

    Make sure you have the latest upstream main branch of the project.

    + +
    git fetch --all
    +git checkout main
    +git rebase upstream/main
    +git push origin main
    +git checkout -b fix-a-little-problem
    +
    + +

    Running the tests and linter

    + +

    Before you start with your implementation make sure you are able to get a successful test run with the current revision.

    + +

    The tests are written with rspec and Appraisal is used to ensure compatibility with 3rd party dependencies providing cryptographic features.

    + +

    Rubocop is used to enforce the Ruby style.

    + +

    To run the complete set of tests and linter run the following

    + +
    bundle install
    +bundle exec appraisal rake test
    +bundle exec rubocop
    +
    + +

    Implement your feature

    + +

    Implement tests and your change. Don't be shy adding a little something in the README. +Add a short description of the change in either the Features or Fixes section in the CHANGELOG file.

    + +

    The form of the row (You need to return to the row when you know the pull request id)

    + +
    - Fix a little problem [#123](https://github.com/jwt/ruby-jwt/pull/123) - [@you](https://github.com/you).
    +
    + +

    Push your branch and create a pull request

    + +

    Before pushing make sure the tests pass and RuboCop is happy.

    + +
    bundle exec appraisal rake test
    +bundle exec rubocop
    +git push origin fix-a-little-problem
    +
    + +

    Make a new pull request on the ruby-jwt project with a description what the change is about.

    + +

    Update the CHANGELOG, again

    + +

    Update the CHANGELOG with the pull request id from the previous step.

    + +

    You can amend the previous commit with the updated changelog change and force push your branch. The PR will get automatically updated.

    + +
    git add CHANGELOG.md
    +git commit --amend --no-edit
    +git push origin fix-a-little-problem -f
    +
    + +

    Keep an eye on your pull request

    + +

    A maintainer will review and probably merge you changes when time allows, be patient.

    + +

    Keeping your branch up-to-date

    + +

    It's recommended that you keep your branch up-to-date by rebasing to the upstream main.

    + +
    git fetch upstream
    +git checkout fix-a-little-problem
    +git rebase upstream/main
    +git push origin fix-a-little-problem -f
    +
    + +

    Releasing a new version

    + +

    The version is using the Semantic Versioning and the version is located in the version.rb file. +Also update the CHANGELOG to reflect the upcoming version release.

    + +
    rake release
    +
    +
    + + + +
    + + \ No newline at end of file diff --git a/file.LICENSE.html b/file.LICENSE.html new file mode 100644 index 000000000..d46f55d5a --- /dev/null +++ b/file.LICENSE.html @@ -0,0 +1,78 @@ + + + + + + + File: LICENSE + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
    + + +
    +

    Copyright © 2011 Jeff Lindsay

    + +

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

    + +

    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

    + +

    THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    +
    + + + +
    + + \ No newline at end of file diff --git a/file.README.html b/file.README.html new file mode 100644 index 000000000..4b1e6b607 --- /dev/null +++ b/file.README.html @@ -0,0 +1,845 @@ + + + + + + + File: README + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
    + + +

    JWT

    + +

    Gem Version +Build Status +Maintainability +Code Coverage

    + +

    A ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.

    + +

    If you have further questions related to development or usage, join us: ruby-jwt google group.

    + +

    See CHANGELOG.md for a complete set of changes and upgrade guide for upgrading between major versions.

    + +

    Sponsors

    + +
+ + + + + + + + + +
LogoMessage
auth0 logoIf you want to quickly add secure token-based authentication to Ruby projects, feel free to check Auth0's Ruby SDK and free plan at auth0.com/developers
+ +

Installing

+ +

Using Rubygems

+ +
gem install jwt
+
+ +

Using Bundler

+ +

Add the following to your Gemfile

+ +
gem 'jwt'
+
+ +

And run bundle install

+ +

Finally require the gem in your application

+ +
require 'jwt'
+
+ +

Algorithms and Usage

+ +

The jwt gem natively supports the NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms via the openssl library. The gem can be extended with additional or alternative implementations of the algorithms via extensions.

+ +

Additionally the EdDSA algorithm is supported via a the jwt-eddsa gem.

+ +

For safe 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. It is strongly recommended that you hard code the algorithm, as you may leave yourself vulnerable by dynamically picking the algorithm

+ +

See JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS

+ +

NONE

+ +
    +
  • none - unsigned token
  • +
+ +
payload = { data: 'test' }
+token   = JWT.encode(payload, nil, 'none')
+# => "eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
+
+decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
+#  => [
+#       {"data"=>"test"}, # payload
+#       {"alg"=>"none"} # header
+#     ]
+
+ +

HMAC

+ +
    +
  • HS256 - HMAC using SHA-256 hash algorithm
  • +
  • HS384 - HMAC using SHA-384 hash algorithm
  • +
  • HS512 - HMAC using SHA-512 hash algorithm
  • +
+ +
payload     = { data: 'test' }
+hmac_secret = 'my$ecretK3y'
+
+token = JWT.encode(payload, hmac_secret, 'HS256')
+# => "eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y"
+
+decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
+# => [
+#      {"data"=>"test"}, # payload
+#      {"alg"=>"HS256"} # header
+#    ]
+
+ +

RSA

+ +
    +
  • RS256 - RSA using SHA-256 hash algorithm
  • +
  • RS384 - RSA using SHA-384 hash algorithm
  • +
  • RS512 - RSA using SHA-512 hash algorithm
  • +
+ +
payload     = { data: 'test' }
+rsa_private = OpenSSL::PKey::RSA.generate(2048)
+rsa_public  = rsa_private.public_key
+
+token = JWT.encode(payload, rsa_private, 'RS256')
+# => "eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.CCkO35qFPijW8Gwhbt8a80PB9fc9FJ19hCMnXSgoDF6Mlvlt0A4G-ah..."
+
+decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })
+# => [
+#      {"data"=>"test"}, # payload
+#      {"alg"=>"RS256"} # header
+#    ]
+
+ +

ECDSA

+ +
    +
  • ES256 - ECDSA using P-256 and SHA-256
  • +
  • ES384 - ECDSA using P-384 and SHA-384
  • +
  • ES512 - ECDSA using P-521 and SHA-512
  • +
  • ES256K - ECDSA using P-256K and SHA-256
  • +
+ +
payload   = { data: 'test' }
+ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
+
+token = JWT.encode(payload, ecdsa_key, 'ES256')
+# => "eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg"
+
+decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' })
+# => [
+#      {"test"=>"data"}, # payload
+#      {"alg"=>"ES256"} # header
+#    ]
+
+ +

EdDSA

+ +

Since version 3.0, the EdDSA algorithm has been moved to the jwt-eddsa gem.

+ +

RSASSA-PSS

+ +
    +
  • PS256 - RSASSA-PSS using SHA-256 hash algorithm
  • +
  • PS384 - RSASSA-PSS using SHA-384 hash algorithm
  • +
  • PS512 - RSASSA-PSS using SHA-512 hash algorithm
  • +
+ +
payload     = { data: 'test' }
+rsa_private = OpenSSL::PKey::RSA.generate(2048)
+rsa_public  = rsa_private.public_key
+
+token = JWT.encode(payload, rsa_private, 'PS256')
+# => "eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.BRWizdUjD5zAWw-EDBcrl3dDpQDAePz9Ol3XKC43SggU47G8OWwveA_..."
+
+decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' })
+# => [
+#      {"data"=>"test"}, # payload
+#      {"alg"=>"PS256"} # header
+#    ]
+
+ +

Custom algorithms

+ +

When encoding or decoding a token, you can pass in a custom object through the algorithm option to handle signing or verification. This custom object must include or extend the JWT::JWA::SigningAlgorithm module and implement certain methods:

+ +
    +
  • For decoding/verifying: The object must implement the methods alg and verify.
  • +
  • For encoding/signing: The object must implement the methods alg and sign.
  • +
+ +

For customization options check the details from JWT::JWA::SigningAlgorithm.

+ +
module CustomHS512Algorithm
+  extend JWT::JWA::SigningAlgorithm
+
+  def self.alg
+    'HS512'
+  end
+
+  def self.sign(data:, signing_key:)
+    OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), signing_key, data)
+  end
+
+  def self.verify(data:, signature:, verification_key:)
+    ::OpenSSL.secure_compare(sign(data: data, signing_key: verification_key), signature)
+  end
+end
+
+payload  = { data: 'test' }
+token    = JWT.encode(payload, 'secret', CustomHS512Algorithm)
+# => "eyJhbGciOiJIUzUxMiJ9.eyJkYXRhIjoidGVzdCJ9.aBNoejLEM2WMF3TxzRDKlehYdG2ATvFpGNauTI4GSD2VJseS_sC8covrVMlgslf0aJM4SKb3EIeORJBFPtZ33w"
+
+decoded_token = JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
+# => [
+#      {"data"=>"test"}, # payload
+#      {"alg"=>"HS512"} # header
+#    ]
+
+ +

Add custom header fields

+ +

The ruby-jwt gem supports custom header fields +To add custom header fields you need to pass header_fields parameter

+ +
payload = { data: 'test' }
+
+token = JWT.encode(payload, nil, 'none', { typ: 'JWT' })
+# => "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
+
+decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
+#  => [
+#       {"data"=>"test"}, # payload
+#       {"typ"=>"JWT", "alg"=>"none"} # header
+#     ]
+
+ +

JWT::Token and JWT::EncodedToken

+ +

The JWT::Token and JWT::EncodedToken classes can be used to manage your JWTs.

+ +

Signing and encoding a token

+ +
payload = { exp: Time.now.to_i + 60, jti: '1234', sub: "my-subject" }
+header =  { kid: 'hmac' }
+
+token = JWT::Token.new(payload: payload, header: header)
+token.sign!(algorithm: 'HS256', key: "secret")
+
+token.jwt
+# => "eyJraWQiOiJobWFjIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjE3NTAwMDU0NzksImp0aSI6IjEyMzQiLCJzdWIiOiJteS1zdWJqZWN0In0.NRLcK6fYr3IdNfmncJePMWLQ34M4n14EgqSYrQIjL9w"
+
+ +

Verifying and decoding a token

+ +

The JWT::EncodedToken can be used as a token object that allows verification of signatures and claims.

+ +
encoded_token = JWT::EncodedToken.new(token.jwt)
+
+encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
+encoded_token.verify_signature!(algorithm: 'HS256', key: "wrong_secret") # raises JWT::VerificationError
+encoded_token.verify_claims!(:exp, :jti)
+encoded_token.verify_claims!(sub: ["not-my-subject"]) # raises JWT::InvalidSubError
+encoded_token.claim_errors(sub: ["not-my-subject"]).map(&:message) # => ["Invalid subject. Expected [\"not-my-subject\"], received my-subject"]
+encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
+encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
+
+ +

The JWT::EncodedToken#verify! method can be used to verify signature and claim verification in one go. The exp claim is verified by default.

+ +
encoded_token = JWT::EncodedToken.new(token.jwt)
+encoded_token.verify!(signature: {algorithm: 'HS256', key: "secret"})
+encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
+encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
+
+ +

A JWK can be used to sign and verify the token if it's possible to derive the signing algorithm from the key.

+ +
jwk_json = '{
+ "kty": "oct",
+ "k": "c2VjcmV0",
+ "alg": "HS256",
+ "kid": "hmac"
+}'
+
+jwk = JWT::JWK.import(JSON.parse(jwk_json))
+
+token = JWT::Token.new(payload: payload, header: header)
+
+token.sign!(key: jwk, algorithm: 'HS256')
+
+encoded_token = JWT::EncodedToken.new(token.jwt)
+encoded_token.verify!(signature: { algorithm: ["HS256", "HS512"], key: jwk})
+
+ +

Using a keyfinder

+ +

A keyfinder can be used to verify a signature. A keyfinder is an object responding to the #call method. The method expects to receive one argument, which is the token to be verified.

+ +

An example on using the built-in JWK keyfinder.

+ +
# Create and sign a token
+jwk = JWT::JWK.new(OpenSSL::PKey::RSA.generate(2048))
+token = JWT::Token.new(payload: { pay: 'load' }, header: { kid: jwk.kid })
+token.sign!(algorithm: 'RS256', key: jwk.signing_key)
+
+# Create keyfinder object, verify and decode token
+key_finder = JWT::JWK::KeyFinder.new(jwks: JWT::JWK::Set.new(jwk))
+encoded_token = JWT::EncodedToken.new(token.jwt)
+encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: key_finder})
+encoded_token.payload # => { 'pay' => 'load' }
+
+ +

Using a custom keyfinder proc.

+ +
# Create and sign a token
+key = OpenSSL::PKey::RSA.generate(2048)
+token = JWT::Token.new(payload: { pay: 'load' })
+token.sign!(algorithm: 'RS256', key: key)
+
+# Verify and decode token
+encoded_token = JWT::EncodedToken.new(token.jwt)
+encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: ->(_token){ key.public_key }})
+encoded_token.payload # => { 'pay' => 'load' }
+
+ +

Detached payload

+ +

The ::JWT::Token#detach_payload! method can be use to detach the payload from the JWT.

+ +
token = JWT::Token.new(payload: { pay: 'load' })
+token.sign!(algorithm: 'HS256', key: "secret")
+token.detach_payload!
+token.jwt # => "eyJhbGciOiJIUzI1NiJ9..UEhDY1Qlj29ammxuVRA_-gBah4qTy5FngIWg0yEAlC0"
+token.encoded_payload # => "eyJwYXkiOiJsb2FkIn0"
+
+ +

The JWT::EncodedToken class can be used to decode a token with a detached payload by providing the payload to the token instance in separate.

+ +
encoded_token = JWT::EncodedToken.new(token.jwt)
+encoded_token.encoded_payload = "eyJwYXkiOiJsb2FkIn0"
+encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
+encoded_token.payload # => {"pay"=>"load"}
+
+ +

Claims

+ +

JSON Web Token defines some reserved claim names and defines how they should be +used. JWT supports these reserved claim names:

+ +
    +
  • 'exp' (Expiration Time) Claim
  • +
  • 'nbf' (Not Before Time) Claim
  • +
  • 'iss' (Issuer) Claim
  • +
  • 'aud' (Audience) Claim
  • +
  • 'jti' (JWT ID) Claim
  • +
  • 'iat' (Issued At) Claim
  • +
  • 'sub' (Subject) Claim
  • +
+ +

Expiration Time Claim

+ +

From Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim:

+ +
+

The exp (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the exp claim requires that the current date/time MUST be before the expiration date/time listed in the exp claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

+
+ +
exp = Time.now.to_i + 4 * 3600
+exp_payload = { data: 'data', exp: exp }
+
+token = JWT.encode(exp_payload, hmac_secret, 'HS256')
+
+begin
+  decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
+rescue JWT::ExpiredSignature
+  # Handle expired token, e.g. logout user or deny access
+end
+
+ +

The Expiration Claim verification can be disabled.

+ +
# Decode token without raising JWT::ExpiredSignature error
+JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' })
+
+ +

Leeway and the exp claim.

+ +
exp = Time.now.to_i - 10
+leeway = 30 # seconds
+
+exp_payload = { data: 'data', exp: exp }
+
+# build expired token
+token = JWT.encode(exp_payload, hmac_secret, 'HS256')
+
+begin
+  # add leeway to ensure the token is still accepted
+  decoded_token = JWT.decode(token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' })
+rescue JWT::ExpiredSignature
+  # Handle expired token, e.g. logout user or deny access
+end
+
+ +

Not Before Time Claim

+ +

From Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim:

+ +
+

The nbf (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the nbf claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the nbf claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

+
+ +
nbf = Time.now.to_i - 3600
+nbf_payload = { data: 'data', nbf: nbf }
+
+token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
+
+begin
+  decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
+rescue JWT::ImmatureSignature
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

The Not Before Claim verification can be disabled.

+ +
# Decode token without raising JWT::ImmatureSignature error
+JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' })
+
+ +

Leeway and the nbf claim.

+ +
nbf = Time.now.to_i + 10
+leeway = 30
+
+nbf_payload = { data: 'data', nbf: nbf }
+
+# build expired token
+token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
+
+begin
+  # add leeway to ensure the token is valid
+  decoded_token = JWT.decode(token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' })
+rescue JWT::ImmatureSignature
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

Issuer Claim

+ +

From Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim:

+ +
+

The iss (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The iss value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.

+
+ +

You can pass multiple allowed issuers as an Array, verification will pass if one of them matches the iss value in the payload.

+ +
iss = 'My Awesome Company Inc. or https://my.awesome.website/'
+iss_payload = { data: 'data', iss: iss }
+
+token = JWT.encode(iss_payload, hmac_secret, 'HS256')
+
+begin
+  # Add iss to the validation to check if the token has been manipulated
+  decoded_token = JWT.decode(token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' })
+rescue JWT::InvalidIssuerError
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

You can also pass a Regexp or Proc (with arity 1), verification will pass if the regexp matches or the proc returns truthy. +On supported ruby versions (>= 2.5) you can also delegate to methods, on older versions you will have +to convert them to proc (using to_proc)

+ +
JWT.decode(token, hmac_secret, true,
+           iss: %r'https://my.awesome.website/',
+           verify_iss: true,
+           algorithm: 'HS256')
+
+ +
JWT.decode(token, hmac_secret, true,
+           iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') },
+           verify_iss: true,
+           algorithm: 'HS256')
+
+ +
JWT.decode(token, hmac_secret, true,
+           iss: method(:valid_issuer?),
+           verify_iss: true,
+           algorithm: 'HS256')
+
+# somewhere in the same class:
+def valid_issuer?(issuer)
+  # custom validation
+end
+
+ +

Audience Claim

+ +

From Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim:

+ +
+

The aud (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT MUST be rejected. In the general case, the aud value is an array of case-sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the aud value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.

+
+ +
aud = ['Young', 'Old']
+aud_payload = { data: 'data', aud: aud }
+
+token = JWT.encode(aud_payload, hmac_secret, 'HS256')
+
+begin
+  # Add aud to the validation to check if the token has been manipulated
+  decoded_token = JWT.decode(token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' })
+rescue JWT::InvalidAudError
+  # Handle invalid token, e.g. logout user or deny access
+  puts 'Audience Error'
+end
+
+ +

JWT ID Claim

+ +

From Oauth JSON Web Token 4.1.7. "jti" (JWT ID) Claim:

+ +
+

The jti (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The jti claim can be used to prevent the JWT from being replayed. The jti value is a case-sensitive string. Use of this claim is OPTIONAL.

+
+ +
# Use the secret and iat to create a unique key per request to prevent replay attacks
+jti_raw = [hmac_secret, iat].join(':').to_s
+jti = Digest::MD5.hexdigest(jti_raw)
+jti_payload = { data: 'data', iat: iat, jti: jti }
+
+token = JWT.encode(jti_payload, hmac_secret, 'HS256')
+
+begin
+  # If :verify_jti is true, validation will pass if a JTI is present
+  #decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' })
+  # Alternatively, pass a proc with your own code to check if the JTI has already been used
+  decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' })
+  # or
+  decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' })
+rescue JWT::InvalidJtiError
+  # Handle invalid token, e.g. logout user or deny access
+  puts 'Error'
+end
+
+ +

Issued At Claim

+ +

From Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim:

+ +
+

The iat (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. The leeway option is not taken into account when verifying this claim. The iat_leeway option was removed in version 2.2.0. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

+
+ +
iat = Time.now.to_i
+iat_payload = { data: 'data', iat: iat }
+
+token = JWT.encode(iat_payload, hmac_secret, 'HS256')
+
+begin
+  # Add iat to the validation to check if the token has been manipulated
+  decoded_token = JWT.decode(token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' })
+rescue JWT::InvalidIatError
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

Subject Claim

+ +

From Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim:

+ +
+

The sub (subject) claim identifies the principal that is the subject of the JWT. The Claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The sub value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.

+
+ +
sub = 'Subject'
+sub_payload = { data: 'data', sub: sub }
+
+token = JWT.encode(sub_payload, hmac_secret, 'HS256')
+
+begin
+  # Add sub to the validation to check if the token has been manipulated
+  decoded_token = JWT.decode(token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' })
+rescue JWT::InvalidSubError
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

Standalone claim verification

+ +

The JWT claim verifications can be used to verify any Hash to include expected keys and values.

+ +

A few example on verifying the claims for a payload:

+ +
JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :numeric, :exp)
+JWT::Claims.valid_payload?({"exp" => Time.now.to_i + 10}, :exp)
+# => true
+JWT::Claims.payload_errors({"exp" => Time.now.to_i - 10}, :exp)
+# => [#<struct JWT::Claims::Error message="Signature has expired">]
+JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11})
+
+JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10, "sub" => "subject"}, :exp, sub: "subject")
+
+ +

Finding a Key

+ +

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.

+ +
issuers = %w[My_Awesome_Company1 My_Awesome_Company2]
+iss_payload = { data: 'data', iss: issuers.first }
+
+secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' }
+
+token = JWT.encode(iss_payload, hmac_secret, 'HS256')
+
+begin
+  # Add iss to the validation to check if the token has been manipulated
+  decoded_token = JWT.decode(token, nil, true, { iss: issuers, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload|
+    secrets[payload['iss']]
+  end
+rescue JWT::InvalidIssuerError
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

Required Claims

+ +

You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing

+ +
# Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
+JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' })
+
+ +

X.509 certificates in x5c header

+ +

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).

+ +
root_certificates = [] # trusted `OpenSSL::X509::Certificate` objects
+crl_uris = root_certificates.map(&:crl_uris)
+crls = crl_uris.map do |uri|
+  # look up cached CRL by `uri` and return it if found, otherwise continue
+  crl = Net::HTTP.get(uri)
+  crl = OpenSSL::X509::CRL.new(crl)
+  # cache `crl` using `uri` as the key, expiry set to `crl.next_update` timestamp
+end
+
+begin
+  JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls } })
+rescue JWT::DecodeError
+  # Handle error, e.g. x5c header certificate revoked or expired
+end
+
+ +

JSON Web Key (JWK)

+ +

JWK is a JSON structure representing a cryptographic key. This gem currently supports RSA, EC, OKP and HMAC keys. OKP support requires RbNaCl and currently only supports the Ed25519 curve.

+ +

To encode a JWT using your JWK:

+ +
optional_parameters = { kid: 'my-kid', use: 'sig', alg: 'RS512' }
+jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters)
+
+# Encoding
+payload = { data: 'data' }
+token = JWT.encode(payload, jwk.signing_key, jwk[:alg], kid: jwk[:kid])
+
+# JSON Web Key Set for advertising your signing keys
+jwks_hash = JWT::JWK::Set.new(jwk).export
+
+ +

To decode a JWT using a trusted entity's JSON Web Key Set (JWKS):

+ +
jwks = JWT::JWK::Set.new(jwks_hash)
+jwks.filter! {|key| key[:use] == 'sig' } # Signing keys only!
+algorithms = jwks.map { |key| key[:alg] }.compact.uniq
+JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
+
+ +

The jwks option can also be given as a lambda that evaluates every time a key identifier is resolved. +This can be used to implement caching of remotely fetched JWK Sets.

+ +

Key identifiers can be specified using kid, x5t header parameters. +If the requested identifier 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.

+ +

Tokens without a specified key identifier (kid or x5t) are rejected by default. +This behaviour may be overwritten by setting the allow_nil_kid option for decode to true.

+ +
jwks_loader = ->(options) do
+  # The jwk loader would fetch the set of JWKs from a trusted source.
+  # 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.
+  # This example only allows cache invalidations every 5 minutes.
+  if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300
+    logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache")
+    @cached_keys = nil
+  end
+  @cached_keys ||= begin
+    @cache_last_update = Time.now.to_i
+    # Replace with your own JWKS fetching routine
+    jwks = JWT::JWK::Set.new(jwks_hash)
+    jwks.select! { |key| key[:use] == 'sig' } # Signing Keys only
+    jwks
+  end
+end
+
+begin
+  JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks_loader })
+rescue JWT::JWKError
+  # Handle problems with the provided JWKs
+rescue JWT::DecodeError
+  # Handle other decode related issues e.g. no kid in header, no matching public key found etc.
+end
+
+ +

Importing and exporting JSON Web Keys

+ +

The ::JWT::JWK class can be used to import both JSON Web Keys and OpenSSL keys +and export to either format with and without the private key included.

+ +

To include the private key in the export pass the include_private parameter to the export method.

+ +
# Import a JWK Hash (showing an HMAC example)
+jwk = JWT::JWK.new({ kty: 'oct', k: 'my-secret', kid: 'my-kid' })
+
+# Import an OpenSSL key
+# You can optionally add descriptive parameters to the JWK
+desc_params = { kid: 'my-kid', use: 'sig' }
+jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), desc_params)
+
+# Export as JWK Hash (public key only by default)
+jwk_hash = jwk.export
+jwk_hash_with_private_key = jwk.export(include_private: true)
+
+# Export as OpenSSL key
+public_key = jwk.verify_key
+private_key = jwk.signing_key if jwk.private?
+
+# You can also import and export entire JSON Web Key Sets
+jwks_hash = { keys: [{ kty: 'oct', k: 'my-secret', kid: 'my-kid' }] }
+jwks = JWT::JWK::Set.new(jwks_hash)
+jwks_hash = jwks.export
+
+ +

Key ID (kid) and JWKs

+ +

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.

+ +
JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint
+# OR
+JWT.configuration.jwk.kid_generator = ::JWT::JWK::Thumbprint
+# OR
+jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), nil, kid_generator: ::JWT::JWK::Thumbprint)
+
+jwk_hash = jwk.export
+
+thumbprint_as_the_kid = jwk_hash[:kid]
+
+ +

Development and testing

+ +

The tests are written with rspec. Appraisal is used to ensure compatibility with 3rd party dependencies providing cryptographic features.

+ +
bundle install
+bundle exec appraisal rake test
+
+ +

Releasing

+ +

To cut a new release adjust the version.rb and CHANGELOG with desired version numbers and dates and commit the changes. Tag the release with the version number using the following command:

+ +
rake release:source_control_push
+
+ +

This will tag a new version an trigger a GitHub action that eventually will push the gem to rubygems.org.

+ +

How to contribute

+ +

See CONTRIBUTING.

+ +

Contributors

+ +

See AUTHORS.

+ +

License

+ +

See LICENSE.

+ + + + + + + \ No newline at end of file diff --git a/file.UPGRADING.html b/file.UPGRADING.html new file mode 100644 index 000000000..f9f00dd32 --- /dev/null +++ b/file.UPGRADING.html @@ -0,0 +1,121 @@ + + + + + + + File: UPGRADING + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

Upgrading ruby-jwt to >= 3.0.0

+ +

Removal of the indirect RbNaCl dependency

+ +

Historically, the set of supported algorithms was extended by including the rbnacl gem in the application's Gemfile. On load, ruby-jwt tried to load the gem and, if available, extend the algorithms to those provided by the rbnacl/libsodium libraries. This indirect dependency has caused some maintenance pain and confusion about which versions of the gem are supported.

+ +

Some work to ease the way alternative algorithms can be implemented has been done. This enables the extraction of the algorithm provided by rbnacl.

+ +

The extracted algorithms now live in the jwt-eddsa gem.

+ +

Dropped support for HS512256

+ +

The algorithm HS512256 (HMAC-SHA-512 truncated to 256-bits) is not part of any JWA/JWT RFC and therefore will not be supported anymore. It was part of the HMAC algorithms provided by the indirect RbNaCl dependency. Currently, there are no direct substitutes for the algorithm.

+ +

JWT::EncodedToken#payload will raise before token is verified

+ +

To avoid accidental use of unverified tokens, the JWT::EncodedToken#payload method will raise an error if accessed before the token signature has been verified.

+ +

To access the payload before verification, use the method JWT::EncodedToken#unverified_payload.

+ +

Stricter requirements on Base64 encoded data

+ +

Base64 decoding will no longer fallback on the looser RFC 2045. The biggest difference is that the looser version was ignoring whitespaces and newlines, whereas the stricter version raises errors in such cases.

+ +

If you, for example, read tokens from files, there could be problems with trailing newlines. Make sure you trim your input before passing it to the decoding mechanisms.

+ +

Claim verification revamp

+ +

Claim verification has been split into separate classes and has a new API, leading to the following deprecations:

+ +
    +
  • The ::JWT::ClaimsValidator class will be removed in favor of the functionality provided by ::JWT::Claims.
  • +
  • The ::JWT::Claims::verify! method will be removed in favor of ::JWT::Claims::verify_payload!.
  • +
  • The ::JWT::JWA.create method will be removed.
  • +
  • The ::JWT::Verify class will be removed in favor of the functionality provided by ::JWT::Claims.
  • +
  • Calling ::JWT::Claims::Numeric.new with a payload will be removed in favor of ::JWT::Claims::verify_payload!(payload, :numeric).
  • +
  • Calling ::JWT::Claims::Numeric.verify! with a payload will be removed in favor of ::JWT::Claims::verify_payload!(payload, :numeric).
  • +
+ +

Algorithm restructuring

+ +

The internal algorithms were restructured to support extensions from separate libraries. The changes led to a few deprecations and new requirements:

+ +
    +
  • The sign and verify static methods on all the algorithms (::JWT::JWA) will be removed.
  • +
  • Custom algorithms are expected to include the JWT::JWA::SigningAlgorithm module.
  • +
+ +

Base64 the `k´ value for HMAC JWKs

+ +

The gem was missing the Base64 encoding and decoding when representing and parsing a HMAC key as a JWK. This issue is now addressed. The added encoding will break compatibility with JWKs produced by older versions of the gem.

+
+ + + +
+ + \ No newline at end of file diff --git a/file_list.html b/file_list.html new file mode 100644 index 000000000..b4d9dabb4 --- /dev/null +++ b/file_list.html @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + File List + + + +
+
+

File List

+ + + +
+ + +
+ + diff --git a/frames.html b/frames.html new file mode 100644 index 000000000..6586005fd --- /dev/null +++ b/frames.html @@ -0,0 +1,22 @@ + + + + + Documentation by YARD 0.9.37 + + + + diff --git a/gemfiles/openssl.gemfile b/gemfiles/openssl.gemfile deleted file mode 100644 index 3b45c62c5..000000000 --- a/gemfiles/openssl.gemfile +++ /dev/null @@ -1,7 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "openssl", "< 2.0" - -gemspec path: "../" diff --git a/gemfiles/standalone.gemfile b/gemfiles/standalone.gemfile deleted file mode 100644 index 095e66085..000000000 --- a/gemfiles/standalone.gemfile +++ /dev/null @@ -1,5 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gemspec path: "../" diff --git a/index.html b/index.html new file mode 100644 index 000000000..84bcd89b0 --- /dev/null +++ b/index.html @@ -0,0 +1,845 @@ + + + + + + + File: README + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
+ + +

JWT

+ +

Gem Version +Build Status +Maintainability +Code Coverage

+ +

A ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.

+ +

If you have further questions related to development or usage, join us: ruby-jwt google group.

+ +

See CHANGELOG.md for a complete set of changes and upgrade guide for upgrading between major versions.

+ +

Sponsors

+ + + + + + + + + + + +
LogoMessage
auth0 logoIf you want to quickly add secure token-based authentication to Ruby projects, feel free to check Auth0's Ruby SDK and free plan at auth0.com/developers
+ +

Installing

+ +

Using Rubygems

+ +
gem install jwt
+
+ +

Using Bundler

+ +

Add the following to your Gemfile

+ +
gem 'jwt'
+
+ +

And run bundle install

+ +

Finally require the gem in your application

+ +
require 'jwt'
+
+ +

Algorithms and Usage

+ +

The jwt gem natively supports the NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms via the openssl library. The gem can be extended with additional or alternative implementations of the algorithms via extensions.

+ +

Additionally the EdDSA algorithm is supported via a the jwt-eddsa gem.

+ +

For safe 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. It is strongly recommended that you hard code the algorithm, as you may leave yourself vulnerable by dynamically picking the algorithm

+ +

See JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS

+ +

NONE

+ +
    +
  • none - unsigned token
  • +
+ +
payload = { data: 'test' }
+token   = JWT.encode(payload, nil, 'none')
+# => "eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
+
+decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
+#  => [
+#       {"data"=>"test"}, # payload
+#       {"alg"=>"none"} # header
+#     ]
+
+ +

HMAC

+ +
    +
  • HS256 - HMAC using SHA-256 hash algorithm
  • +
  • HS384 - HMAC using SHA-384 hash algorithm
  • +
  • HS512 - HMAC using SHA-512 hash algorithm
  • +
+ +
payload     = { data: 'test' }
+hmac_secret = 'my$ecretK3y'
+
+token = JWT.encode(payload, hmac_secret, 'HS256')
+# => "eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y"
+
+decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
+# => [
+#      {"data"=>"test"}, # payload
+#      {"alg"=>"HS256"} # header
+#    ]
+
+ +

RSA

+ +
    +
  • RS256 - RSA using SHA-256 hash algorithm
  • +
  • RS384 - RSA using SHA-384 hash algorithm
  • +
  • RS512 - RSA using SHA-512 hash algorithm
  • +
+ +
payload     = { data: 'test' }
+rsa_private = OpenSSL::PKey::RSA.generate(2048)
+rsa_public  = rsa_private.public_key
+
+token = JWT.encode(payload, rsa_private, 'RS256')
+# => "eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.CCkO35qFPijW8Gwhbt8a80PB9fc9FJ19hCMnXSgoDF6Mlvlt0A4G-ah..."
+
+decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })
+# => [
+#      {"data"=>"test"}, # payload
+#      {"alg"=>"RS256"} # header
+#    ]
+
+ +

ECDSA

+ +
    +
  • ES256 - ECDSA using P-256 and SHA-256
  • +
  • ES384 - ECDSA using P-384 and SHA-384
  • +
  • ES512 - ECDSA using P-521 and SHA-512
  • +
  • ES256K - ECDSA using P-256K and SHA-256
  • +
+ +
payload   = { data: 'test' }
+ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
+
+token = JWT.encode(payload, ecdsa_key, 'ES256')
+# => "eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg"
+
+decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' })
+# => [
+#      {"test"=>"data"}, # payload
+#      {"alg"=>"ES256"} # header
+#    ]
+
+ +

EdDSA

+ +

Since version 3.0, the EdDSA algorithm has been moved to the jwt-eddsa gem.

+ +

RSASSA-PSS

+ +
    +
  • PS256 - RSASSA-PSS using SHA-256 hash algorithm
  • +
  • PS384 - RSASSA-PSS using SHA-384 hash algorithm
  • +
  • PS512 - RSASSA-PSS using SHA-512 hash algorithm
  • +
+ +
payload     = { data: 'test' }
+rsa_private = OpenSSL::PKey::RSA.generate(2048)
+rsa_public  = rsa_private.public_key
+
+token = JWT.encode(payload, rsa_private, 'PS256')
+# => "eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.BRWizdUjD5zAWw-EDBcrl3dDpQDAePz9Ol3XKC43SggU47G8OWwveA_..."
+
+decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' })
+# => [
+#      {"data"=>"test"}, # payload
+#      {"alg"=>"PS256"} # header
+#    ]
+
+ +

Custom algorithms

+ +

When encoding or decoding a token, you can pass in a custom object through the algorithm option to handle signing or verification. This custom object must include or extend the JWT::JWA::SigningAlgorithm module and implement certain methods:

+ +
    +
  • For decoding/verifying: The object must implement the methods alg and verify.
  • +
  • For encoding/signing: The object must implement the methods alg and sign.
  • +
+ +

For customization options check the details from JWT::JWA::SigningAlgorithm.

+ +
module CustomHS512Algorithm
+  extend JWT::JWA::SigningAlgorithm
+
+  def self.alg
+    'HS512'
+  end
+
+  def self.sign(data:, signing_key:)
+    OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), signing_key, data)
+  end
+
+  def self.verify(data:, signature:, verification_key:)
+    ::OpenSSL.secure_compare(sign(data: data, signing_key: verification_key), signature)
+  end
+end
+
+payload  = { data: 'test' }
+token    = JWT.encode(payload, 'secret', CustomHS512Algorithm)
+# => "eyJhbGciOiJIUzUxMiJ9.eyJkYXRhIjoidGVzdCJ9.aBNoejLEM2WMF3TxzRDKlehYdG2ATvFpGNauTI4GSD2VJseS_sC8covrVMlgslf0aJM4SKb3EIeORJBFPtZ33w"
+
+decoded_token = JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
+# => [
+#      {"data"=>"test"}, # payload
+#      {"alg"=>"HS512"} # header
+#    ]
+
+ +

Add custom header fields

+ +

The ruby-jwt gem supports custom header fields +To add custom header fields you need to pass header_fields parameter

+ +
payload = { data: 'test' }
+
+token = JWT.encode(payload, nil, 'none', { typ: 'JWT' })
+# => "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
+
+decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
+#  => [
+#       {"data"=>"test"}, # payload
+#       {"typ"=>"JWT", "alg"=>"none"} # header
+#     ]
+
+ +

JWT::Token and JWT::EncodedToken

+ +

The JWT::Token and JWT::EncodedToken classes can be used to manage your JWTs.

+ +

Signing and encoding a token

+ +
payload = { exp: Time.now.to_i + 60, jti: '1234', sub: "my-subject" }
+header =  { kid: 'hmac' }
+
+token = JWT::Token.new(payload: payload, header: header)
+token.sign!(algorithm: 'HS256', key: "secret")
+
+token.jwt
+# => "eyJraWQiOiJobWFjIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjE3NTAwMDU0NzksImp0aSI6IjEyMzQiLCJzdWIiOiJteS1zdWJqZWN0In0.NRLcK6fYr3IdNfmncJePMWLQ34M4n14EgqSYrQIjL9w"
+
+ +

Verifying and decoding a token

+ +

The JWT::EncodedToken can be used as a token object that allows verification of signatures and claims.

+ +
encoded_token = JWT::EncodedToken.new(token.jwt)
+
+encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
+encoded_token.verify_signature!(algorithm: 'HS256', key: "wrong_secret") # raises JWT::VerificationError
+encoded_token.verify_claims!(:exp, :jti)
+encoded_token.verify_claims!(sub: ["not-my-subject"]) # raises JWT::InvalidSubError
+encoded_token.claim_errors(sub: ["not-my-subject"]).map(&:message) # => ["Invalid subject. Expected [\"not-my-subject\"], received my-subject"]
+encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
+encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
+
+ +

The JWT::EncodedToken#verify! method can be used to verify signature and claim verification in one go. The exp claim is verified by default.

+ +
encoded_token = JWT::EncodedToken.new(token.jwt)
+encoded_token.verify!(signature: {algorithm: 'HS256', key: "secret"})
+encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
+encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
+
+ +

A JWK can be used to sign and verify the token if it's possible to derive the signing algorithm from the key.

+ +
jwk_json = '{
+ "kty": "oct",
+ "k": "c2VjcmV0",
+ "alg": "HS256",
+ "kid": "hmac"
+}'
+
+jwk = JWT::JWK.import(JSON.parse(jwk_json))
+
+token = JWT::Token.new(payload: payload, header: header)
+
+token.sign!(key: jwk, algorithm: 'HS256')
+
+encoded_token = JWT::EncodedToken.new(token.jwt)
+encoded_token.verify!(signature: { algorithm: ["HS256", "HS512"], key: jwk})
+
+ +

Using a keyfinder

+ +

A keyfinder can be used to verify a signature. A keyfinder is an object responding to the #call method. The method expects to receive one argument, which is the token to be verified.

+ +

An example on using the built-in JWK keyfinder.

+ +
# Create and sign a token
+jwk = JWT::JWK.new(OpenSSL::PKey::RSA.generate(2048))
+token = JWT::Token.new(payload: { pay: 'load' }, header: { kid: jwk.kid })
+token.sign!(algorithm: 'RS256', key: jwk.signing_key)
+
+# Create keyfinder object, verify and decode token
+key_finder = JWT::JWK::KeyFinder.new(jwks: JWT::JWK::Set.new(jwk))
+encoded_token = JWT::EncodedToken.new(token.jwt)
+encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: key_finder})
+encoded_token.payload # => { 'pay' => 'load' }
+
+ +

Using a custom keyfinder proc.

+ +
# Create and sign a token
+key = OpenSSL::PKey::RSA.generate(2048)
+token = JWT::Token.new(payload: { pay: 'load' })
+token.sign!(algorithm: 'RS256', key: key)
+
+# Verify and decode token
+encoded_token = JWT::EncodedToken.new(token.jwt)
+encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: ->(_token){ key.public_key }})
+encoded_token.payload # => { 'pay' => 'load' }
+
+ +

Detached payload

+ +

The ::JWT::Token#detach_payload! method can be use to detach the payload from the JWT.

+ +
token = JWT::Token.new(payload: { pay: 'load' })
+token.sign!(algorithm: 'HS256', key: "secret")
+token.detach_payload!
+token.jwt # => "eyJhbGciOiJIUzI1NiJ9..UEhDY1Qlj29ammxuVRA_-gBah4qTy5FngIWg0yEAlC0"
+token.encoded_payload # => "eyJwYXkiOiJsb2FkIn0"
+
+ +

The JWT::EncodedToken class can be used to decode a token with a detached payload by providing the payload to the token instance in separate.

+ +
encoded_token = JWT::EncodedToken.new(token.jwt)
+encoded_token.encoded_payload = "eyJwYXkiOiJsb2FkIn0"
+encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
+encoded_token.payload # => {"pay"=>"load"}
+
+ +

Claims

+ +

JSON Web Token defines some reserved claim names and defines how they should be +used. JWT supports these reserved claim names:

+ +
    +
  • 'exp' (Expiration Time) Claim
  • +
  • 'nbf' (Not Before Time) Claim
  • +
  • 'iss' (Issuer) Claim
  • +
  • 'aud' (Audience) Claim
  • +
  • 'jti' (JWT ID) Claim
  • +
  • 'iat' (Issued At) Claim
  • +
  • 'sub' (Subject) Claim
  • +
+ +

Expiration Time Claim

+ +

From Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim:

+ +
+

The exp (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the exp claim requires that the current date/time MUST be before the expiration date/time listed in the exp claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

+
+ +
exp = Time.now.to_i + 4 * 3600
+exp_payload = { data: 'data', exp: exp }
+
+token = JWT.encode(exp_payload, hmac_secret, 'HS256')
+
+begin
+  decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
+rescue JWT::ExpiredSignature
+  # Handle expired token, e.g. logout user or deny access
+end
+
+ +

The Expiration Claim verification can be disabled.

+ +
# Decode token without raising JWT::ExpiredSignature error
+JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' })
+
+ +

Leeway and the exp claim.

+ +
exp = Time.now.to_i - 10
+leeway = 30 # seconds
+
+exp_payload = { data: 'data', exp: exp }
+
+# build expired token
+token = JWT.encode(exp_payload, hmac_secret, 'HS256')
+
+begin
+  # add leeway to ensure the token is still accepted
+  decoded_token = JWT.decode(token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' })
+rescue JWT::ExpiredSignature
+  # Handle expired token, e.g. logout user or deny access
+end
+
+ +

Not Before Time Claim

+ +

From Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim:

+ +
+

The nbf (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the nbf claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the nbf claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

+
+ +
nbf = Time.now.to_i - 3600
+nbf_payload = { data: 'data', nbf: nbf }
+
+token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
+
+begin
+  decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
+rescue JWT::ImmatureSignature
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

The Not Before Claim verification can be disabled.

+ +
# Decode token without raising JWT::ImmatureSignature error
+JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' })
+
+ +

Leeway and the nbf claim.

+ +
nbf = Time.now.to_i + 10
+leeway = 30
+
+nbf_payload = { data: 'data', nbf: nbf }
+
+# build expired token
+token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
+
+begin
+  # add leeway to ensure the token is valid
+  decoded_token = JWT.decode(token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' })
+rescue JWT::ImmatureSignature
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

Issuer Claim

+ +

From Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim:

+ +
+

The iss (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The iss value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.

+
+ +

You can pass multiple allowed issuers as an Array, verification will pass if one of them matches the iss value in the payload.

+ +
iss = 'My Awesome Company Inc. or https://my.awesome.website/'
+iss_payload = { data: 'data', iss: iss }
+
+token = JWT.encode(iss_payload, hmac_secret, 'HS256')
+
+begin
+  # Add iss to the validation to check if the token has been manipulated
+  decoded_token = JWT.decode(token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' })
+rescue JWT::InvalidIssuerError
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

You can also pass a Regexp or Proc (with arity 1), verification will pass if the regexp matches or the proc returns truthy. +On supported ruby versions (>= 2.5) you can also delegate to methods, on older versions you will have +to convert them to proc (using to_proc)

+ +
JWT.decode(token, hmac_secret, true,
+           iss: %r'https://my.awesome.website/',
+           verify_iss: true,
+           algorithm: 'HS256')
+
+ +
JWT.decode(token, hmac_secret, true,
+           iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') },
+           verify_iss: true,
+           algorithm: 'HS256')
+
+ +
JWT.decode(token, hmac_secret, true,
+           iss: method(:valid_issuer?),
+           verify_iss: true,
+           algorithm: 'HS256')
+
+# somewhere in the same class:
+def valid_issuer?(issuer)
+  # custom validation
+end
+
+ +

Audience Claim

+ +

From Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim:

+ +
+

The aud (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT MUST be rejected. In the general case, the aud value is an array of case-sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the aud value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.

+
+ +
aud = ['Young', 'Old']
+aud_payload = { data: 'data', aud: aud }
+
+token = JWT.encode(aud_payload, hmac_secret, 'HS256')
+
+begin
+  # Add aud to the validation to check if the token has been manipulated
+  decoded_token = JWT.decode(token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' })
+rescue JWT::InvalidAudError
+  # Handle invalid token, e.g. logout user or deny access
+  puts 'Audience Error'
+end
+
+ +

JWT ID Claim

+ +

From Oauth JSON Web Token 4.1.7. "jti" (JWT ID) Claim:

+ +
+

The jti (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The jti claim can be used to prevent the JWT from being replayed. The jti value is a case-sensitive string. Use of this claim is OPTIONAL.

+
+ +
# Use the secret and iat to create a unique key per request to prevent replay attacks
+jti_raw = [hmac_secret, iat].join(':').to_s
+jti = Digest::MD5.hexdigest(jti_raw)
+jti_payload = { data: 'data', iat: iat, jti: jti }
+
+token = JWT.encode(jti_payload, hmac_secret, 'HS256')
+
+begin
+  # If :verify_jti is true, validation will pass if a JTI is present
+  #decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' })
+  # Alternatively, pass a proc with your own code to check if the JTI has already been used
+  decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' })
+  # or
+  decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' })
+rescue JWT::InvalidJtiError
+  # Handle invalid token, e.g. logout user or deny access
+  puts 'Error'
+end
+
+ +

Issued At Claim

+ +

From Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim:

+ +
+

The iat (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. The leeway option is not taken into account when verifying this claim. The iat_leeway option was removed in version 2.2.0. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

+
+ +
iat = Time.now.to_i
+iat_payload = { data: 'data', iat: iat }
+
+token = JWT.encode(iat_payload, hmac_secret, 'HS256')
+
+begin
+  # Add iat to the validation to check if the token has been manipulated
+  decoded_token = JWT.decode(token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' })
+rescue JWT::InvalidIatError
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

Subject Claim

+ +

From Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim:

+ +
+

The sub (subject) claim identifies the principal that is the subject of the JWT. The Claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The sub value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.

+
+ +
sub = 'Subject'
+sub_payload = { data: 'data', sub: sub }
+
+token = JWT.encode(sub_payload, hmac_secret, 'HS256')
+
+begin
+  # Add sub to the validation to check if the token has been manipulated
+  decoded_token = JWT.decode(token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' })
+rescue JWT::InvalidSubError
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

Standalone claim verification

+ +

The JWT claim verifications can be used to verify any Hash to include expected keys and values.

+ +

A few example on verifying the claims for a payload:

+ +
JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :numeric, :exp)
+JWT::Claims.valid_payload?({"exp" => Time.now.to_i + 10}, :exp)
+# => true
+JWT::Claims.payload_errors({"exp" => Time.now.to_i - 10}, :exp)
+# => [#<struct JWT::Claims::Error message="Signature has expired">]
+JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11})
+
+JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10, "sub" => "subject"}, :exp, sub: "subject")
+
+ +

Finding a Key

+ +

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.

+ +
issuers = %w[My_Awesome_Company1 My_Awesome_Company2]
+iss_payload = { data: 'data', iss: issuers.first }
+
+secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' }
+
+token = JWT.encode(iss_payload, hmac_secret, 'HS256')
+
+begin
+  # Add iss to the validation to check if the token has been manipulated
+  decoded_token = JWT.decode(token, nil, true, { iss: issuers, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload|
+    secrets[payload['iss']]
+  end
+rescue JWT::InvalidIssuerError
+  # Handle invalid token, e.g. logout user or deny access
+end
+
+ +

Required Claims

+ +

You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing

+ +
# Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent
+JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' })
+
+ +

X.509 certificates in x5c header

+ +

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).

+ +
root_certificates = [] # trusted `OpenSSL::X509::Certificate` objects
+crl_uris = root_certificates.map(&:crl_uris)
+crls = crl_uris.map do |uri|
+  # look up cached CRL by `uri` and return it if found, otherwise continue
+  crl = Net::HTTP.get(uri)
+  crl = OpenSSL::X509::CRL.new(crl)
+  # cache `crl` using `uri` as the key, expiry set to `crl.next_update` timestamp
+end
+
+begin
+  JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls } })
+rescue JWT::DecodeError
+  # Handle error, e.g. x5c header certificate revoked or expired
+end
+
+ +

JSON Web Key (JWK)

+ +

JWK is a JSON structure representing a cryptographic key. This gem currently supports RSA, EC, OKP and HMAC keys. OKP support requires RbNaCl and currently only supports the Ed25519 curve.

+ +

To encode a JWT using your JWK:

+ +
optional_parameters = { kid: 'my-kid', use: 'sig', alg: 'RS512' }
+jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters)
+
+# Encoding
+payload = { data: 'data' }
+token = JWT.encode(payload, jwk.signing_key, jwk[:alg], kid: jwk[:kid])
+
+# JSON Web Key Set for advertising your signing keys
+jwks_hash = JWT::JWK::Set.new(jwk).export
+
+ +

To decode a JWT using a trusted entity's JSON Web Key Set (JWKS):

+ +
jwks = JWT::JWK::Set.new(jwks_hash)
+jwks.filter! {|key| key[:use] == 'sig' } # Signing keys only!
+algorithms = jwks.map { |key| key[:alg] }.compact.uniq
+JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
+
+ +

The jwks option can also be given as a lambda that evaluates every time a key identifier is resolved. +This can be used to implement caching of remotely fetched JWK Sets.

+ +

Key identifiers can be specified using kid, x5t header parameters. +If the requested identifier 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.

+ +

Tokens without a specified key identifier (kid or x5t) are rejected by default. +This behaviour may be overwritten by setting the allow_nil_kid option for decode to true.

+ +
jwks_loader = ->(options) do
+  # The jwk loader would fetch the set of JWKs from a trusted source.
+  # 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.
+  # This example only allows cache invalidations every 5 minutes.
+  if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300
+    logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache")
+    @cached_keys = nil
+  end
+  @cached_keys ||= begin
+    @cache_last_update = Time.now.to_i
+    # Replace with your own JWKS fetching routine
+    jwks = JWT::JWK::Set.new(jwks_hash)
+    jwks.select! { |key| key[:use] == 'sig' } # Signing Keys only
+    jwks
+  end
+end
+
+begin
+  JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks_loader })
+rescue JWT::JWKError
+  # Handle problems with the provided JWKs
+rescue JWT::DecodeError
+  # Handle other decode related issues e.g. no kid in header, no matching public key found etc.
+end
+
+ +

Importing and exporting JSON Web Keys

+ +

The ::JWT::JWK class can be used to import both JSON Web Keys and OpenSSL keys +and export to either format with and without the private key included.

+ +

To include the private key in the export pass the include_private parameter to the export method.

+ +
# Import a JWK Hash (showing an HMAC example)
+jwk = JWT::JWK.new({ kty: 'oct', k: 'my-secret', kid: 'my-kid' })
+
+# Import an OpenSSL key
+# You can optionally add descriptive parameters to the JWK
+desc_params = { kid: 'my-kid', use: 'sig' }
+jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), desc_params)
+
+# Export as JWK Hash (public key only by default)
+jwk_hash = jwk.export
+jwk_hash_with_private_key = jwk.export(include_private: true)
+
+# Export as OpenSSL key
+public_key = jwk.verify_key
+private_key = jwk.signing_key if jwk.private?
+
+# You can also import and export entire JSON Web Key Sets
+jwks_hash = { keys: [{ kty: 'oct', k: 'my-secret', kid: 'my-kid' }] }
+jwks = JWT::JWK::Set.new(jwks_hash)
+jwks_hash = jwks.export
+
+ +

Key ID (kid) and JWKs

+ +

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.

+ +
JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint
+# OR
+JWT.configuration.jwk.kid_generator = ::JWT::JWK::Thumbprint
+# OR
+jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), nil, kid_generator: ::JWT::JWK::Thumbprint)
+
+jwk_hash = jwk.export
+
+thumbprint_as_the_kid = jwk_hash[:kid]
+
+ +

Development and testing

+ +

The tests are written with rspec. Appraisal is used to ensure compatibility with 3rd party dependencies providing cryptographic features.

+ +
bundle install
+bundle exec appraisal rake test
+
+ +

Releasing

+ +

To cut a new release adjust the version.rb and CHANGELOG with desired version numbers and dates and commit the changes. Tag the release with the version number using the following command:

+ +
rake release:source_control_push
+
+ +

This will tag a new version an trigger a GitHub action that eventually will push the gem to rubygems.org.

+ +

How to contribute

+ +

See CONTRIBUTING.

+ +

Contributors

+ +

See AUTHORS.

+ +

License

+ +

See LICENSE.

+
+ + + +
+ + \ No newline at end of file diff --git a/js/app.js b/js/app.js new file mode 100644 index 000000000..b5610effe --- /dev/null +++ b/js/app.js @@ -0,0 +1,344 @@ +(function () { + var localStorage = {}, + sessionStorage = {}; + try { + localStorage = window.localStorage; + } catch (e) {} + try { + sessionStorage = window.sessionStorage; + } catch (e) {} + + function createSourceLinks() { + $(".method_details_list .source_code").before( + "[View source]" + ); + $(".toggleSource").toggle( + function () { + $(this).parent().nextAll(".source_code").slideDown(100); + $(this).text("Hide source"); + }, + function () { + $(this).parent().nextAll(".source_code").slideUp(100); + $(this).text("View source"); + } + ); + } + + function createDefineLinks() { + var tHeight = 0; + $(".defines").after(" more..."); + $(".toggleDefines").toggle( + function () { + tHeight = $(this).parent().prev().height(); + $(this).prev().css("display", "inline"); + $(this).parent().prev().height($(this).parent().height()); + $(this).text("(less)"); + }, + function () { + $(this).prev().hide(); + $(this).parent().prev().height(tHeight); + $(this).text("more..."); + } + ); + } + + function createFullTreeLinks() { + var tHeight = 0; + $(".inheritanceTree").toggle( + function () { + tHeight = $(this).parent().prev().height(); + $(this).parent().toggleClass("showAll"); + $(this).text("(hide)"); + $(this).parent().prev().height($(this).parent().height()); + }, + function () { + $(this).parent().toggleClass("showAll"); + $(this).parent().prev().height(tHeight); + $(this).text("show all"); + } + ); + } + + function searchFrameButtons() { + $(".full_list_link").click(function () { + toggleSearchFrame(this, $(this).attr("href")); + return false; + }); + window.addEventListener("message", function (e) { + if (e.data === "navEscape") { + $("#nav").slideUp(100); + $("#search a").removeClass("active inactive"); + $(window).focus(); + } + }); + + $(window).resize(function () { + if ($("#search:visible").length === 0) { + $("#nav").removeAttr("style"); + $("#search a").removeClass("active inactive"); + $(window).focus(); + } + }); + } + + function toggleSearchFrame(id, link) { + var frame = $("#nav"); + $("#search a").removeClass("active").addClass("inactive"); + if (frame.attr("src") === link && frame.css("display") !== "none") { + frame.slideUp(100); + $("#search a").removeClass("active inactive"); + } else { + $(id).addClass("active").removeClass("inactive"); + if (frame.attr("src") !== link) frame.attr("src", link); + frame.slideDown(100); + } + } + + function linkSummaries() { + $(".summary_signature").click(function () { + document.location = $(this).find("a").attr("href"); + }); + } + + function summaryToggle() { + $(".summary_toggle").click(function (e) { + e.preventDefault(); + localStorage.summaryCollapsed = $(this).text(); + $(".summary_toggle").each(function () { + $(this).text($(this).text() == "collapse" ? "expand" : "collapse"); + var next = $(this).parent().parent().nextAll("ul.summary").first(); + if (next.hasClass("compact")) { + next.toggle(); + next.nextAll("ul.summary").first().toggle(); + } else if (next.hasClass("summary")) { + var list = $('
    '); + list.html(next.html()); + list.find(".summary_desc, .note").remove(); + list.find("a").each(function () { + $(this).html($(this).find("strong").html()); + $(this).parent().html($(this)[0].outerHTML); + }); + next.before(list); + next.toggle(); + } + }); + return false; + }); + if (localStorage.summaryCollapsed == "collapse") { + $(".summary_toggle").first().click(); + } else { + localStorage.summaryCollapsed = "expand"; + } + } + + function constantSummaryToggle() { + $(".constants_summary_toggle").click(function (e) { + e.preventDefault(); + localStorage.summaryCollapsed = $(this).text(); + $(".constants_summary_toggle").each(function () { + $(this).text($(this).text() == "collapse" ? "expand" : "collapse"); + var next = $(this).parent().parent().nextAll("dl.constants").first(); + if (next.hasClass("compact")) { + next.toggle(); + next.nextAll("dl.constants").first().toggle(); + } else if (next.hasClass("constants")) { + var list = $('
    '); + list.html(next.html()); + list.find("dt").each(function () { + $(this).addClass("summary_signature"); + $(this).text($(this).text().split("=")[0]); + if ($(this).has(".deprecated").length) { + $(this).addClass("deprecated"); + } + }); + // Add the value of the constant as "Tooltip" to the summary object + list.find("pre.code").each(function () { + console.log($(this).parent()); + var dt_element = $(this).parent().prev(); + var tooltip = $(this).text(); + if (dt_element.hasClass("deprecated")) { + tooltip = "Deprecated. " + tooltip; + } + dt_element.attr("title", tooltip); + }); + list.find(".docstring, .tags, dd").remove(); + next.before(list); + next.toggle(); + } + }); + return false; + }); + if (localStorage.summaryCollapsed == "collapse") { + $(".constants_summary_toggle").first().click(); + } else { + localStorage.summaryCollapsed = "expand"; + } + } + + function generateTOC() { + if ($("#filecontents").length === 0) return; + var _toc = $('
      '); + var show = false; + var toc = _toc; + var counter = 0; + var tags = ["h2", "h3", "h4", "h5", "h6"]; + var i; + var curli; + if ($("#filecontents h1").length > 1) tags.unshift("h1"); + for (i = 0; i < tags.length; i++) { + tags[i] = "#filecontents " + tags[i]; + } + var lastTag = parseInt(tags[0][1], 10); + $(tags.join(", ")).each(function () { + if ($(this).parents(".method_details .docstring").length != 0) return; + if (this.id == "filecontents") return; + show = true; + var thisTag = parseInt(this.tagName[1], 10); + if (this.id.length === 0) { + var proposedId = $(this).attr("toc-id"); + if (typeof proposedId != "undefined") this.id = proposedId; + else { + var proposedId = $(this) + .text() + .replace(/[^a-z0-9-]/gi, "_"); + if ($("#" + proposedId).length > 0) { + proposedId += counter; + counter++; + } + this.id = proposedId; + } + } + if (thisTag > lastTag) { + for (i = 0; i < thisTag - lastTag; i++) { + if (typeof curli == "undefined") { + curli = $("
    1. "); + toc.append(curli); + } + toc = $("
        "); + curli.append(toc); + curli = undefined; + } + } + if (thisTag < lastTag) { + for (i = 0; i < lastTag - thisTag; i++) { + toc = toc.parent(); + toc = toc.parent(); + } + } + var title = $(this).attr("toc-title"); + if (typeof title == "undefined") title = $(this).text(); + curli = $('
      1. ' + title + "
      2. "); + toc.append(curli); + lastTag = thisTag; + }); + if (!show) return; + html = + ''; + $("#content").prepend(html); + $("#toc").append(_toc); + $("#toc .hide_toc").toggle( + function () { + $("#toc .top").slideUp("fast"); + $("#toc").toggleClass("hidden"); + $("#toc .title small").toggle(); + }, + function () { + $("#toc .top").slideDown("fast"); + $("#toc").toggleClass("hidden"); + $("#toc .title small").toggle(); + } + ); + } + + function navResizeFn(e) { + if (e.which !== 1) { + navResizeFnStop(); + return; + } + + sessionStorage.navWidth = e.pageX.toString(); + $(".nav_wrap").css("width", e.pageX); + $(".nav_wrap").css("-ms-flex", "inherit"); + } + + function navResizeFnStop() { + $(window).unbind("mousemove", navResizeFn); + window.removeEventListener("message", navMessageFn, false); + } + + function navMessageFn(e) { + if (e.data.action === "mousemove") navResizeFn(e.data.event); + if (e.data.action === "mouseup") navResizeFnStop(); + } + + function navResizer() { + $("#resizer").mousedown(function (e) { + e.preventDefault(); + $(window).mousemove(navResizeFn); + window.addEventListener("message", navMessageFn, false); + }); + $(window).mouseup(navResizeFnStop); + + if (sessionStorage.navWidth) { + navResizeFn({ which: 1, pageX: parseInt(sessionStorage.navWidth, 10) }); + } + } + + function navExpander() { + if (typeof pathId === "undefined") return; + var done = false, + timer = setTimeout(postMessage, 500); + function postMessage() { + if (done) return; + clearTimeout(timer); + var opts = { action: "expand", path: pathId }; + document.getElementById("nav").contentWindow.postMessage(opts, "*"); + done = true; + } + + window.addEventListener( + "message", + function (event) { + if (event.data === "navReady") postMessage(); + return false; + }, + false + ); + } + + function mainFocus() { + var hash = window.location.hash; + if (hash !== "" && $(hash)[0]) { + $(hash)[0].scrollIntoView(); + } + + setTimeout(function () { + $("#main").focus(); + }, 10); + } + + function navigationChange() { + // This works around the broken anchor navigation with the YARD template. + window.onpopstate = function () { + var hash = window.location.hash; + if (hash !== "" && $(hash)[0]) { + $(hash)[0].scrollIntoView(); + } + }; + } + + $(document).ready(function () { + navResizer(); + navExpander(); + createSourceLinks(); + createDefineLinks(); + createFullTreeLinks(); + searchFrameButtons(); + linkSummaries(); + summaryToggle(); + constantSummaryToggle(); + generateTOC(); + mainFocus(); + navigationChange(); + }); +})(); diff --git a/js/full_list.js b/js/full_list.js new file mode 100644 index 000000000..12bba48d8 --- /dev/null +++ b/js/full_list.js @@ -0,0 +1,242 @@ +(function() { + +var $clicked = $(null); +var searchTimeout = null; +var searchCache = []; +var caseSensitiveMatch = false; +var ignoreKeyCodeMin = 8; +var ignoreKeyCodeMax = 46; +var commandKey = 91; + +RegExp.escape = function(text) { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); +} + +function escapeShortcut() { + $(document).keydown(function(evt) { + if (evt.which == 27) { + window.parent.postMessage('navEscape', '*'); + } + }); +} + +function navResizer() { + $(window).mousemove(function(e) { + window.parent.postMessage({ + action: 'mousemove', event: {pageX: e.pageX, which: e.which} + }, '*'); + }).mouseup(function(e) { + window.parent.postMessage({action: 'mouseup'}, '*'); + }); + window.parent.postMessage("navReady", "*"); +} + +function clearSearchTimeout() { + clearTimeout(searchTimeout); + searchTimeout = null; +} + +function enableLinks() { + // load the target page in the parent window + $('#full_list li').on('click', function(evt) { + $('#full_list li').removeClass('clicked'); + $clicked = $(this); + $clicked.addClass('clicked'); + evt.stopPropagation(); + + if (evt.target.tagName === 'A') return true; + + var elem = $clicked.find('> .item .object_link a')[0]; + var e = evt.originalEvent; + var newEvent = new MouseEvent(evt.originalEvent.type); + newEvent.initMouseEvent(e.type, e.canBubble, e.cancelable, e.view, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget); + elem.dispatchEvent(newEvent); + evt.preventDefault(); + return false; + }); +} + +function enableToggles() { + // show/hide nested classes on toggle click + $('#full_list a.toggle').on('click', function(evt) { + evt.stopPropagation(); + evt.preventDefault(); + $(this).parent().parent().toggleClass('collapsed'); + $(this).attr('aria-expanded', function (i, attr) { + return attr == 'true' ? 'false' : 'true' + }); + highlight(); + }); + + // navigation of nested classes using keyboard + $('#full_list a.toggle').on('keypress',function(evt) { + // enter key is pressed + if (evt.which == 13) { + evt.stopPropagation(); + evt.preventDefault(); + $(this).parent().parent().toggleClass('collapsed'); + $(this).attr('aria-expanded', function (i, attr) { + return attr == 'true' ? 'false' : 'true' + }); + highlight(); + } + }); +} + +function populateSearchCache() { + $('#full_list li .item').each(function() { + var $node = $(this); + var $link = $node.find('.object_link a'); + if ($link.length > 0) { + searchCache.push({ + node: $node, + link: $link, + name: $link.text(), + fullName: $link.attr('title').split(' ')[0] + }); + } + }); +} + +function enableSearch() { + $('#search input').keyup(function(event) { + if (ignoredKeyPress(event)) return; + if (this.value === "") { + clearSearch(); + } else { + performSearch(this.value); + } + }); + + $('#full_list').after(""); +} + +function ignoredKeyPress(event) { + if ( + (event.keyCode > ignoreKeyCodeMin && event.keyCode < ignoreKeyCodeMax) || + (event.keyCode == commandKey) + ) { + return true; + } else { + return false; + } +} + +function clearSearch() { + clearSearchTimeout(); + $('#full_list .found').removeClass('found').each(function() { + var $link = $(this).find('.object_link a'); + $link.text($link.text()); + }); + $('#full_list, #content').removeClass('insearch'); + $clicked.parents().removeClass('collapsed'); + highlight(); +} + +function performSearch(searchString) { + clearSearchTimeout(); + $('#full_list, #content').addClass('insearch'); + $('#noresults').text('').hide(); + partialSearch(searchString, 0); +} + +function partialSearch(searchString, offset) { + var lastRowClass = ''; + var i = null; + for (i = offset; i < Math.min(offset + 50, searchCache.length); i++) { + var item = searchCache[i]; + var searchName = (searchString.indexOf('::') != -1 ? item.fullName : item.name); + var matchString = buildMatchString(searchString); + var matchRegexp = new RegExp(matchString, caseSensitiveMatch ? "" : "i"); + if (searchName.match(matchRegexp) == null) { + item.node.removeClass('found'); + item.link.text(item.link.text()); + } + else { + item.node.addClass('found'); + item.node.removeClass(lastRowClass).addClass(lastRowClass == 'r1' ? 'r2' : 'r1'); + lastRowClass = item.node.hasClass('r1') ? 'r1' : 'r2'; + item.link.html(item.name.replace(matchRegexp, "$&")); + } + } + if(i == searchCache.length) { + searchDone(); + } else { + searchTimeout = setTimeout(function() { + partialSearch(searchString, i); + }, 0); + } +} + +function searchDone() { + searchTimeout = null; + highlight(); + var found = $('#full_list li:visible').size(); + if (found === 0) { + $('#noresults').text('No results were found.'); + } else { + // This is read out to screen readers + $('#noresults').text('There are ' + found + ' results.'); + } + $('#noresults').show(); + $('#content').removeClass('insearch'); +} + +function buildMatchString(searchString, event) { + caseSensitiveMatch = searchString.match(/[A-Z]/) != null; + var regexSearchString = RegExp.escape(searchString); + if (caseSensitiveMatch) { + regexSearchString += "|" + + $.map(searchString.split(''), function(e) { return RegExp.escape(e); }). + join('.+?'); + } + return regexSearchString; +} + +function highlight() { + $('#full_list li:visible').each(function(n) { + $(this).removeClass('even odd').addClass(n % 2 == 0 ? 'odd' : 'even'); + }); +} + +/** + * Expands the tree to the target element and its immediate + * children. + */ +function expandTo(path) { + var $target = $(document.getElementById('object_' + path)); + $target.addClass('clicked'); + $target.removeClass('collapsed'); + $target.parentsUntil('#full_list', 'li').removeClass('collapsed'); + + $target.find('a.toggle').attr('aria-expanded', 'true') + $target.parentsUntil('#full_list', 'li').each(function(i, el) { + $(el).find('> div > a.toggle').attr('aria-expanded', 'true'); + }); + + if($target[0]) { + window.scrollTo(window.scrollX, $target.offset().top - 250); + highlight(); + } +} + +function windowEvents(event) { + var msg = event.data; + if (msg.action === "expand") { + expandTo(msg.path); + } + return false; +} + +window.addEventListener("message", windowEvents, false); + +$(document).ready(function() { + escapeShortcut(); + navResizer(); + enableLinks(); + enableToggles(); + populateSearchCache(); + enableSearch(); +}); + +})(); diff --git a/js/jquery.js b/js/jquery.js new file mode 100644 index 000000000..198b3ff07 --- /dev/null +++ b/js/jquery.js @@ -0,0 +1,4 @@ +/*! jQuery v1.7.1 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"":"")+""),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;g=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
        a",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="
        "+""+"
        ",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="
        t
        ",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="
        ",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; +f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&i.push({elem:this,matches:d.slice(e)});for(j=0;j0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

        ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
        ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
        ","
        "],thead:[1,"","
        "],tr:[2,"","
        "],td:[3,"","
        "],col:[2,"","
        "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
        ","
        "]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() +{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
        ").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/lib/jwt.rb b/lib/jwt.rb deleted file mode 100644 index 86ac2e6ac..000000000 --- a/lib/jwt.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require 'jwt/version' -require 'jwt/base64' -require 'jwt/json' -require 'jwt/decode' -require 'jwt/configuration' -require 'jwt/encode' -require 'jwt/error' -require 'jwt/jwk' -require 'jwt/claims' -require 'jwt/encoded_token' -require 'jwt/token' - -# JSON Web Token implementation -# -# Should be up to date with the latest spec: -# https://tools.ietf.org/html/rfc7519 -module JWT - extend ::JWT::Configuration - - module_function - - # Encodes a payload into a JWT. - # - # @param payload [Hash] the payload to encode. - # @param key [String] the key used to sign the JWT. - # @param algorithm [String] the algorithm used to sign the JWT. - # @param header_fields [Hash] additional headers to include in the JWT. - # @return [String] the encoded JWT. - def encode(payload, key, algorithm = 'HS256', header_fields = {}) - Encode.new(payload: payload, - key: key, - algorithm: algorithm, - headers: header_fields).segments - end - - # Decodes a JWT to extract the payload and header - # - # @param jwt [String] the JWT to decode. - # @param key [String] the key used to verify the JWT. - # @param verify [Boolean] whether to verify the JWT signature. - # @param options [Hash] additional options for decoding. - # @return [Array] the decoded payload and headers. - def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter - Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments - end -end diff --git a/lib/jwt/base64.rb b/lib/jwt/base64.rb deleted file mode 100644 index fdf1bf95a..000000000 --- a/lib/jwt/base64.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require 'base64' - -module JWT - # Base64 encoding and decoding - # @api private - class Base64 - class << self - # Encode a string with URL-safe Base64 complying with RFC 4648 (not padded). - # @api private - def url_encode(str) - ::Base64.urlsafe_encode64(str, padding: false) - end - - # Decode a string with URL-safe Base64 complying with RFC 4648. - # @api private - def url_decode(str) - ::Base64.urlsafe_decode64(str) - rescue ArgumentError => e - raise unless e.message == 'invalid base64' - - raise Base64DecodeError, 'Invalid base64 encoding' - end - end - end -end diff --git a/lib/jwt/claims.rb b/lib/jwt/claims.rb deleted file mode 100644 index 45ed547b1..000000000 --- a/lib/jwt/claims.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -require_relative 'claims/audience' -require_relative 'claims/crit' -require_relative 'claims/decode_verifier' -require_relative 'claims/expiration' -require_relative 'claims/issued_at' -require_relative 'claims/issuer' -require_relative 'claims/jwt_id' -require_relative 'claims/not_before' -require_relative 'claims/numeric' -require_relative 'claims/required' -require_relative 'claims/subject' -require_relative 'claims/verifier' - -module JWT - # JWT Claim verifications - # https://datatracker.ietf.org/doc/html/rfc7519#section-4 - # - # Verification is supported for the following claims: - # exp - # nbf - # iss - # iat - # jti - # aud - # sub - # required - # numeric - module Claims - # Represents a claim verification error - Error = Struct.new(:message, keyword_init: true) - - class << self - # Checks if the claims in the JWT payload are valid. - # @example - # - # ::JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :exp) - # ::JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11}) - # - # @param payload [Hash] the JWT payload. - # @param options [Array] the options for verifying the claims. - # @return [void] - # @raise [JWT::DecodeError] if any claim is invalid. - def verify_payload!(payload, *options) - Verifier.verify!(VerificationContext.new(payload: payload), *options) - end - - # Checks if the claims in the JWT payload are valid. - # - # @param payload [Hash] the JWT payload. - # @param options [Array] the options for verifying the claims. - # @return [Boolean] true if the claims are valid, false otherwise - def valid_payload?(payload, *options) - payload_errors(payload, *options).empty? - end - - # Returns the errors in the claims of the JWT token. - # - # @param options [Array] the options for verifying the claims. - # @return [Array] the errors in the claims of the JWT - def payload_errors(payload, *options) - Verifier.errors(VerificationContext.new(payload: payload), *options) - end - end - end -end diff --git a/lib/jwt/claims/audience.rb b/lib/jwt/claims/audience.rb deleted file mode 100644 index f828fc592..000000000 --- a/lib/jwt/claims/audience.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # The Audience class is responsible for validating the audience claim ('aud') in a JWT token. - class Audience - # Initializes a new Audience instance. - # - # @param expected_audience [String, Array] the expected audience(s) for the JWT token. - def initialize(expected_audience:) - @expected_audience = expected_audience - end - - # Verifies the audience claim ('aud') in the JWT token. - # - # @param context [Object] the context containing the JWT payload. - # @param _args [Hash] additional arguments (not used). - # @raise [JWT::InvalidAudError] if the audience claim is invalid. - # @return [nil] - def verify!(context:, **_args) - aud = context.payload['aud'] - raise JWT::InvalidAudError, "Invalid audience. Expected #{expected_audience}, received #{aud || ''}" if ([*aud] & [*expected_audience]).empty? - end - - private - - attr_reader :expected_audience - end - end -end diff --git a/lib/jwt/claims/crit.rb b/lib/jwt/claims/crit.rb deleted file mode 100644 index ac339eb0b..000000000 --- a/lib/jwt/claims/crit.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # Responsible of validation the crit header - class Crit - # Initializes a new Crit instance. - # - # @param expected_crits [String] the expected crit header values for the JWT token. - def initialize(expected_crits:) - @expected_crits = Array(expected_crits) - end - - # Verifies the critical claim ('crit') in the JWT token header. - # - # @param context [Object] the context containing the JWT payload and header. - # @param _args [Hash] additional arguments (not used). - # @raise [JWT::InvalidCritError] if the crit claim is invalid. - # @return [nil] - def verify!(context:, **_args) - raise(JWT::InvalidCritError, 'Crit header missing') unless context.header['crit'] - raise(JWT::InvalidCritError, 'Crit header should be an array') unless context.header['crit'].is_a?(Array) - - missing = (expected_crits - context.header['crit']) - raise(JWT::InvalidCritError, "Crit header missing expected values: #{missing.join(', ')}") if missing.any? - - nil - end - - private - - attr_reader :expected_crits - end - end -end diff --git a/lib/jwt/claims/decode_verifier.rb b/lib/jwt/claims/decode_verifier.rb deleted file mode 100644 index 411bb97cd..000000000 --- a/lib/jwt/claims/decode_verifier.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # Context class to contain the data passed to individual claim validators - # - # @api private - VerificationContext = Struct.new(:payload, keyword_init: true) - - # Verifiers to support the ::JWT.decode method - # - # @api private - module DecodeVerifier - VERIFIERS = { - verify_expiration: ->(options) { Claims::Expiration.new(leeway: options[:exp_leeway] || options[:leeway]) }, - verify_not_before: ->(options) { Claims::NotBefore.new(leeway: options[:nbf_leeway] || options[:leeway]) }, - verify_iss: ->(options) { options[:iss] && Claims::Issuer.new(issuers: options[:iss]) }, - verify_iat: ->(*) { Claims::IssuedAt.new }, - verify_jti: ->(options) { Claims::JwtId.new(validator: options[:verify_jti]) }, - verify_aud: ->(options) { options[:aud] && Claims::Audience.new(expected_audience: options[:aud]) }, - verify_sub: ->(options) { options[:sub] && Claims::Subject.new(expected_subject: options[:sub]) }, - required_claims: ->(options) { Claims::Required.new(required_claims: options[:required_claims]) } - }.freeze - - private_constant(:VERIFIERS) - - class << self - # @api private - def verify!(payload, options) - VERIFIERS.each do |key, verifier_builder| - next unless options[key] || options[key.to_s] - - verifier_builder&.call(options)&.verify!(context: VerificationContext.new(payload: payload)) - end - nil - end - end - end - end -end diff --git a/lib/jwt/claims/expiration.rb b/lib/jwt/claims/expiration.rb deleted file mode 100644 index 0412dc4c7..000000000 --- a/lib/jwt/claims/expiration.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # The Expiration class is responsible for validating the expiration claim ('exp') in a JWT token. - class Expiration - # Initializes a new Expiration instance. - # - # @param leeway [Integer] the amount of leeway (in seconds) to allow when validating the expiration time. Default: 0. - def initialize(leeway:) - @leeway = leeway || 0 - end - - # Verifies the expiration claim ('exp') in the JWT token. - # - # @param context [Object] the context containing the JWT payload. - # @param _args [Hash] additional arguments (not used). - # @raise [JWT::ExpiredSignature] if the token has expired. - # @return [nil] - def verify!(context:, **_args) - return unless context.payload.is_a?(Hash) - return unless context.payload.key?('exp') - - raise JWT::ExpiredSignature, 'Signature has expired' if context.payload['exp'].to_i <= (Time.now.to_i - leeway) - end - - private - - attr_reader :leeway - end - end -end diff --git a/lib/jwt/claims/issued_at.rb b/lib/jwt/claims/issued_at.rb deleted file mode 100644 index 0eb08446b..000000000 --- a/lib/jwt/claims/issued_at.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # The IssuedAt class is responsible for validating the issued at claim ('iat') in a JWT token. - class IssuedAt - # Verifies the issued at claim ('iat') in the JWT token. - # - # @param context [Object] the context containing the JWT payload. - # @param _args [Hash] additional arguments (not used). - # @raise [JWT::InvalidIatError] if the issued at claim is invalid. - # @return [nil] - def verify!(context:, **_args) - return unless context.payload.is_a?(Hash) - return unless context.payload.key?('iat') - - iat = context.payload['iat'] - raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(::Numeric) || iat.to_f > Time.now.to_f - end - end - end -end diff --git a/lib/jwt/claims/issuer.rb b/lib/jwt/claims/issuer.rb deleted file mode 100644 index ca8783752..000000000 --- a/lib/jwt/claims/issuer.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # The Issuer class is responsible for validating the issuer claim ('iss') in a JWT token. - class Issuer - # Initializes a new Issuer instance. - # - # @param issuers [String, Symbol, Array] the expected issuer(s) for the JWT token. - def initialize(issuers:) - @issuers = Array(issuers).map { |item| item.is_a?(Symbol) ? item.to_s : item } - end - - # Verifies the issuer claim ('iss') in the JWT token. - # - # @param context [Object] the context containing the JWT payload. - # @param _args [Hash] additional arguments (not used). - # @raise [JWT::InvalidIssuerError] if the issuer claim is invalid. - # @return [nil] - def verify!(context:, **_args) - case (iss = context.payload['iss']) - when *issuers - nil - else - raise JWT::InvalidIssuerError, "Invalid issuer. Expected #{issuers}, received #{iss || ''}" - end - end - - private - - attr_reader :issuers - end - end -end diff --git a/lib/jwt/claims/jwt_id.rb b/lib/jwt/claims/jwt_id.rb deleted file mode 100644 index 8d17fdccb..000000000 --- a/lib/jwt/claims/jwt_id.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # The JwtId class is responsible for validating the JWT ID claim ('jti') in a JWT token. - class JwtId - # Initializes a new JwtId instance. - # - # @param validator [#call] an object responding to `call` to validate the JWT ID. - def initialize(validator:) - @validator = validator - end - - # Verifies the JWT ID claim ('jti') in the JWT token. - # - # @param context [Object] the context containing the JWT payload. - # @param _args [Hash] additional arguments (not used). - # @raise [JWT::InvalidJtiError] if the JWT ID claim is invalid or missing. - # @return [nil] - def verify!(context:, **_args) - jti = context.payload['jti'] - if validator.respond_to?(:call) - verified = validator.arity == 2 ? validator.call(jti, context.payload) : validator.call(jti) - raise(JWT::InvalidJtiError, 'Invalid jti') unless verified - elsif jti.to_s.strip.empty? - raise(JWT::InvalidJtiError, 'Missing jti') - end - end - - private - - attr_reader :validator - end - end -end diff --git a/lib/jwt/claims/not_before.rb b/lib/jwt/claims/not_before.rb deleted file mode 100644 index 879ad0319..000000000 --- a/lib/jwt/claims/not_before.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # The NotBefore class is responsible for validating the 'nbf' (Not Before) claim in a JWT token. - class NotBefore - # Initializes a new NotBefore instance. - # - # @param leeway [Integer] the amount of leeway (in seconds) to allow when validating the 'nbf' claim. Defaults to 0. - def initialize(leeway:) - @leeway = leeway || 0 - end - - # Verifies the 'nbf' (Not Before) claim in the JWT token. - # - # @param context [Object] the context containing the JWT payload. - # @param _args [Hash] additional arguments (not used). - # @raise [JWT::ImmatureSignature] if the 'nbf' claim has not been reached. - # @return [nil] - def verify!(context:, **_args) - return unless context.payload.is_a?(Hash) - return unless context.payload.key?('nbf') - - raise JWT::ImmatureSignature, 'Signature nbf has not been reached' if context.payload['nbf'].to_i > (Time.now.to_i + leeway) - end - - private - - attr_reader :leeway - end - end -end diff --git a/lib/jwt/claims/numeric.rb b/lib/jwt/claims/numeric.rb deleted file mode 100644 index 7589dc6bf..000000000 --- a/lib/jwt/claims/numeric.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # The Numeric class is responsible for validating numeric claims in a JWT token. - # The numeric claims are: exp, iat and nbf - class Numeric - # List of numeric claims that can be validated. - NUMERIC_CLAIMS = %i[ - exp - iat - nbf - ].freeze - - private_constant(:NUMERIC_CLAIMS) - - # Verifies the numeric claims in the JWT context. - # - # @param context [Object] the context containing the JWT payload. - # @raise [JWT::InvalidClaimError] if any numeric claim is invalid. - # @return [nil] - def verify!(context:) - validate_numeric_claims(context.payload) - end - - private - - def validate_numeric_claims(payload) - NUMERIC_CLAIMS.each do |claim| - validate_is_numeric(payload, claim) - end - end - - def validate_is_numeric(payload, claim) - return unless payload.is_a?(Hash) - return unless payload.key?(claim) || - payload.key?(claim.to_s) - - return if payload[claim].is_a?(::Numeric) || payload[claim.to_s].is_a?(::Numeric) - - raise InvalidPayload, "#{claim} claim must be a Numeric value but it is a #{(payload[claim] || payload[claim.to_s]).class}" - end - end - end -end diff --git a/lib/jwt/claims/required.rb b/lib/jwt/claims/required.rb deleted file mode 100644 index e0f0e1d77..000000000 --- a/lib/jwt/claims/required.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # The Required class is responsible for validating that all required claims are present in a JWT token. - class Required - # Initializes a new Required instance. - # - # @param required_claims [Array] the list of required claims. - def initialize(required_claims:) - @required_claims = required_claims - end - - # Verifies that all required claims are present in the JWT payload. - # - # @param context [Object] the context containing the JWT payload. - # @param _args [Hash] additional arguments (not used). - # @raise [JWT::MissingRequiredClaim] if any required claim is missing. - # @return [nil] - def verify!(context:, **_args) - required_claims.each do |required_claim| - next if context.payload.is_a?(Hash) && context.payload.key?(required_claim) - - raise JWT::MissingRequiredClaim, "Missing required claim #{required_claim}" - end - end - - private - - attr_reader :required_claims - end - end -end diff --git a/lib/jwt/claims/subject.rb b/lib/jwt/claims/subject.rb deleted file mode 100644 index 18b26eebf..000000000 --- a/lib/jwt/claims/subject.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # The Subject class is responsible for validating the subject claim ('sub') in a JWT token. - class Subject - # Initializes a new Subject instance. - # - # @param expected_subject [String] the expected subject for the JWT token. - def initialize(expected_subject:) - @expected_subject = expected_subject.to_s - end - - # Verifies the subject claim ('sub') in the JWT token. - # - # @param context [Object] the context containing the JWT payload. - # @param _args [Hash] additional arguments (not used). - # @raise [JWT::InvalidSubError] if the subject claim is invalid. - # @return [nil] - def verify!(context:, **_args) - sub = context.payload['sub'] - raise(JWT::InvalidSubError, "Invalid subject. Expected #{expected_subject}, received #{sub || ''}") unless sub.to_s == expected_subject - end - - private - - attr_reader :expected_subject - end - end -end diff --git a/lib/jwt/claims/verifier.rb b/lib/jwt/claims/verifier.rb deleted file mode 100644 index 81ce8a23d..000000000 --- a/lib/jwt/claims/verifier.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Claims - # @api private - module Verifier - VERIFIERS = { - exp: ->(options) { Claims::Expiration.new(leeway: options.dig(:exp, :leeway)) }, - nbf: ->(options) { Claims::NotBefore.new(leeway: options.dig(:nbf, :leeway)) }, - iss: ->(options) { Claims::Issuer.new(issuers: options[:iss]) }, - iat: ->(*) { Claims::IssuedAt.new }, - jti: ->(options) { Claims::JwtId.new(validator: options[:jti]) }, - aud: ->(options) { Claims::Audience.new(expected_audience: options[:aud]) }, - sub: ->(options) { Claims::Subject.new(expected_subject: options[:sub]) }, - crit: ->(options) { Claims::Crit.new(expected_crits: options[:crit]) }, - required: ->(options) { Claims::Required.new(required_claims: options[:required]) }, - numeric: ->(*) { Claims::Numeric.new } - }.freeze - - private_constant(:VERIFIERS) - - class << self - # @api private - def verify!(context, *options) - iterate_verifiers(*options) do |verifier, verifier_options| - verify_one!(context, verifier, verifier_options) - end - nil - end - - # @api private - def errors(context, *options) - errors = [] - iterate_verifiers(*options) do |verifier, verifier_options| - verify_one!(context, verifier, verifier_options) - rescue ::JWT::DecodeError => e - errors << Error.new(message: e.message) - end - errors - end - - private - - def iterate_verifiers(*options) - options.each do |element| - if element.is_a?(Hash) - element.each_key { |key| yield(key, element) } - else - yield(element, {}) - end - end - end - - def verify_one!(context, verifier, options) - verifier_builder = VERIFIERS.fetch(verifier) { raise ArgumentError, "#{verifier} not a valid claim verifier" } - verifier_builder.call(options || {}).verify!(context: context) - end - end - end - end -end diff --git a/lib/jwt/configuration.rb b/lib/jwt/configuration.rb deleted file mode 100644 index cdd37a4ab..000000000 --- a/lib/jwt/configuration.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require_relative 'configuration/container' - -module JWT - # The Configuration module provides methods to configure JWT settings. - module Configuration - # Configures the JWT settings. - # - # @yield [config] Gives the current configuration to the block. - # @yieldparam config [JWT::Configuration::Container] the configuration container. - def configure - yield(configuration) - end - - # Returns the JWT configuration container. - # - # @return [JWT::Configuration::Container] the configuration container. - def configuration - @configuration ||= ::JWT::Configuration::Container.new - end - end -end diff --git a/lib/jwt/configuration/container.rb b/lib/jwt/configuration/container.rb deleted file mode 100644 index 9351d965e..000000000 --- a/lib/jwt/configuration/container.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require_relative 'decode_configuration' -require_relative 'jwk_configuration' - -module JWT - module Configuration - # The Container class holds the configuration settings for JWT. - class Container - # @!attribute [rw] decode - # @return [DecodeConfiguration] the decode configuration. - # @!attribute [rw] jwk - # @return [JwkConfiguration] the JWK configuration. - # @!attribute [rw] strict_base64_decoding - # @return [Boolean] whether strict Base64 decoding is enabled. - attr_accessor :decode, :jwk, :strict_base64_decoding - - # @!attribute [r] deprecation_warnings - # @return [Symbol] the deprecation warnings setting. - attr_reader :deprecation_warnings - - # Initializes a new Container instance and resets the configuration. - def initialize - reset! - end - - # Resets the configuration to default values. - # - # @return [void] - def reset! - @decode = DecodeConfiguration.new - @jwk = JwkConfiguration.new - - self.deprecation_warnings = :once - end - - DEPRECATION_WARNINGS_VALUES = %i[once warn silent].freeze - private_constant(:DEPRECATION_WARNINGS_VALUES) - # Sets the deprecation warnings setting. - # - # @param value [Symbol] the deprecation warnings setting. Must be one of `:once`, `:warn`, or `:silent`. - # @raise [ArgumentError] if the value is not one of the supported values. - # @return [void] - def deprecation_warnings=(value) - raise ArgumentError, "Invalid deprecation_warnings value #{value}. Supported values: #{DEPRECATION_WARNINGS_VALUES}" unless DEPRECATION_WARNINGS_VALUES.include?(value) - - @deprecation_warnings = value - end - end - end -end diff --git a/lib/jwt/configuration/decode_configuration.rb b/lib/jwt/configuration/decode_configuration.rb deleted file mode 100644 index 4acfd3ebb..000000000 --- a/lib/jwt/configuration/decode_configuration.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -module JWT - module Configuration - # The DecodeConfiguration class holds the configuration settings for decoding JWT tokens. - class DecodeConfiguration - # @!attribute [rw] verify_expiration - # @return [Boolean] whether to verify the expiration claim. - # @!attribute [rw] verify_not_before - # @return [Boolean] whether to verify the not before claim. - # @!attribute [rw] verify_iss - # @return [Boolean] whether to verify the issuer claim. - # @!attribute [rw] verify_iat - # @return [Boolean] whether to verify the issued at claim. - # @!attribute [rw] verify_jti - # @return [Boolean] whether to verify the JWT ID claim. - # @!attribute [rw] verify_aud - # @return [Boolean] whether to verify the audience claim. - # @!attribute [rw] verify_sub - # @return [Boolean] whether to verify the subject claim. - # @!attribute [rw] leeway - # @return [Integer] the leeway in seconds for time-based claims. - # @!attribute [rw] algorithms - # @return [Array] the list of acceptable algorithms. - # @!attribute [rw] required_claims - # @return [Array] the list of required claims. - - attr_accessor :verify_expiration, - :verify_not_before, - :verify_iss, - :verify_iat, - :verify_jti, - :verify_aud, - :verify_sub, - :leeway, - :algorithms, - :required_claims - - # Initializes a new DecodeConfiguration instance with default settings. - def initialize - @verify_expiration = true - @verify_not_before = true - @verify_iss = false - @verify_iat = false - @verify_jti = false - @verify_aud = false - @verify_sub = false - @leeway = 0 - @algorithms = ['HS256'] - @required_claims = [] - end - - # @api private - def to_h - { - verify_expiration: verify_expiration, - verify_not_before: verify_not_before, - verify_iss: verify_iss, - verify_iat: verify_iat, - verify_jti: verify_jti, - verify_aud: verify_aud, - verify_sub: verify_sub, - leeway: leeway, - algorithms: algorithms, - required_claims: required_claims - } - end - end - end -end diff --git a/lib/jwt/configuration/jwk_configuration.rb b/lib/jwt/configuration/jwk_configuration.rb deleted file mode 100644 index f1373bcee..000000000 --- a/lib/jwt/configuration/jwk_configuration.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require_relative '../jwk/kid_as_key_digest' -require_relative '../jwk/thumbprint' - -module JWT - module Configuration - # @api private - class JwkConfiguration - def initialize - self.kid_generator_type = :key_digest - end - - def kid_generator_type=(value) - self.kid_generator = case value - when :key_digest - JWT::JWK::KidAsKeyDigest - when :rfc7638_thumbprint - JWT::JWK::Thumbprint - else - raise ArgumentError, "#{value} is not a valid kid generator type." - end - end - - attr_accessor :kid_generator - end - end -end diff --git a/lib/jwt/decode.rb b/lib/jwt/decode.rb deleted file mode 100644 index 9a8a0a60b..000000000 --- a/lib/jwt/decode.rb +++ /dev/null @@ -1,123 +0,0 @@ -# frozen_string_literal: true - -require 'json' -require 'jwt/x5c_key_finder' - -module JWT - # The Decode class is responsible for decoding and verifying JWT tokens. - class Decode - # Order is very important - first check for string keys, next for symbols - ALGORITHM_KEYS = ['algorithm', - :algorithm, - 'algorithms', - :algorithms].freeze - # Initializes a new Decode instance. - # - # @param jwt [String] the JWT to decode. - # @param key [String, Array] the key(s) to use for verification. - # @param verify [Boolean] whether to verify the token's signature. - # @param options [Hash] additional options for decoding and verification. - # @param keyfinder [Proc] an optional key finder block to dynamically find the key for verification. - # @raise [JWT::DecodeError] if decoding or verification fails. - def initialize(jwt, key, verify, options, &keyfinder) - raise JWT::DecodeError, 'Nil JSON web token' unless jwt - - @token = EncodedToken.new(jwt) - @key = key - @options = options - @verify = verify - @keyfinder = keyfinder - end - - # Decodes the JWT token and verifies its segments if verification is enabled. - # - # @return [Array] an array containing the decoded payload and header. - def decode_segments - validate_segment_count! - if @verify - verify_algo - set_key - verify_signature - Claims::DecodeVerifier.verify!(token.unverified_payload, @options) - end - - [token.unverified_payload, token.header] - end - - private - - attr_reader :token - - def verify_signature - return if none_algorithm? - - raise JWT::DecodeError, 'No verification key available' unless @key - - token.verify_signature!(algorithm: allowed_and_valid_algorithms, key: @key) - end - - def verify_algo - raise JWT::IncorrectAlgorithm, 'An algorithm must be specified' if allowed_algorithms.empty? - raise JWT::DecodeError, 'Token header not a JSON object' unless token.header.is_a?(Hash) - raise JWT::IncorrectAlgorithm, 'Token is missing alg header' unless alg_in_header - raise JWT::IncorrectAlgorithm, 'Expected a different algorithm' if allowed_and_valid_algorithms.empty? - end - - def set_key - @key = find_key(&@keyfinder) if @keyfinder - if @options[:jwks] - @key = ::JWT::JWK::KeyFinder.new( - jwks: @options[:jwks], - allow_nil_kid: @options[:allow_nil_kid], - key_fields: @options[:key_fields] - ).call(token) - end - - return unless (x5c_options = @options[:x5c]) - - @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(token.header['x5c']) - end - - def allowed_and_valid_algorithms - @allowed_and_valid_algorithms ||= allowed_algorithms.select { |alg| alg.valid_alg?(alg_in_header) } - end - - def given_algorithms - alg_key = ALGORITHM_KEYS.find { |key| @options[key] } - Array(@options[alg_key]) - end - - def allowed_algorithms - @allowed_algorithms ||= resolve_allowed_algorithms - end - - def resolve_allowed_algorithms - given_algorithms.map { |alg| JWA.resolve(alg) } - end - - def find_key(&keyfinder) - key = (keyfinder.arity == 2 ? yield(token.header, token.unverified_payload) : yield(token.header)) - # key can be of type [string, nil, OpenSSL::PKey, Array] - return key if key && !Array(key).empty? - - raise JWT::DecodeError, 'No verification key available' - end - - def validate_segment_count! - segment_count = token.jwt.count('.') + 1 - return if segment_count == 3 - return if !@verify && segment_count == 2 # If no verifying required, the signature is not needed - return if segment_count == 2 && none_algorithm? - - raise JWT::DecodeError, 'Not enough or too many segments' - end - - def none_algorithm? - alg_in_header == 'none' - end - - def alg_in_header - token.header['alg'] - end - end -end diff --git a/lib/jwt/encode.rb b/lib/jwt/encode.rb deleted file mode 100644 index 32164e09e..000000000 --- a/lib/jwt/encode.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require_relative 'jwa' - -module JWT - # The Encode class is responsible for encoding JWT tokens. - class Encode - # Initializes a new Encode instance. - # - # @param options [Hash] the options for encoding the JWT token. - # @option options [Hash] :payload the payload of the JWT token. - # @option options [Hash] :headers the headers of the JWT token. - # @option options [String] :key the key used to sign the JWT token. - # @option options [String] :algorithm the algorithm used to sign the JWT token. - def initialize(options) - @token = Token.new(payload: options[:payload], header: options[:headers]) - @key = options[:key] - @algorithm = options[:algorithm] - end - - # Encodes the JWT token and returns its segments. - # - # @return [String] the encoded JWT token. - def segments - @token.verify_claims!(:numeric) - @token.sign!(algorithm: @algorithm, key: @key) - @token.jwt - end - end -end diff --git a/lib/jwt/encoded_token.rb b/lib/jwt/encoded_token.rb deleted file mode 100644 index cbaec1c8d..000000000 --- a/lib/jwt/encoded_token.rb +++ /dev/null @@ -1,236 +0,0 @@ -# frozen_string_literal: true - -module JWT - # Represents an encoded JWT token - # - # Processing an encoded and signed token: - # - # token = JWT::Token.new(payload: {pay: 'load'}) - # token.sign!(algorithm: 'HS256', key: 'secret') - # - # encoded_token = JWT::EncodedToken.new(token.jwt) - # encoded_token.verify_signature!(algorithm: 'HS256', key: 'secret') - # encoded_token.payload # => {'pay' => 'load'} - class EncodedToken - # @private - # Allow access to the unverified payload for claim verification. - class ClaimsContext - extend Forwardable - - def_delegators :@token, :header, :unverified_payload - - def initialize(token) - @token = token - end - - def payload - unverified_payload - end - end - - DEFAULT_CLAIMS = [:exp].freeze - - private_constant(:DEFAULT_CLAIMS) - - # Returns the original token provided to the class. - # @return [String] The JWT token. - attr_reader :jwt - - # Initializes a new EncodedToken instance. - # - # @param jwt [String] the encoded JWT token. - # @raise [ArgumentError] if the provided JWT is not a String. - def initialize(jwt) - raise ArgumentError, 'Provided JWT must be a String' unless jwt.is_a?(String) - - @jwt = jwt - @signature_verified = false - @claims_verified = false - - @encoded_header, @encoded_payload, @encoded_signature = jwt.split('.') - end - - # Returns the decoded signature of the JWT token. - # - # @return [String] the decoded signature. - def signature - @signature ||= ::JWT::Base64.url_decode(encoded_signature || '') - end - - # Returns the encoded signature of the JWT token. - # - # @return [String] the encoded signature. - attr_reader :encoded_signature - - # Returns the decoded header of the JWT token. - # - # @return [Hash] the header. - def header - @header ||= parse_and_decode(@encoded_header) - end - - # Returns the encoded header of the JWT token. - # - # @return [String] the encoded header. - attr_reader :encoded_header - - # Returns the payload of the JWT token. Access requires the signature and claims to have been verified. - # - # @return [Hash] the payload. - # @raise [JWT::DecodeError] if the signature has not been verified. - def payload - raise JWT::DecodeError, 'Verify the token signature before accessing the payload' unless @signature_verified - raise JWT::DecodeError, 'Verify the token claims before accessing the payload' unless @claims_verified - - decoded_payload - end - - # Returns the payload of the JWT token without requiring the signature to have been verified. - # @return [Hash] the payload. - def unverified_payload - decoded_payload - end - - # Sets or returns the encoded payload of the JWT token. - # - # @return [String] the encoded payload. - attr_accessor :encoded_payload - - # Returns the signing input of the JWT token. - # - # @return [String] the signing input. - def signing_input - [encoded_header, encoded_payload].join('.') - end - - # Verifies the token signature and claims. - # By default it verifies the 'exp' claim. - # - # @example - # encoded_token.verify!(signature: { algorithm: 'HS256', key: 'secret' }, claims: [:exp]) - # - # @param signature [Hash] the parameters for signature verification (see {#verify_signature!}). - # @param claims [Array, Hash] the claims to verify (see {#verify_claims!}). - # @return [nil] - # @raise [JWT::DecodeError] if the signature or claim verification fails. - def verify!(signature:, claims: nil) - verify_signature!(**signature) - claims.is_a?(Array) ? verify_claims!(*claims) : verify_claims!(claims) - nil - end - - # Verifies the token signature and claims. - # By default it verifies the 'exp' claim. - - # @param signature [Hash] the parameters for signature verification (see {#verify_signature!}). - # @param claims [Array, Hash] the claims to verify (see {#verify_claims!}). - # @return [Boolean] true if the signature and claims are valid, false otherwise. - def valid?(signature:, claims: nil) - valid_signature?(**signature) && - (claims.is_a?(Array) ? valid_claims?(*claims) : valid_claims?(claims)) - end - - # Verifies the signature of the JWT token. - # - # @param algorithm [String, Array, Object, Array] the algorithm(s) to use for verification. - # @param key [String, Array] the key(s) to use for verification. - # @param key_finder [#call] an object responding to `call` to find the key for verification. - # @return [nil] - # @raise [JWT::VerificationError] if the signature verification fails. - # @raise [ArgumentError] if neither key nor key_finder is provided, or if both are provided. - def verify_signature!(algorithm:, key: nil, key_finder: nil) - return if valid_signature?(algorithm: algorithm, key: key, key_finder: key_finder) - - raise JWT::VerificationError, 'Signature verification failed' - end - - # Checks if the signature of the JWT token is valid. - # - # @param algorithm [String, Array, Object, Array] the algorithm(s) to use for verification. - # @param key [String, Array, JWT::JWK::KeyBase, Array] the key(s) to use for verification. - # @param key_finder [#call] an object responding to `call` to find the key for verification. - # @return [Boolean] true if the signature is valid, false otherwise. - def valid_signature?(algorithm: nil, key: nil, key_finder: nil) - raise ArgumentError, 'Provide either key or key_finder, not both or neither' if key.nil? == key_finder.nil? - - keys = Array(key || key_finder.call(self)) - verifiers = JWA.create_verifiers(algorithms: algorithm, keys: keys, preferred_algorithm: header['alg']) - - raise JWT::VerificationError, 'No algorithm provided' if verifiers.empty? - - valid = verifiers.any? do |jwa| - jwa.verify(data: signing_input, signature: signature) - end - valid.tap { |verified| @signature_verified = verified } - end - - # Verifies the claims of the token. - # @param options [Array, Hash] the claims to verify. By default, it checks the 'exp' claim. - # @raise [JWT::DecodeError] if the claims are invalid. - def verify_claims!(*options) - Claims::Verifier.verify!(ClaimsContext.new(self), *claims_options(options)).tap do - @claims_verified = true - end - rescue StandardError - @claims_verified = false - raise - end - - # Returns the errors of the claims of the token. - # @param options [Array, Hash] the claims to verify. By default, it checks the 'exp' claim. - # @return [Array] the errors of the claims. - def claim_errors(*options) - Claims::Verifier.errors(ClaimsContext.new(self), *claims_options(options)) - end - - # Returns whether the claims of the token are valid. - # @param options [Array, Hash] the claims to verify. By default, it checks the 'exp' claim. - # @return [Boolean] whether the claims are valid. - def valid_claims?(*options) - claim_errors(*claims_options(options)).empty?.tap { |verified| @claims_verified = verified } - end - - alias to_s jwt - - private - - def claims_options(options) - return DEFAULT_CLAIMS if options.first.nil? - - options - end - - def decode_payload - raise JWT::DecodeError, 'Encoded payload is empty' if encoded_payload == '' - - if unencoded_payload? - verify_claims!(crit: ['b64']) - return parse_unencoded(encoded_payload) - end - - parse_and_decode(encoded_payload) - end - - def unencoded_payload? - header['b64'] == false - end - - def parse_and_decode(segment) - parse(::JWT::Base64.url_decode(segment || '')) - end - - def parse_unencoded(segment) - parse(segment) - end - - def parse(segment) - JWT::JSON.parse(segment) - rescue ::JSON::ParserError - raise JWT::DecodeError, 'Invalid segment encoding' - end - - def decoded_payload - @decoded_payload ||= decode_payload - end - end -end diff --git a/lib/jwt/error.rb b/lib/jwt/error.rb deleted file mode 100644 index 2a0f8a2ce..000000000 --- a/lib/jwt/error.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -module JWT - # The EncodeError class is raised when there is an error encoding a JWT. - class EncodeError < StandardError; end - - # The DecodeError class is raised when there is an error decoding a JWT. - class DecodeError < StandardError; end - - # The VerificationError class is raised when there is an error verifying a JWT. - class VerificationError < DecodeError; end - - # The ExpiredSignature class is raised when the JWT signature has expired. - class ExpiredSignature < DecodeError; end - - # The IncorrectAlgorithm class is raised when the JWT algorithm is incorrect. - class IncorrectAlgorithm < DecodeError; end - - # The ImmatureSignature class is raised when the JWT signature is immature. - class ImmatureSignature < DecodeError; end - - # The InvalidIssuerError class is raised when the JWT issuer is invalid. - class InvalidIssuerError < DecodeError; end - - # The UnsupportedEcdsaCurve class is raised when the ECDSA curve is unsupported. - class UnsupportedEcdsaCurve < IncorrectAlgorithm; end - - # The InvalidIatError class is raised when the JWT issued at (iat) claim is invalid. - class InvalidIatError < DecodeError; end - - # The InvalidAudError class is raised when the JWT audience (aud) claim is invalid. - class InvalidAudError < DecodeError; end - - # The InvalidSubError class is raised when the JWT subject (sub) claim is invalid. - class InvalidSubError < DecodeError; end - - # The InvalidCritError class is raised when the JWT crit header is invalid. - class InvalidCritError < DecodeError; end - - # The InvalidJtiError class is raised when the JWT ID (jti) claim is invalid. - class InvalidJtiError < DecodeError; end - - # The InvalidPayload class is raised when the JWT payload is invalid. - class InvalidPayload < DecodeError; end - - # The MissingRequiredClaim class is raised when a required claim is missing from the JWT. - class MissingRequiredClaim < DecodeError; end - - # The Base64DecodeError class is raised when there is an error decoding a Base64-encoded string. - class Base64DecodeError < DecodeError; end - - # The JWKError class is raised when there is an error with the JSON Web Key (JWK). - class JWKError < DecodeError; end -end diff --git a/lib/jwt/json.rb b/lib/jwt/json.rb deleted file mode 100644 index 90ae45855..000000000 --- a/lib/jwt/json.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -require 'json' - -module JWT - # @api private - class JSON - class << self - def generate(data) - ::JSON.generate(data) - end - - def parse(data) - ::JSON.parse(data) - end - end - end -end diff --git a/lib/jwt/jwa.rb b/lib/jwt/jwa.rb deleted file mode 100644 index 11f23c4b3..000000000 --- a/lib/jwt/jwa.rb +++ /dev/null @@ -1,103 +0,0 @@ -# frozen_string_literal: true - -require 'openssl' - -require_relative 'jwa/signing_algorithm' -require_relative 'jwa/ecdsa' -require_relative 'jwa/hmac' -require_relative 'jwa/none' -require_relative 'jwa/ps' -require_relative 'jwa/rsa' -require_relative 'jwa/unsupported' - -module JWT - # The JWA module contains all supported algorithms. - module JWA - # @api private - class VerifierContext - attr_reader :jwa - - def initialize(jwa:, keys:) - @jwa = jwa - @keys = Array(keys) - end - - def verify(*args, **kwargs) - @keys.any? do |key| - @jwa.verify(*args, **kwargs, verification_key: key) - end - end - end - - # @api private - class SignerContext - attr_reader :jwa - - def initialize(jwa:, key:) - @jwa = jwa - @key = key - end - - def sign(*args, **kwargs) - @jwa.sign(*args, **kwargs, signing_key: @key) - end - end - - class << self - # @api private - def resolve(algorithm) - return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol) - - raise ArgumentError, 'Algorithm must be provided' if algorithm.nil? - - raise ArgumentError, 'Custom algorithms are required to include JWT::JWA::SigningAlgorithm' unless algorithm.is_a?(SigningAlgorithm) - - algorithm - end - - # @api private - def resolve_and_sort(algorithms:, preferred_algorithm:) - Array(algorithms).map { |alg| JWA.resolve(alg) } - .partition { |alg| alg.valid_alg?(preferred_algorithm) } - .flatten - end - - # @api private - def create_signer(algorithm:, key:) - if key.is_a?(JWK::KeyBase) - validate_jwk_algorithms!(key, algorithm, DecodeError) - - return key - end - - SignerContext.new(jwa: resolve(algorithm), key: key) - end - - # @api private - def create_verifiers(algorithms:, keys:, preferred_algorithm:) - jwks, other_keys = keys.partition { |key| key.is_a?(JWK::KeyBase) } - - validate_jwk_algorithms!(jwks, algorithms, VerificationError) - - jwks + resolve_and_sort(algorithms: algorithms, - preferred_algorithm: preferred_algorithm) - .map { |jwa| VerifierContext.new(jwa: jwa, keys: other_keys) } - end - - # @api private - def validate_jwk_algorithms!(jwks, algorithms, error_class) - algorithms = Array(algorithms) - - return if algorithms.empty? - - return if Array(jwks).all? do |jwk| - algorithms.any? do |alg| - jwk.jwa.valid_alg?(alg) - end - end - - raise error_class, "Provided JWKs do not support one of the specified algorithms: #{algorithms.join(', ')}" - end - end - end -end diff --git a/lib/jwt/jwa/ecdsa.rb b/lib/jwt/jwa/ecdsa.rb deleted file mode 100644 index 9840621f7..000000000 --- a/lib/jwt/jwa/ecdsa.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWA - # ECDSA signing algorithm - class Ecdsa - include JWT::JWA::SigningAlgorithm - - def initialize(alg, digest) - @alg = alg - @digest = digest - end - - def sign(data:, signing_key:) - raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::EC instance") unless signing_key.is_a?(::OpenSSL::PKey::EC) - raise_sign_error!('The given key is not a private key') unless signing_key.private? - - curve_definition = curve_by_name(signing_key.group.curve_name) - key_algorithm = curve_definition[:algorithm] - - raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided" if alg != key_algorithm - - asn1_to_raw(signing_key.dsa_sign_asn1(OpenSSL::Digest.new(digest).digest(data)), signing_key) - end - - def verify(data:, signature:, verification_key:) - verification_key = self.class.create_public_key_from_point(verification_key) if verification_key.is_a?(::OpenSSL::PKey::EC::Point) - - raise_verify_error!("The given key is a #{verification_key.class}. It has to be an OpenSSL::PKey::EC instance") unless verification_key.is_a?(::OpenSSL::PKey::EC) - - curve_definition = curve_by_name(verification_key.group.curve_name) - key_algorithm = curve_definition[:algorithm] - raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided" if alg != key_algorithm - - verification_key.dsa_verify_asn1(OpenSSL::Digest.new(digest).digest(data), raw_to_asn1(signature, verification_key)) - rescue OpenSSL::PKey::PKeyError - raise JWT::VerificationError, 'Signature verification raised' - end - - NAMED_CURVES = { - 'prime256v1' => { - algorithm: 'ES256', - digest: 'sha256' - }, - 'secp256r1' => { # alias for prime256v1 - algorithm: 'ES256', - digest: 'sha256' - }, - 'secp384r1' => { - algorithm: 'ES384', - digest: 'sha384' - }, - 'secp521r1' => { - algorithm: 'ES512', - digest: 'sha512' - }, - 'secp256k1' => { - algorithm: 'ES256K', - digest: 'sha256' - } - }.freeze - - NAMED_CURVES.each_value do |v| - register_algorithm(new(v[:algorithm], v[:digest])) - end - - # @api private - def self.curve_by_name(name) - NAMED_CURVES.fetch(name) do - raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported" - end - end - - if ::JWT.openssl_3? - def self.create_public_key_from_point(point) - sequence = OpenSSL::ASN1::Sequence([ - OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(point.group.curve_name)]), - OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed)) - ]) - OpenSSL::PKey::EC.new(sequence.to_der) - end - else - def self.create_public_key_from_point(point) - OpenSSL::PKey::EC.new(point.group.curve_name).tap do |key| - key.public_key = point - end - end - end - - private - - attr_reader :digest - - def curve_by_name(name) - self.class.curve_by_name(name) - end - - def raw_to_asn1(signature, private_key) - byte_size = (private_key.group.degree + 7) / 8 - sig_bytes = signature[0..(byte_size - 1)] - sig_char = signature[byte_size..-1] || '' - OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der - end - - def asn1_to_raw(signature, public_key) - byte_size = (public_key.group.degree + 7) / 8 - OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join - end - end - end -end diff --git a/lib/jwt/jwa/hmac.rb b/lib/jwt/jwa/hmac.rb deleted file mode 100644 index 86b3278cd..000000000 --- a/lib/jwt/jwa/hmac.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWA - # Implementation of the HMAC family of algorithms - class Hmac - include JWT::JWA::SigningAlgorithm - - def initialize(alg, digest) - @alg = alg - @digest = digest - end - - def sign(data:, signing_key:) - signing_key ||= '' - raise_verify_error!('HMAC key expected to be a String') unless signing_key.is_a?(String) - - OpenSSL::HMAC.digest(digest.new, signing_key, data) - rescue OpenSSL::HMACError => e - raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret') if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure' - - raise e - end - - def verify(data:, signature:, verification_key:) - SecurityUtils.secure_compare(signature, sign(data: data, signing_key: verification_key)) - end - - register_algorithm(new('HS256', OpenSSL::Digest::SHA256)) - register_algorithm(new('HS384', OpenSSL::Digest::SHA384)) - register_algorithm(new('HS512', OpenSSL::Digest::SHA512)) - - private - - attr_reader :digest - - # Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb - # rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate - module SecurityUtils - # Constant time string comparison, for fixed length strings. - # - # The values compared should be of fixed length, such as strings - # that have already been processed by HMAC. Raises in case of length mismatch. - - if defined?(OpenSSL.fixed_length_secure_compare) - def fixed_length_secure_compare(a, b) - OpenSSL.fixed_length_secure_compare(a, b) - end - else - # :nocov: - def fixed_length_secure_compare(a, b) - raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize - - l = a.unpack "C#{a.bytesize}" - - res = 0 - b.each_byte { |byte| res |= byte ^ l.shift } - res == 0 - end - # :nocov: - end - module_function :fixed_length_secure_compare - - # Secure string comparison for strings of variable length. - # - # While a timing attack would not be able to discern the content of - # a secret compared via secure_compare, it is possible to determine - # the secret length. This should be considered when using secure_compare - # to compare weak, short secrets to user input. - def secure_compare(a, b) - a.bytesize == b.bytesize && fixed_length_secure_compare(a, b) - end - module_function :secure_compare - end - # rubocop:enable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate - end - end -end diff --git a/lib/jwt/jwa/none.rb b/lib/jwt/jwa/none.rb deleted file mode 100644 index ddac94956..000000000 --- a/lib/jwt/jwa/none.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWA - # Implementation of the none algorithm - class None - include JWT::JWA::SigningAlgorithm - - def initialize - @alg = 'none' - end - - def sign(*) - '' - end - - def verify(*) - true - end - - register_algorithm(new) - end - end -end diff --git a/lib/jwt/jwa/ps.rb b/lib/jwt/jwa/ps.rb deleted file mode 100644 index 85ef615a5..000000000 --- a/lib/jwt/jwa/ps.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWA - # Implementation of the RSASSA-PSS family of algorithms - class Ps - include JWT::JWA::SigningAlgorithm - - def initialize(alg) - @alg = alg - @digest_algorithm = alg.sub('PS', 'sha') - end - - def sign(data:, signing_key:) - raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance.") unless signing_key.is_a?(::OpenSSL::PKey::RSA) - raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048 - - signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm) - end - - def verify(data:, signature:, verification_key:) - verification_key.verify_pss(digest_algorithm, signature, data, salt_length: :auto, mgf1_hash: digest_algorithm) - rescue OpenSSL::PKey::PKeyError - raise JWT::VerificationError, 'Signature verification raised' - end - - register_algorithm(new('PS256')) - register_algorithm(new('PS384')) - register_algorithm(new('PS512')) - - private - - attr_reader :digest_algorithm - end - end -end diff --git a/lib/jwt/jwa/rsa.rb b/lib/jwt/jwa/rsa.rb deleted file mode 100644 index d25b57646..000000000 --- a/lib/jwt/jwa/rsa.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWA - # Implementation of the RSA family of algorithms - class Rsa - include JWT::JWA::SigningAlgorithm - - def initialize(alg) - @alg = alg - @digest = alg.sub('RS', 'SHA') - end - - def sign(data:, signing_key:) - raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance") unless signing_key.is_a?(OpenSSL::PKey::RSA) - raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048 - - signing_key.sign(OpenSSL::Digest.new(digest), data) - end - - def verify(data:, signature:, verification_key:) - verification_key.verify(OpenSSL::Digest.new(digest), signature, data) - rescue OpenSSL::PKey::PKeyError - raise JWT::VerificationError, 'Signature verification raised' - end - - register_algorithm(new('RS256')) - register_algorithm(new('RS384')) - register_algorithm(new('RS512')) - - private - - attr_reader :digest - end - end -end diff --git a/lib/jwt/jwa/signing_algorithm.rb b/lib/jwt/jwa/signing_algorithm.rb deleted file mode 100644 index b4590a8b0..000000000 --- a/lib/jwt/jwa/signing_algorithm.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -module JWT - # JSON Web Algorithms - module JWA - # Base functionality for signing algorithms - module SigningAlgorithm - # Class methods for the SigningAlgorithm module - module ClassMethods - def register_algorithm(algo) - ::JWT::JWA.register_algorithm(algo) - end - end - - def self.included(klass) - klass.extend(ClassMethods) - end - - attr_reader :alg - - def valid_alg?(alg_to_check) - alg&.casecmp(alg_to_check)&.zero? == true - end - - def header(*) - { 'alg' => alg } - end - - def sign(*) - raise_sign_error!('Algorithm implementation is missing the sign method') - end - - def verify(*) - raise_verify_error!('Algorithm implementation is missing the verify method') - end - - def raise_verify_error!(message) - raise(DecodeError.new(message).tap { |e| e.set_backtrace(caller(1)) }) - end - - def raise_sign_error!(message) - raise(EncodeError.new(message).tap { |e| e.set_backtrace(caller(1)) }) - end - end - - class << self - def register_algorithm(algo) - algorithms[algo.alg.to_s.downcase] = algo - end - - def find(algo) - algorithms.fetch(algo.to_s.downcase, Unsupported) - end - - private - - def algorithms - @algorithms ||= {} - end - end - end -end diff --git a/lib/jwt/jwa/unsupported.rb b/lib/jwt/jwa/unsupported.rb deleted file mode 100644 index beb4be1f4..000000000 --- a/lib/jwt/jwa/unsupported.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWA - # Represents an unsupported algorithm - module Unsupported - class << self - include JWT::JWA::SigningAlgorithm - - def sign(*) - raise_sign_error!('Unsupported signing method') - end - - def verify(*) - raise JWT::VerificationError, 'Algorithm not supported' - end - end - end - end -end diff --git a/lib/jwt/jwk.rb b/lib/jwt/jwk.rb deleted file mode 100644 index d717bac78..000000000 --- a/lib/jwt/jwk.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -require_relative 'jwk/key_finder' -require_relative 'jwk/set' - -module JWT - # JSON Web Key (JWK) - module JWK - class << self - def create_from(key, params = nil, options = {}) - if key.is_a?(Hash) - jwk_kty = key[:kty] || key['kty'] - raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty - - return mappings.fetch(jwk_kty.to_s) do |kty| - raise JWT::JWKError, "Key type #{kty} not supported" - end.new(key, params, options) - end - - mappings.fetch(key.class) do |klass| - raise JWT::JWKError, "Cannot create JWK from a #{klass.name}" - end.new(key, params, options) - end - - def classes - @mappings = nil # reset the cached mappings - @classes ||= [] - end - - alias new create_from - alias import create_from - - private - - def mappings - @mappings ||= generate_mappings - end - - def generate_mappings - classes.each_with_object({}) do |klass, hash| - next unless klass.const_defined?('KTYS') - - Array(klass::KTYS).each do |kty| - hash[kty] = klass - end - end - end - end - end -end - -require_relative 'jwk/key_base' -require_relative 'jwk/ec' -require_relative 'jwk/rsa' -require_relative 'jwk/hmac' diff --git a/lib/jwt/jwk/ec.rb b/lib/jwt/jwk/ec.rb deleted file mode 100644 index e240aa049..000000000 --- a/lib/jwt/jwk/ec.rb +++ /dev/null @@ -1,240 +0,0 @@ -# frozen_string_literal: true - -require 'forwardable' - -module JWT - module JWK - # JWK representation for Elliptic Curve (EC) keys - class EC < KeyBase # rubocop:disable Metrics/ClassLength - KTY = 'EC' - KTYS = [KTY, OpenSSL::PKey::EC, JWT::JWK::EC].freeze - BINARY = 2 - EC_PUBLIC_KEY_ELEMENTS = %i[kty crv x y].freeze - EC_PRIVATE_KEY_ELEMENTS = %i[d].freeze - EC_KEY_ELEMENTS = (EC_PRIVATE_KEY_ELEMENTS + EC_PUBLIC_KEY_ELEMENTS).freeze - ZERO_BYTE = "\0".b.freeze - - def initialize(key, params = nil, options = {}) - params ||= {} - - # For backwards compatibility when kid was a String - params = { kid: params } if params.is_a?(String) - - key_params = extract_key_params(key) - - params = params.transform_keys(&:to_sym) - check_jwk_params!(key_params, params) - - super(options, key_params.merge(params)) - end - - def keypair - ec_key - end - - def private? - ec_key.private_key? - end - - def signing_key - ec_key - end - - def verify_key - ec_key - end - - def public_key - ec_key - end - - def members - EC_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] } - end - - def export(options = {}) - exported = parameters.clone - exported.reject! { |k, _| EC_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true - exported - end - - def key_digest - _crv, x_octets, y_octets = keypair_components(ec_key) - sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)), - OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))]) - OpenSSL::Digest::SHA256.hexdigest(sequence.to_der) - end - - def []=(key, value) - raise ArgumentError, 'cannot overwrite cryptographic key attributes' if EC_KEY_ELEMENTS.include?(key.to_sym) - - super - end - - def jwa - return super if self[:alg] - - curve_name = self.class.to_openssl_curve(self[:crv]) - JWA.resolve(JWA::Ecdsa.curve_by_name(curve_name)[:algorithm]) - end - - private - - def ec_key - @ec_key ||= create_ec_key(self[:crv], self[:x], self[:y], self[:d]) - end - - def extract_key_params(key) - case key - when JWT::JWK::EC - key.export(include_private: true) - when OpenSSL::PKey::EC # Accept OpenSSL key as input - @ec_key = key # Preserve the object to avoid recreation - parse_ec_key(key) - when Hash - key.transform_keys(&:to_sym) - else - raise ArgumentError, 'key must be of type OpenSSL::PKey::EC or Hash with key parameters' - end - end - - def check_jwk_params!(key_params, params) - raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (EC_KEY_ELEMENTS & params.keys).empty? - raise JWT::JWKError, "Incorrect 'kty' value: #{key_params[:kty]}, expected #{KTY}" unless key_params[:kty] == KTY - raise JWT::JWKError, 'Key format is invalid for EC' unless key_params[:crv] && key_params[:x] && key_params[:y] - end - - def keypair_components(ec_keypair) - encoded_point = ec_keypair.public_key.to_bn.to_s(BINARY) - case ec_keypair.group.curve_name - when 'prime256v1' - crv = 'P-256' - x_octets, y_octets = encoded_point.unpack('xa32a32') - when 'secp256k1' - crv = 'P-256K' - x_octets, y_octets = encoded_point.unpack('xa32a32') - when 'secp384r1' - crv = 'P-384' - x_octets, y_octets = encoded_point.unpack('xa48a48') - when 'secp521r1' - crv = 'P-521' - x_octets, y_octets = encoded_point.unpack('xa66a66') - else - raise JWT::JWKError, "Unsupported curve '#{ec_keypair.group.curve_name}'" - end - [crv, x_octets, y_octets] - end - - def encode_octets(octets) - return unless octets - - ::JWT::Base64.url_encode(octets) - end - - def parse_ec_key(key) - crv, x_octets, y_octets = keypair_components(key) - octets = key.private_key&.to_bn&.to_s(BINARY) - { - kty: KTY, - crv: crv, - x: encode_octets(x_octets), - y: encode_octets(y_octets), - d: encode_octets(octets) - }.compact - end - - def create_point(jwk_crv, jwk_x, jwk_y) - curve = EC.to_openssl_curve(jwk_crv) - x_octets = decode_octets(jwk_x) - y_octets = decode_octets(jwk_y) - - # The details of the `Point` instantiation are covered in: - # - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html - # - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html - # - https://tools.ietf.org/html/rfc5480#section-2.2 - # - https://www.secg.org/SEC1-Ver-1.0.pdf - # Section 2.3.3 of the last of these references specifies that the - # encoding of an uncompressed point consists of the byte `0x04` followed - # by the x value then the y value. - OpenSSL::PKey::EC::Point.new( - OpenSSL::PKey::EC::Group.new(curve), - OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2) - ) - end - - if ::JWT.openssl_3? - def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) - point = create_point(jwk_crv, jwk_x, jwk_y) - - return ::JWT::JWA::Ecdsa.create_public_key_from_point(point) unless jwk_d - - # https://datatracker.ietf.org/doc/html/rfc5915.html - # ECPrivateKey ::= SEQUENCE { - # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), - # privateKey OCTET STRING, - # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, - # publicKey [1] BIT STRING OPTIONAL - # } - - sequence = OpenSSL::ASN1::Sequence([ - OpenSSL::ASN1::Integer(1), - OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)), - OpenSSL::ASN1::ObjectId(point.group.curve_name, 0, :EXPLICIT), - OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT) - ]) - OpenSSL::PKey::EC.new(sequence.to_der) - end - else - def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) - point = create_point(jwk_crv, jwk_x, jwk_y) - - ::JWT::JWA::Ecdsa.create_public_key_from_point(point).tap do |key| - key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d - end - end - end - - def decode_octets(base64_encoded_coordinate) - bytes = ::JWT::Base64.url_decode(base64_encoded_coordinate) - # Some base64 encoders on some platform omit a single 0-byte at - # the start of either Y or X coordinate of the elliptic curve point. - # This leads to an encoding error when data is passed to OpenSSL BN. - # It is know to have happened to exported JWKs on a Java application and - # on a Flutter/Dart application (both iOS and Android). All that is - # needed to fix the problem is adding a leading 0-byte. We know the - # required byte is 0 because with any other byte the point is no longer - # on the curve - and OpenSSL will actually communicate this via another - # exception. The indication of a stripped byte will be the fact that the - # coordinates - once decoded into bytes - should always be an even - # bytesize. For example, with a P-521 curve, both x and y must be 66 bytes. - # With a P-256 curve, both x and y must be 32 and so on. The simplest way - # to check for this truncation is thus to check whether the number of bytes - # is odd, and restore the leading 0-byte if it is. - if bytes.bytesize.odd? - ZERO_BYTE + bytes - else - bytes - end - end - - class << self - def import(jwk_data) - new(jwk_data) - end - - def to_openssl_curve(crv) - # The JWK specs and OpenSSL use different names for the same curves. - # See https://tools.ietf.org/html/rfc5480#section-2.1.1.1 for some - # pointers on different names for common curves. - case crv - when 'P-256' then 'prime256v1' - when 'P-384' then 'secp384r1' - when 'P-521' then 'secp521r1' - when 'P-256K' then 'secp256k1' - else raise JWT::JWKError, 'Invalid curve provided' - end - end - end - end - end -end diff --git a/lib/jwt/jwk/hmac.rb b/lib/jwt/jwk/hmac.rb deleted file mode 100644 index 6813367ac..000000000 --- a/lib/jwt/jwk/hmac.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWK - # JWK for HMAC keys - class HMAC < KeyBase - KTY = 'oct' - KTYS = [KTY, String, JWT::JWK::HMAC].freeze - HMAC_PUBLIC_KEY_ELEMENTS = %i[kty].freeze - HMAC_PRIVATE_KEY_ELEMENTS = %i[k].freeze - HMAC_KEY_ELEMENTS = (HMAC_PRIVATE_KEY_ELEMENTS + HMAC_PUBLIC_KEY_ELEMENTS).freeze - - def initialize(key, params = nil, options = {}) - params ||= {} - - # For backwards compatibility when kid was a String - params = { kid: params } if params.is_a?(String) - - key_params = extract_key_params(key) - - params = params.transform_keys(&:to_sym) - check_jwk(key_params, params) - - super(options, key_params.merge(params)) - end - - def keypair - secret - end - - def private? - true - end - - def public_key - nil - end - - def verify_key - secret - end - - def signing_key - secret - end - - # See https://tools.ietf.org/html/rfc7517#appendix-A.3 - def export(options = {}) - exported = parameters.clone - exported.reject! { |k, _| HMAC_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true - exported - end - - def members - HMAC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] } - end - - def key_digest - sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::UTF8String.new(signing_key), - OpenSSL::ASN1::UTF8String.new(KTY)]) - OpenSSL::Digest::SHA256.hexdigest(sequence.to_der) - end - - def []=(key, value) - raise ArgumentError, 'cannot overwrite cryptographic key attributes' if HMAC_KEY_ELEMENTS.include?(key.to_sym) - - super - end - - private - - def secret - @secret ||= ::JWT::Base64.url_decode(self[:k]) - end - - def extract_key_params(key) - case key - when JWT::JWK::HMAC - key.export(include_private: true) - when String # Accept String key as input - { kty: KTY, k: ::JWT::Base64.url_encode(key) } - when Hash - key.transform_keys(&:to_sym) - else - raise ArgumentError, 'key must be of type String or Hash with key parameters' - end - end - - def check_jwk(keypair, params) - raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (HMAC_KEY_ELEMENTS & params.keys).empty? - raise JWT::JWKError, "Incorrect 'kty' value: #{keypair[:kty]}, expected #{KTY}" unless keypair[:kty] == KTY - raise JWT::JWKError, 'Key format is invalid for HMAC' unless keypair[:k] - end - - class << self - def import(jwk_data) - new(jwk_data) - end - end - end - end -end diff --git a/lib/jwt/jwk/key_base.rb b/lib/jwt/jwk/key_base.rb deleted file mode 100644 index ac9d9b911..000000000 --- a/lib/jwt/jwk/key_base.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWK - # Base for JWK implementations - class KeyBase - def self.inherited(klass) - super - ::JWT::JWK.classes << klass - end - - def initialize(options, params = {}) - options ||= {} - - @parameters = params.transform_keys(&:to_sym) # Uniform interface - - # For backwards compatibility, kid_generator may be specified in the parameters - options[:kid_generator] ||= @parameters.delete(:kid_generator) - - # Make sure the key has a kid - kid_generator = options[:kid_generator] || ::JWT.configuration.jwk.kid_generator - self[:kid] ||= kid_generator.new(self).generate - end - - def kid - self[:kid] - end - - def hash - self[:kid].hash - end - - def [](key) - @parameters[key.to_sym] - end - - def []=(key, value) - @parameters[key.to_sym] = value - end - - def ==(other) - other.is_a?(::JWT::JWK::KeyBase) && self[:kid] == other[:kid] - end - - def verify(**kwargs) - jwa.verify(**kwargs, verification_key: verify_key) - end - - def sign(**kwargs) - jwa.sign(**kwargs, signing_key: signing_key) - end - - alias eql? == - - def <=>(other) - return nil unless other.is_a?(::JWT::JWK::KeyBase) - - self[:kid] <=> other[:kid] - end - - def jwa - raise JWT::JWKError, 'Could not resolve the JWA, the "alg" parameter is missing' unless self[:alg] - - JWA.resolve(self[:alg]).tap do |jwa| - raise JWT::JWKError, 'none algorithm usage not supported via JWK' if jwa.is_a?(JWA::None) - end - end - - attr_reader :parameters - end - end -end diff --git a/lib/jwt/jwk/key_finder.rb b/lib/jwt/jwk/key_finder.rb deleted file mode 100644 index c7387841e..000000000 --- a/lib/jwt/jwk/key_finder.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWK - # JSON Web Key keyfinder - # To find the key for a given kid - class KeyFinder - # Initializes a new KeyFinder instance. - # @param [Hash] options the options to create a KeyFinder with - # @option options [Proc, JWT::JWK::Set] :jwks the jwks or a loader proc - # @option options [Boolean] :allow_nil_kid whether to allow nil kid - # @option options [Array] :key_fields the fields to use for key matching, - # the order of the fields are used to determine - # the priority of the keys. - def initialize(options) - @allow_nil_kid = options[:allow_nil_kid] - jwks_or_loader = options[:jwks] - - @jwks_loader = if jwks_or_loader.respond_to?(:call) - jwks_or_loader - else - ->(_options) { jwks_or_loader } - end - - @key_fields = options[:key_fields] || %i[kid] - end - - # Returns the verification key for the given kid - # @param [String] kid the key id - def key_for(kid, key_field = :kid) - raise ::JWT::DecodeError, "Invalid type for #{key_field} header parameter" unless kid.nil? || kid.is_a?(String) - - jwk = resolve_key(kid, key_field) - - raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any? - raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk - - jwk.verify_key - end - - # Returns the key for the given token - # @param [JWT::EncodedToken] token the token - def call(token) - @key_fields.each do |key_field| - field_value = token.header[key_field.to_s] - - return key_for(field_value, key_field) if field_value - end - - raise ::JWT::DecodeError, 'No key id (kid) or x5t found from token headers' unless @allow_nil_kid - - kid = token.header['kid'] - key_for(kid) - end - - private - - def resolve_key(kid, key_field) - key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[key_field] == kid } - - # First try without invalidation to facilitate application caching - @jwks ||= JWT::JWK::Set.new(@jwks_loader.call(key_field => kid)) - jwk = @jwks.find { |key| key_matcher.call(key) } - - return jwk if jwk - - # Second try, invalidate for backwards compatibility - @jwks = JWT::JWK::Set.new(@jwks_loader.call(invalidate: true, kid_not_found: true, key_field => kid)) - @jwks.find { |key| key_matcher.call(key) } - end - end - end -end diff --git a/lib/jwt/jwk/kid_as_key_digest.rb b/lib/jwt/jwk/kid_as_key_digest.rb deleted file mode 100644 index 08a1d2a70..000000000 --- a/lib/jwt/jwk/kid_as_key_digest.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWK - # @api private - class KidAsKeyDigest - def initialize(jwk) - @jwk = jwk - end - - def generate - @jwk.key_digest - end - end - end -end diff --git a/lib/jwt/jwk/rsa.rb b/lib/jwt/jwk/rsa.rb deleted file mode 100644 index 2a28e0f7f..000000000 --- a/lib/jwt/jwk/rsa.rb +++ /dev/null @@ -1,206 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWK - # JSON Web Key (JWK) representation of a RSA key - class RSA < KeyBase # rubocop:disable Metrics/ClassLength - BINARY = 2 - KTY = 'RSA' - KTYS = [KTY, OpenSSL::PKey::RSA, JWT::JWK::RSA].freeze - RSA_PUBLIC_KEY_ELEMENTS = %i[kty n e].freeze - RSA_PRIVATE_KEY_ELEMENTS = %i[d p q dp dq qi].freeze - RSA_KEY_ELEMENTS = (RSA_PRIVATE_KEY_ELEMENTS + RSA_PUBLIC_KEY_ELEMENTS).freeze - - RSA_OPT_PARAMS = %i[p q dp dq qi].freeze - RSA_ASN1_SEQUENCE = (%i[n e d] + RSA_OPT_PARAMS).freeze # https://www.rfc-editor.org/rfc/rfc3447#appendix-A.1.2 - - def initialize(key, params = nil, options = {}) - params ||= {} - - # For backwards compatibility when kid was a String - params = { kid: params } if params.is_a?(String) - - key_params = extract_key_params(key) - - params = params.transform_keys(&:to_sym) - check_jwk_params!(key_params, params) - - super(options, key_params.merge(params)) - end - - def keypair - rsa_key - end - - def private? - rsa_key.private? - end - - def public_key - rsa_key.public_key - end - - def signing_key - rsa_key if private? - end - - def verify_key - rsa_key.public_key - end - - def export(options = {}) - exported = parameters.clone - exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true - - exported - end - - def members - RSA_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] } - end - - def key_digest - sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n), - OpenSSL::ASN1::Integer.new(public_key.e)]) - OpenSSL::Digest::SHA256.hexdigest(sequence.to_der) - end - - def []=(key, value) - raise ArgumentError, 'cannot overwrite cryptographic key attributes' if RSA_KEY_ELEMENTS.include?(key.to_sym) - - super - end - - private - - def rsa_key - @rsa_key ||= self.class.create_rsa_key(jwk_attributes(*(RSA_KEY_ELEMENTS - [:kty]))) - end - - def extract_key_params(key) - case key - when JWT::JWK::RSA - key.export(include_private: true) - when OpenSSL::PKey::RSA # Accept OpenSSL key as input - @rsa_key = key # Preserve the object to avoid recreation - parse_rsa_key(key) - when Hash - key.transform_keys(&:to_sym) - else - raise ArgumentError, 'key must be of type OpenSSL::PKey::RSA or Hash with key parameters' - end - end - - def check_jwk_params!(key_params, params) - raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (RSA_KEY_ELEMENTS & params.keys).empty? - raise JWT::JWKError, "Incorrect 'kty' value: #{key_params[:kty]}, expected #{KTY}" unless key_params[:kty] == KTY - raise JWT::JWKError, 'Key format is invalid for RSA' unless key_params[:n] && key_params[:e] - end - - def parse_rsa_key(key) - { - kty: KTY, - n: encode_open_ssl_bn(key.n), - e: encode_open_ssl_bn(key.e), - d: encode_open_ssl_bn(key.d), - p: encode_open_ssl_bn(key.p), - q: encode_open_ssl_bn(key.q), - dp: encode_open_ssl_bn(key.dmp1), - dq: encode_open_ssl_bn(key.dmq1), - qi: encode_open_ssl_bn(key.iqmp) - }.compact - end - - def jwk_attributes(*attributes) - attributes.each_with_object({}) do |attribute, hash| - hash[attribute] = decode_open_ssl_bn(self[attribute]) - end - end - - def encode_open_ssl_bn(key_part) - return unless key_part - - ::JWT::Base64.url_encode(key_part.to_s(BINARY)) - end - - def decode_open_ssl_bn(jwk_data) - self.class.decode_open_ssl_bn(jwk_data) - end - - class << self - def import(jwk_data) - new(jwk_data) - end - - def decode_open_ssl_bn(jwk_data) - return nil unless jwk_data - - OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY) - end - - def create_rsa_key_using_der(rsa_parameters) - validate_rsa_parameters!(rsa_parameters) - - sequence = RSA_ASN1_SEQUENCE.each_with_object([]) do |key, arr| - next if rsa_parameters[key].nil? - - arr << OpenSSL::ASN1::Integer.new(rsa_parameters[key]) - end - - if sequence.size > 2 # Append "two-prime" version for private key - sequence.unshift(OpenSSL::ASN1::Integer.new(0)) - - raise JWT::JWKError, 'Creating a RSA key with a private key requires the CRT parameters to be defined' if sequence.size < RSA_ASN1_SEQUENCE.size - end - - OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der) - end - - def create_rsa_key_using_sets(rsa_parameters) - validate_rsa_parameters!(rsa_parameters) - - OpenSSL::PKey::RSA.new.tap do |rsa_key| - rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d]) - rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q] - rsa_key.set_crt_params(rsa_parameters[:dp], rsa_parameters[:dq], rsa_parameters[:qi]) if rsa_parameters[:dp] && rsa_parameters[:dq] && rsa_parameters[:qi] - end - end - - # :nocov: - # Before openssl 2.0, we need to use the accessors to set the key - def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/AbcSize - validate_rsa_parameters!(rsa_parameters) - - OpenSSL::PKey::RSA.new.tap do |rsa_key| - rsa_key.n = rsa_parameters[:n] - rsa_key.e = rsa_parameters[:e] - rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d] - rsa_key.p = rsa_parameters[:p] if rsa_parameters[:p] - rsa_key.q = rsa_parameters[:q] if rsa_parameters[:q] - rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp] - rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq] - rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi] - end - end - # :nocov: - - def validate_rsa_parameters!(rsa_parameters) - return unless rsa_parameters.key?(:d) - - parameters = RSA_OPT_PARAMS - rsa_parameters.keys - return if parameters.empty? || parameters.size == RSA_OPT_PARAMS.size - - raise JWT::JWKError, 'When one of p, q, dp, dq or qi is given all the other optimization parameters also needs to be defined' # https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.2 - end - - if ::JWT.openssl_3? - alias create_rsa_key create_rsa_key_using_der - elsif OpenSSL::PKey::RSA.method_defined?(:set_key) - alias create_rsa_key create_rsa_key_using_sets - else - alias create_rsa_key create_rsa_key_using_accessors - end - end - end - end -end diff --git a/lib/jwt/jwk/set.rb b/lib/jwt/jwk/set.rb deleted file mode 100644 index 6f93e56ee..000000000 --- a/lib/jwt/jwk/set.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -require 'forwardable' - -module JWT - module JWK - # JSON Web Key Set (JWKS) representation - # https://tools.ietf.org/html/rfc7517 - class Set - include Enumerable - extend Forwardable - - attr_reader :keys - - def initialize(jwks = nil, options = {}) # rubocop:disable Metrics/CyclomaticComplexity - jwks ||= {} - - @keys = case jwks - when JWT::JWK::Set # Simple duplication - jwks.keys - when JWT::JWK::KeyBase # Singleton - [jwks] - when Hash - jwks = jwks.transform_keys(&:to_sym) - [*jwks[:keys]].map { |k| JWT::JWK.new(k, nil, options) } - when Array - jwks.map { |k| JWT::JWK.new(k, nil, options) } - else - raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK' - end - end - - def export(options = {}) - { keys: @keys.map { |k| k.export(options) } } - end - - def_delegators :@keys, :each, :size, :delete, :dig - - def select!(&block) - return @keys.select! unless block - - self if @keys.select!(&block) - end - - def reject!(&block) - return @keys.reject! unless block - - self if @keys.reject!(&block) - end - - def uniq!(&block) - self if @keys.uniq!(&block) - end - - def merge(enum) - @keys += JWT::JWK::Set.new(enum.to_a).keys - self - end - - def union(enum) - dup.merge(enum) - end - - def add(key) - @keys << JWT::JWK.new(key) - self - end - - def ==(other) - other.is_a?(JWT::JWK::Set) && keys.sort == other.keys.sort - end - - alias eql? == - alias filter! select! - alias length size - # For symbolic manipulation - alias | union - alias + union - alias << add - end - end -end diff --git a/lib/jwt/jwk/thumbprint.rb b/lib/jwt/jwk/thumbprint.rb deleted file mode 100644 index 3583f5780..000000000 --- a/lib/jwt/jwk/thumbprint.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module JWT - module JWK - # https://tools.ietf.org/html/rfc7638 - class Thumbprint - attr_reader :jwk - - def initialize(jwk) - @jwk = jwk - end - - def generate - ::Base64.urlsafe_encode64( - Digest::SHA256.digest( - JWT::JSON.generate( - jwk.members.sort.to_h - ) - ), padding: false - ) - end - - alias to_s generate - end - end -end diff --git a/lib/jwt/token.rb b/lib/jwt/token.rb deleted file mode 100644 index 0c643886f..000000000 --- a/lib/jwt/token.rb +++ /dev/null @@ -1,131 +0,0 @@ -# frozen_string_literal: true - -module JWT - # Represents a JWT token - # - # Basic token signed using the HS256 algorithm: - # - # token = JWT::Token.new(payload: {pay: 'load'}) - # token.sign!(algorithm: 'HS256', key: 'secret') - # token.jwt # => eyJhb.... - # - # Custom headers will be combined with generated headers: - # token = JWT::Token.new(payload: {pay: 'load'}, header: {custom: "value"}) - # token.sign!(algorithm: 'HS256', key: 'secret') - # token.header # => {"custom"=>"value", "alg"=>"HS256"} - # - class Token - # Initializes a new Token instance. - # - # @param header [Hash] the header of the JWT token. - # @param payload [Hash] the payload of the JWT token. - def initialize(payload:, header: {}) - @header = header&.transform_keys(&:to_s) - @payload = payload - end - - # Returns the decoded signature of the JWT token. - # - # @return [String] the decoded signature of the JWT token. - def signature - @signature ||= ::JWT::Base64.url_decode(encoded_signature || '') - end - - # Returns the encoded signature of the JWT token. - # - # @return [String] the encoded signature of the JWT token. - def encoded_signature - @encoded_signature ||= ::JWT::Base64.url_encode(signature) - end - - # Returns the decoded header of the JWT token. - # - # @return [Hash] the header of the JWT token. - attr_reader :header - - # Returns the encoded header of the JWT token. - # - # @return [String] the encoded header of the JWT token. - def encoded_header - @encoded_header ||= ::JWT::Base64.url_encode(JWT::JSON.generate(header)) - end - - # Returns the payload of the JWT token. - # - # @return [Hash] the payload of the JWT token. - attr_reader :payload - - # Returns the encoded payload of the JWT token. - # - # @return [String] the encoded payload of the JWT token. - def encoded_payload - @encoded_payload ||= ::JWT::Base64.url_encode(JWT::JSON.generate(payload)) - end - - # Returns the signing input of the JWT token. - # - # @return [String] the signing input of the JWT token. - def signing_input - @signing_input ||= [encoded_header, encoded_payload].join('.') - end - - # Returns the JWT token as a string. - # - # @return [String] the JWT token as a string. - # @raise [JWT::EncodeError] if the token is not signed or other encoding issues - def jwt - @jwt ||= (@signature && [encoded_header, @detached_payload ? '' : encoded_payload, encoded_signature].join('.')) || raise(::JWT::EncodeError, 'Token is not signed') - end - - # Detaches the payload according to https://datatracker.ietf.org/doc/html/rfc7515#appendix-F - # - def detach_payload! - @detached_payload = true - - nil - end - - # Signs the JWT token. - # - # @param key [String, JWT::JWK::KeyBase] the key to use for signing. - # @param algorithm [String, Object] the algorithm to use for signing. - # @return [void] - # @raise [JWT::EncodeError] if the token is already signed or other problems when signing - def sign!(key:, algorithm:) - raise ::JWT::EncodeError, 'Token already signed' if @signature - - JWA.create_signer(algorithm: algorithm, key: key).tap do |signer| - header.merge!(signer.jwa.header) { |_key, old, _new| old } - @signature = signer.sign(data: signing_input) - end - - nil - end - - # Verifies the claims of the token. - # @param options [Array, Hash] the claims to verify. - # @raise [JWT::DecodeError] if the claims are invalid. - def verify_claims!(*options) - Claims::Verifier.verify!(self, *options) - end - - # Returns the errors of the claims of the token. - # @param options [Array, Hash] the claims to verify. - # @return [Array] the errors of the claims. - def claim_errors(*options) - Claims::Verifier.errors(self, *options) - end - - # Returns whether the claims of the token are valid. - # @param options [Array, Hash] the claims to verify. - # @return [Boolean] whether the claims are valid. - def valid_claims?(*options) - claim_errors(*options).empty? - end - - # Returns the JWT token as a string. - # - # @return [String] the JWT token as a string. - alias to_s jwt - end -end diff --git a/lib/jwt/version.rb b/lib/jwt/version.rb deleted file mode 100644 index 4c1321c03..000000000 --- a/lib/jwt/version.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -# JSON Web Token implementation -# -# Should be up to date with the latest spec: -# https://tools.ietf.org/html/rfc7519 -module JWT - # Returns the gem version of the JWT library. - # - # @return [Gem::Version] the gem version. - def self.gem_version - Gem::Version.new(VERSION::STRING) - end - - # Version constants - module VERSION - MAJOR = 3 - MINOR = 1 - TINY = 3 - PRE = nil - - STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') - end - - # Checks if the OpenSSL version is 3 or greater. - # - # @return [Boolean] true if OpenSSL version is 3 or greater, false otherwise. - # @api private - def self.openssl_3? - return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL') - - true if 3 * 0x10000000 <= OpenSSL::OPENSSL_VERSION_NUMBER - end - - # Checks if there is an OpenSSL 3 HMAC empty key regression. - # - # @return [Boolean] true if there is an OpenSSL 3 HMAC empty key regression, false otherwise. - # @api private - def self.openssl_3_hmac_empty_key_regression? - openssl_3? && openssl_version <= ::Gem::Version.new('3.0.0') - end - - # Returns the OpenSSL version. - # - # @return [Gem::Version] the OpenSSL version. - # @api private - def self.openssl_version - @openssl_version ||= ::Gem::Version.new(OpenSSL::VERSION) - end -end diff --git a/lib/jwt/x5c_key_finder.rb b/lib/jwt/x5c_key_finder.rb deleted file mode 100644 index 265408552..000000000 --- a/lib/jwt/x5c_key_finder.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -module JWT - # If the x5c header certificate chain can be validated by trusted root - # certificates, and none of the certificates are revoked, returns the public - # key from the first certificate. - # See https://tools.ietf.org/html/rfc7515#section-4.1.6 - class X5cKeyFinder - def initialize(root_certificates, crls = nil) - raise ArgumentError, 'Root certificates must be specified' unless root_certificates - - @store = build_store(root_certificates, crls) - end - - def from(x5c_header_or_certificates) - signing_certificate, *certificate_chain = parse_certificates(x5c_header_or_certificates) - store_context = OpenSSL::X509::StoreContext.new(@store, signing_certificate, certificate_chain) - - if store_context.verify - signing_certificate.public_key - else - error = "Certificate verification failed: #{store_context.error_string}." - if (current_cert = store_context.current_cert) - error = "#{error} Certificate subject: #{current_cert.subject}." - end - - raise JWT::VerificationError, error - end - end - - private - - def build_store(root_certificates, crls) - store = OpenSSL::X509::Store.new - store.purpose = OpenSSL::X509::PURPOSE_ANY - store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL - root_certificates.each { |certificate| store.add_cert(certificate) } - crls&.each { |crl| store.add_crl(crl) } - store - end - - def parse_certificates(x5c_header_or_certificates) - if x5c_header_or_certificates.all? { |obj| obj.is_a?(OpenSSL::X509::Certificate) } - x5c_header_or_certificates - else - x5c_header_or_certificates.map do |encoded| - OpenSSL::X509::Certificate.new(::JWT::Base64.url_decode(encoded)) - end - end - end - end -end diff --git a/method_list.html b/method_list.html new file mode 100644 index 000000000..d9e86974f --- /dev/null +++ b/method_list.html @@ -0,0 +1,1726 @@ + + + + + + + + + + + + + + + + + + Method List + + + +
        +
        +

        Method List

        + + + +
        + + +
        + + diff --git a/ruby-jwt.gemspec b/ruby-jwt.gemspec deleted file mode 100644 index 1c469c462..000000000 --- a/ruby-jwt.gemspec +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -lib = File.expand_path('lib', __dir__) -$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'jwt/version' - -Gem::Specification.new do |spec| - spec.name = 'jwt' - spec.version = JWT.gem_version - spec.authors = [ - 'Tim Rudat' - ] - spec.email = 'timrudat@gmail.com' - spec.summary = 'JSON Web Token implementation in Ruby' - spec.description = 'A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.' - spec.homepage = 'https://github.com/jwt/ruby-jwt' - spec.license = 'MIT' - spec.required_ruby_version = '>= 2.5' - spec.metadata = { - 'bug_tracker_uri' => 'https://github.com/jwt/ruby-jwt/issues', - 'changelog_uri' => "https://github.com/jwt/ruby-jwt/blob/v#{JWT.gem_version}/CHANGELOG.md", - 'rubygems_mfa_required' => 'true' - } - - spec.files = `git ls-files -z`.split("\x0").reject do |f| - f.match(%r{^(spec|gemfiles|coverage|bin)/}) || # Irrelevant folders - f.match(/^\.+/) || # Files and folders starting with . - f.match(/^(Appraisals|Gemfile|Rakefile)$/) # Irrelevant files - end - - spec.executables = [] - spec.require_paths = %w[lib] - - spec.add_dependency 'base64' - - spec.add_development_dependency 'appraisal' - spec.add_development_dependency 'bundler' - spec.add_development_dependency 'irb' - spec.add_development_dependency 'logger' - spec.add_development_dependency 'rake' - spec.add_development_dependency 'rspec' - spec.add_development_dependency 'rubocop' - spec.add_development_dependency 'simplecov' -end diff --git a/spec/fixtures/keys/ec256-private-v2.pem b/spec/fixtures/keys/ec256-private-v2.pem deleted file mode 100644 index 15793beed..000000000 --- a/spec/fixtures/keys/ec256-private-v2.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIFZpgytOAXPVreqGsHPdD9pojw30bnlqfUAqFZ3V3/qeoAoGCCqGSM49 -AwEHoUQDQgAE7JbAf3pWEEPje6NG+4dGOwIZnNwRFIe7DnQ4xFWKPrL5tVWlBh7N -DFhjGNhiyO+aQjbcx9uWV74ifq7i21Bemg== ------END EC PRIVATE KEY----- diff --git a/spec/fixtures/keys/ec256-private.pem b/spec/fixtures/keys/ec256-private.pem deleted file mode 100644 index e84b54c3c..000000000 --- a/spec/fixtures/keys/ec256-private.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIJmVse5uPfj6B4TcXrUAvf9/8pJh+KrKKYLNcmOnp/vPoAoGCCqGSM49 -AwEHoUQDQgAEAr+WbDE5VtIDGhtYMxvEc6cMsDBc/DX1wuhIMu8dQzOLSt0tpqK9 -MVfXbVfrKdayVFgoWzs8MilcYq0QIhKx/w== ------END EC PRIVATE KEY----- diff --git a/spec/fixtures/keys/ec256-public-v2.pem b/spec/fixtures/keys/ec256-public-v2.pem deleted file mode 100644 index a38495d5f..000000000 --- a/spec/fixtures/keys/ec256-public-v2.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7JbAf3pWEEPje6NG+4dGOwIZnNwR -FIe7DnQ4xFWKPrL5tVWlBh7NDFhjGNhiyO+aQjbcx9uWV74ifq7i21Bemg== ------END PUBLIC KEY----- diff --git a/spec/fixtures/keys/ec256-public.pem b/spec/fixtures/keys/ec256-public.pem deleted file mode 100644 index e6f22827d..000000000 --- a/spec/fixtures/keys/ec256-public.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAr+WbDE5VtIDGhtYMxvEc6cMsDBc -/DX1wuhIMu8dQzOLSt0tpqK9MVfXbVfrKdayVFgoWzs8MilcYq0QIhKx/w== ------END PUBLIC KEY----- diff --git a/spec/fixtures/keys/ec256-wrong-public.pem b/spec/fixtures/keys/ec256-wrong-public.pem deleted file mode 100644 index 511a0def9..000000000 --- a/spec/fixtures/keys/ec256-wrong-public.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEPmuXZT3jpJnEMVPOW6RMsmxeGLOCE1PN -6fwvUwOsxv7YnyoQ5/bpo64n+Jp4slSl1aUNoCBF2oz9bS0iyBo3jg== ------END PUBLIC KEY----- diff --git a/spec/fixtures/keys/ec256k-private.pem b/spec/fixtures/keys/ec256k-private.pem deleted file mode 100644 index 76bcd12c3..000000000 --- a/spec/fixtures/keys/ec256k-private.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHQCAQEEIMTine3s8tT+8bswDM4/z8o+wIYGb9PQPrw8x6Nu6QDdoAcGBSuBBAAK -oUQDQgAEy8wuv6+fXodLPLfhxm132y1R8m4dkng7tHe7N+sULV2Eth6AxEXQfd+E -4nuceR21UNCvQKqxiYwCzVwIKcHe/A== ------END EC PRIVATE KEY----- diff --git a/spec/fixtures/keys/ec256k-public.pem b/spec/fixtures/keys/ec256k-public.pem deleted file mode 100644 index 21eca675a..000000000 --- a/spec/fixtures/keys/ec256k-public.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEy8wuv6+fXodLPLfhxm132y1R8m4dkng7 -tHe7N+sULV2Eth6AxEXQfd+E4nuceR21UNCvQKqxiYwCzVwIKcHe/A== ------END PUBLIC KEY----- diff --git a/spec/fixtures/keys/ec384-private.pem b/spec/fixtures/keys/ec384-private.pem deleted file mode 100644 index 59cebf979..000000000 --- a/spec/fixtures/keys/ec384-private.pem +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MIGkAgEBBDDxOljqUKw9YNhkluSJIBAYO1YXcNtS+vckd5hpTZ5toxsOlwbmyrnU -Tn+D5Xma1m2gBwYFK4EEACKhZANiAASQwYTiRvXu1hMHceSosMs/8uf50sJI3jvK -kdSkvuRAPxSzhtrUvCQDnVsThFq4aOdZZY1qh2ErJGtzmrx+pEsJvJnvfOTG3NGU -KRalek+LQfVqAUSvDMKlxdkz2e67tso= ------END EC PRIVATE KEY----- diff --git a/spec/fixtures/keys/ec384-public.pem b/spec/fixtures/keys/ec384-public.pem deleted file mode 100644 index 6b7638d4a..000000000 --- a/spec/fixtures/keys/ec384-public.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PUBLIC KEY----- -MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEkMGE4kb17tYTB3HkqLDLP/Ln+dLCSN47 -ypHUpL7kQD8Us4ba1LwkA51bE4RauGjnWWWNaodhKyRrc5q8fqRLCbyZ73zkxtzR -lCkWpXpPi0H1agFErwzCpcXZM9nuu7bK ------END PUBLIC KEY----- diff --git a/spec/fixtures/keys/ec512-private.pem b/spec/fixtures/keys/ec512-private.pem deleted file mode 100644 index 753317a5c..000000000 --- a/spec/fixtures/keys/ec512-private.pem +++ /dev/null @@ -1,7 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MIHcAgEBBEIB0/+ffxEj7j62xvGaB5pvzk888e412ESO/EK/K0QlS9dSF8+Rj1rG -zqpRB8fvDnoe8xdmkW/W5GKzojMyv7YQYumgBwYFK4EEACOhgYkDgYYABAEw74Yw -aTbPY6TtWmxx6LJDzCX2nKWCPnKdZcEH9Ncu8g5RjRBRq2yacja3OoS6nA2YeDng -reBJxZr376P6Ns6XcQFWDA6K/MCTrEBCsPxXZNxd8KR9vMGWhgNtWRrcKzwJfQkr -suyehZkbbYyFnAWyARKHZuV7VUXmeEmRS/f93MPqVA== ------END EC PRIVATE KEY----- diff --git a/spec/fixtures/keys/ec512-public.pem b/spec/fixtures/keys/ec512-public.pem deleted file mode 100644 index a74a57b07..000000000 --- a/spec/fixtures/keys/ec512-public.pem +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBMO+GMGk2z2Ok7VpsceiyQ8wl9pyl -gj5ynWXBB/TXLvIOUY0QUatsmnI2tzqEupwNmHg54K3gScWa9++j+jbOl3EBVgwO -ivzAk6xAQrD8V2TcXfCkfbzBloYDbVka3Cs8CX0JK7LsnoWZG22MhZwFsgESh2bl -e1VF5nhJkUv3/dzD6lQ= ------END PUBLIC KEY----- diff --git a/spec/fixtures/keys/rsa-2048-private.pem b/spec/fixtures/keys/rsa-2048-private.pem deleted file mode 100644 index dab50df27..000000000 --- a/spec/fixtures/keys/rsa-2048-private.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA4GzZTLU48c4WbyvHi+QKrB71x+T0eq5hqDbQqnlYjhD1Ika7 -io1iplsdJWJuyxfYbUkb2Ol0fj4koZ/GS6lgCZr4+8UHbr1qf0Eu5HZSpszs2YxY -8U5RHnrpw67co7hlgAR9HbyNf5XIYgLV9ldHH/eazwnc3F/hgNsV0xjScVilejgo -cJ4zcsyymvW8t42lteM7bI867ZuJhGop/V+Y0HFyrMsPoQyLuCUpr6ulOfrkr7ZO -dhAIG8r1HcjOp/AUjM15vfXcbUZjkM/VloifX1YitU3upMGJ8/DpFGffMOImrn5r -6BT494V8rRyN2qvQoAkLJpqZ0avLxwiR2lgVQQIDAQABAoIBAEH0Ozgr2fxWEInD -V/VooypKPvjr9F1JejGxSkmPN9MocKIOH3dsbZ1uEXa3ItBUxan4XlK06SNgp+tH -xULfF/Y6sQlsse59hBq50Uoa69dRShn1AP6JgZVvkduMPBNxUYL5zrs6emsQXb9Q -DglDRQfEAJ7vyxSIqQDxYcyT8uSUF70dqFe+E9B2VE3D6ccHc98k41pJrAFAUFH1 -wwvDhfyYr7/Ultut9wzpZvU1meF3Vna3GOUHfxrG6wu1G+WIWHGjouzThsc1qiVI -BtMCJxuCt5fOXRbU4STbMqhB6sZHiOh6J/dZU6JwRYt+IS8FB6kCNFSEWZWQledJ -XqtYSQECgYEA9nmnFTRj3fTBq9zMXfCRujkSy6X2bOb39ftNXzHFuc+I6xmv/3Bs -P9tDdjueP/SnCb7i/9hXkpEIcxjrjiqgcvD2ym1hE4q+odMzRAXYMdnmzI34SVZE -U5hYJcYsXNKrTTleba7QgqdORmyJ9FwqLO40udvmrZMY223XDwgRkOkCgYEA6RkO -5wjjrWWp/G1YN3KXZTS1m2/eGrUThohXKAfAjbWWiouNLW2msXrxEWsPRL6xKiHu -X9cwZwzi3MstAgk+bphUGUVUkGKNDjWHJA25tDYjbPtkd6xbL4eCHsKpNL3HNYr9 -N0CIvgn7qjaHRBem0iK7T6keY4axaSVddEwYapkCgYEA13K5qaB1F4Smcpt8DTWH -vPe8xUUaZlFzOJLmLCsuwmB2N8Ppg2j7RspcaxJsH021YaB5ftjWm+ipMSr8ZPY/ -8JlPsNzxuYpTXtNmAbT2KYVm6THEch61dTk6/DIBf1YrpUJbl5by7vJeStL/uBmE -SGgksL5XIyzs0opuLdaIvFkCgYAyBLWE8AxjFfCvAQuwAj/ocLITo6KmWnrRIIqL -RXaVMgUWv7FQsTnW1cnK8g05tC2yG8vZ9wQk6Mf5lwOWb0NdWgSZ0528ydj41pWk -L+nMeN2LMjqxz2NVxJ8wWJcUgTCxFZ0WcRumo9/D+6V1ABpE9zz4cBLcSnfhVypB -nV6T6QKBgQCSZNCQ9HPxjAgYcsqc5sjNwuN1GHQZSav3Tye3k6zHENe1lsteT9K8 -xciGIuhybKZBvB4yImIIHCtnH+AS+mHAGqHarjNDMfvjOq0dMibPx4+bkIiHdBIH -Xz+j5kmntvFiUnzr0Z/Tcqo+r8FvyCo1YWgwqGP8XoFrswD7gy7cZw== ------END RSA PRIVATE KEY----- diff --git a/spec/fixtures/keys/rsa-2048-public.pem b/spec/fixtures/keys/rsa-2048-public.pem deleted file mode 100644 index c80a9523c..000000000 --- a/spec/fixtures/keys/rsa-2048-public.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4GzZTLU48c4WbyvHi+QK -rB71x+T0eq5hqDbQqnlYjhD1Ika7io1iplsdJWJuyxfYbUkb2Ol0fj4koZ/GS6lg -CZr4+8UHbr1qf0Eu5HZSpszs2YxY8U5RHnrpw67co7hlgAR9HbyNf5XIYgLV9ldH -H/eazwnc3F/hgNsV0xjScVilejgocJ4zcsyymvW8t42lteM7bI867ZuJhGop/V+Y -0HFyrMsPoQyLuCUpr6ulOfrkr7ZOdhAIG8r1HcjOp/AUjM15vfXcbUZjkM/Vloif -X1YitU3upMGJ8/DpFGffMOImrn5r6BT494V8rRyN2qvQoAkLJpqZ0avLxwiR2lgV -QQIDAQAB ------END PUBLIC KEY----- diff --git a/spec/fixtures/keys/rsa-2048-wrong-public.pem b/spec/fixtures/keys/rsa-2048-wrong-public.pem deleted file mode 100644 index 8d4960141..000000000 --- a/spec/fixtures/keys/rsa-2048-wrong-public.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzHAVGaW9j4l3/b4ngcjj -oIoIcnsQEWOMqErb5VhLZMGIq1gEO5qxPDAwooKsNotzcAOB3ZyLn7p5D+dmOrNU -YkYWgYITNGeSifrnVqQugd5Fh1L8K7zOGltUo2UtjbN4uJ56tzxBMZp2wejs2/Qu -0eu0xZK3To+YkDcWOk92rmNgmUSQC/kNyIOj+yBvOo3wTk6HvbhoIarCgJ6Lay1v -/hMLyQLzwRY/Qfty1FTIDyTv2dch47FsfkZ1KAL+MbUnHuCBPzGxRjXa8Iy9Z7YG -xrYasUt1b0um64bscxoIiCu8yLL8jlg01Rwrjr/MTwKRhwXlMp8B7HTonwtaG6ar -JwIDAQAB ------END PUBLIC KEY----- diff --git a/spec/integration/readme_examples_spec.rb b/spec/integration/readme_examples_spec.rb deleted file mode 100644 index ea9f779c5..000000000 --- a/spec/integration/readme_examples_spec.rb +++ /dev/null @@ -1,481 +0,0 @@ -# frozen_string_literal: true - -require 'logger' - -RSpec.describe 'README.md code test' do - context 'algorithm usage' do - let(:payload) { { data: 'test' } } - - it 'NONE' do - token = JWT.encode payload, nil, 'none' - decoded_token = JWT.decode token, nil, false - - expect(token).to eq 'eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.' - expect(decoded_token).to eq [ - { 'data' => 'test' }, - { 'alg' => 'none' } - ] - end - - it 'decodes with HMAC algorithm with secret key' do - token = JWT.encode payload, 'my$ecretK3y', 'HS256' - decoded_token = JWT.decode token, 'my$ecretK3y', false - - expect(token).to eq 'eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y' - expect(decoded_token).to eq [ - { 'data' => 'test' }, - { 'alg' => 'HS256' } - ] - end - - it 'decodes with HMAC algorithm without secret key' do - pending 'Different behaviour on OpenSSL 3.0 (https://github.com/openssl/openssl/issues/13089)' if JWT.openssl_3_hmac_empty_key_regression? - token = JWT.encode payload, nil, 'HS256' - decoded_token = JWT.decode token, nil, false - - expect(token).to eq 'eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pVzcY2dX8JNM3LzIYeP2B1e1Wcpt1K3TWVvIYSF4x-o' - expect(decoded_token).to eq [ - { 'data' => 'test' }, - { 'alg' => 'HS256' } - ] - end - - it 'RSA' do - rsa_private = OpenSSL::PKey::RSA.generate 2048 - rsa_public = rsa_private.public_key - - token = JWT.encode payload, rsa_private, 'RS256' - decoded_token = JWT.decode token, rsa_public, true, algorithm: 'RS256' - - expect(decoded_token).to eq [ - { 'data' => 'test' }, - { 'alg' => 'RS256' } - ] - end - - it 'ECDSA' do - ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1') - - token = JWT.encode payload, ecdsa_key, 'ES256' - decoded_token = JWT.decode token, ecdsa_key, true, algorithm: 'ES256' - - expect(decoded_token).to eq [ - { 'data' => 'test' }, - { 'alg' => 'ES256' } - ] - end - - if Gem::Version.new(OpenSSL::VERSION) >= Gem::Version.new('2.1') - it 'RSASSA-PSS' do - rsa_private = OpenSSL::PKey::RSA.generate 2048 - rsa_public = rsa_private.public_key - - token = JWT.encode payload, rsa_private, 'PS256' - decoded_token = JWT.decode token, rsa_public, true, algorithm: 'PS256' - - expect(decoded_token).to eq [ - { 'data' => 'test' }, - { 'alg' => 'PS256' } - ] - end - end - end - - context 'claims' do - let(:hmac_secret) { 'MyP4ssW0rD' } - - context 'exp' do - it 'without leeway' do - exp = Time.now.to_i + (4 * 3600) - exp_payload = { data: 'data', exp: exp } - - token = JWT.encode exp_payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, algorithm: 'HS256' - end.not_to raise_error - end - - it 'with leeway' do - exp = Time.now.to_i - 10 - leeway = 30 # seconds - - exp_payload = { data: 'data', exp: exp } - - token = JWT.encode exp_payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, leeway: leeway, algorithm: 'HS256' - end.not_to raise_error - end - end - - context 'nbf' do - it 'without leeway' do - nbf = Time.now.to_i - 3600 - nbf_payload = { data: 'data', nbf: nbf } - token = JWT.encode nbf_payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, algorithm: 'HS256' - end.not_to raise_error - end - - it 'with leeway' do - nbf = Time.now.to_i + 10 - leeway = 30 - nbf_payload = { data: 'data', nbf: nbf } - token = JWT.encode nbf_payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, leeway: leeway, algorithm: 'HS256' - end.not_to raise_error - end - end - - it 'iss' do - iss = 'My Awesome Company Inc. or https://my.awesome.website/' - iss_payload = { data: 'data', iss: iss } - - token = JWT.encode iss_payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, iss: iss, algorithm: 'HS256' - end.not_to raise_error - end - - context 'aud' do - it 'array' do - aud = %w[Young Old] - aud_payload = { data: 'data', aud: aud } - - token = JWT.encode aud_payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, aud: %w[Old Young], verify_aud: true, algorithm: 'HS256' - end.not_to raise_error - end - - it 'string' do - aud = 'Kids' - aud_payload = { data: 'data', aud: aud } - - token = JWT.encode aud_payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, aud: 'Kids', verify_aud: true, algorithm: 'HS256' - end.not_to raise_error - end - end - - it 'jti' do - iat = Time.now.to_i - hmac_secret = 'test' - jti_raw = [hmac_secret, iat].join(':').to_s - jti = Digest::MD5.hexdigest(jti_raw) - jti_payload = { data: 'data', iat: iat, jti: jti } - - token = JWT.encode jti_payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, verify_jti: true, algorithm: 'HS256' - end.not_to raise_error - end - - context 'iat' do - it 'without leeway' do - iat = Time.now.to_i - iat_payload = { data: 'data', iat: iat } - - token = JWT.encode iat_payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, verify_iat: true, algorithm: 'HS256' - end.not_to raise_error - end - - it 'with leeway' do - iat = Time.now.to_i - 7 - iat_payload = { data: 'data', iat: iat, leeway: 10 } - - token = JWT.encode iat_payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, verify_iat: true, algorithm: 'HS256' - end.not_to raise_error - end - end - - context 'custom header fields' do - it 'with custom field' do - payload = { data: 'test' } - - token = JWT.encode payload, nil, 'none', typ: 'JWT' - _, header = JWT.decode token, nil, false - - expect(header['typ']).to eq 'JWT' - end - end - - it 'sub' do - sub = 'Subject' - sub_payload = { data: 'data', sub: sub } - - token = JWT.encode sub_payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' } - end.not_to raise_error - - expect do - JWT.decode token, hmac_secret, true, { sub: 'sub', verify_sub: true, algorithm: 'HS256' } - end.to raise_error(JWT::InvalidSubError) - - expect do - JWT.decode token, hmac_secret, true, { 'sub' => 'sub', verify_sub: true, algorithm: 'HS256' } - end.not_to raise_error - end - - it 'required_claims' do - payload = { data: 'test' } - - token = JWT.encode payload, hmac_secret, 'HS256' - - expect do - JWT.decode token, hmac_secret, true, required_claims: ['exp'], algorithm: 'HS256' - end.to raise_error(JWT::MissingRequiredClaim) - - expect do - JWT.decode token, hmac_secret, true, required_claims: ['data'], algorithm: 'HS256' - end.not_to raise_error - end - - it 'find_key' do - issuers = %w[My_Awesome_Company1 My_Awesome_Company2] - iss_payload = { data: 'data', iss: issuers.first } - - secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' } - - token = JWT.encode iss_payload, hmac_secret, 'HS256' - - expect do - # Add iss to the validation to check if the token has been manipulated - JWT.decode(token, nil, true, { iss: issuers, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload| - secrets[payload['iss']] - end - end.not_to raise_error - end - - context 'The JWK based encode/decode routine' do - it 'works as expected' do - # ---------- ENCODE ---------- - optional_parameters = { kid: 'my-kid', use: 'sig', alg: 'RS512' } - jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters) - - # Encoding - payload = { data: 'data' } - token = JWT.encode(payload, jwk.signing_key, jwk[:alg], kid: jwk[:kid]) - - # JSON Web Key Set for advertising your signing keys - jwks_hash = JWT::JWK::Set.new(jwk).export - - # ---------- DECODE ---------- - jwks = JWT::JWK::Set.new(jwks_hash) - jwks.filter! { |key| key[:use] == 'sig' } # Signing keys only! - algorithms = jwks.map { |key| key[:alg] }.compact.uniq - JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks) - end - end - - context 'The JWKS loader example' do - let(:logger_output) { StringIO.new } - let(:logger) { Logger.new(logger_output) } - - it 'works as expected (legacy)' do - jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), 'optional-kid') - payload = { data: 'data' } - headers = { kid: jwk.kid } - - token = JWT.encode(payload, jwk.signing_key, 'RS512', headers) - - # The jwk loader would fetch the set of JWKs from a trusted source, - # to avoid malicious invalidations some kind of protection needs to be implemented. - # This example only allows cache invalidations every 5 minutes. - jwk_loader = lambda do |options| - if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300 - logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache") - @cached_keys = nil - end - @cached_keys ||= begin - @cache_last_update = Time.now.to_i - { keys: [jwk.export] } - end - end - - begin - JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwk_loader }) - rescue JWT::JWKError - # Handle problems with the provided JWKs - rescue JWT::DecodeError - # Handle other decode related issues e.g. no kid in header, no matching public key found etc. - end - - ## This is not in the example but verifies that the cache is invalidated after 5 minutes - jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), 'new-kid') - - headers = { kid: jwk.kid } - - token = JWT.encode(payload, jwk.signing_key, 'RS512', headers) - @cache_last_update = Time.now.to_i - 301 - - JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwk_loader }) - expect(logger_output.string.chomp).to match(/^I, .* : Invalidating JWK cache. new-kid not found from previous cache/) - - jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), 'yet-another-new-kid') - headers = { kid: jwk.kid } - token = JWT.encode(payload, jwk.signing_key, 'RS512', headers) - expect { JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwk_loader }) }.to raise_error(JWT::DecodeError, 'Could not find public key for kid yet-another-new-kid') - end - - it 'works as expected' do - jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), use: 'sig') - jwks_hash = JWT::JWK::Set.new(jwk) - payload = { data: 'data' } - headers = { kid: jwk.kid } - - token = JWT.encode(payload, jwk.signing_key, 'RS512', headers) - - jwks_loader = lambda do |options| - # The jwk loader would fetch the set of JWKs from a trusted source. - # 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. - # This example only allows cache invalidations every 5 minutes. - if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300 - logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache") - @cached_keys = nil - end - @cached_keys ||= begin - @cache_last_update = Time.now.to_i - # Replace with your own JWKS fetching routine - jwks = JWT::JWK::Set.new(jwks_hash) - jwks.select! { |key| key[:use] == 'sig' } # Signing Keys only - jwks - end - end - - begin - JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks_loader }) - rescue JWT::JWKError - # Handle problems with the provided JWKs - rescue JWT::DecodeError - # Handle other decode related issues e.g. no kid in header, no matching public key found etc. - end - end - end - - it 'JWK import and export' do - # Import a JWK Hash (showing an HMAC example) - _jwk = JWT::JWK.new({ kty: 'oct', k: 'my-secret', kid: 'my-kid' }) - - # Import an OpenSSL key - # You can optionally add descriptive parameters to the JWK - desc_params = { kid: 'my-kid', use: 'sig' } - jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), desc_params) - - # Export as JWK Hash (public key only by default) - _jwk_hash = jwk.export - _jwk_hash_with_private_key = jwk.export(include_private: true) - - # Export as OpenSSL key - _public_key = jwk.verify_key - _private_key = jwk.signing_key if jwk.private? - - # You can also import and export entire JSON Web Key Sets - jwks_hash = { keys: [{ kty: 'oct', k: 'my-secret', kid: 'my-kid' }] } - jwks = JWT::JWK::Set.new(jwks_hash) - _jwks_hash = jwks.export - end - - it 'JWK with thumbprint as kid via symbol' do - JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint - - jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048)) - - jwk_hash = jwk.export - - expect(jwk_hash[:kid].size).to eq(43) - end - - it 'JWK with thumbprint as kid via type' do - JWT.configuration.jwk.kid_generator = JWT::JWK::Thumbprint - - jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048)) - - jwk_hash = jwk.export - - expect(jwk_hash[:kid].size).to eq(43) - end - - it 'JWK with thumbprint given in the initializer (legacy)' do - jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), kid_generator: JWT::JWK::Thumbprint) - - jwk_hash = jwk.export - - expect(jwk_hash[:kid].size).to eq(43) - end - - it 'JWK with thumbprint given in the initializer' do - jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), nil, kid_generator: JWT::JWK::Thumbprint) - - jwk_hash = jwk.export - - expect(jwk_hash[:kid].size).to eq(43) - end - end - - context 'custom algorithm example' do - it 'allows a module to be used as algorithm on encode and decode' do - custom_hs512_alg = Module.new do - extend JWT::JWA::SigningAlgorithm - - def self.alg - 'HS512' - end - - def self.sign(data:, signing_key:) - OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), signing_key, data) - end - - def self.verify(data:, signature:, verification_key:) - sign(data: data, signing_key: verification_key) == signature - end - end - - token = JWT.encode({ 'pay' => 'load' }, 'secret', custom_hs512_alg) - _payload, header = JWT.decode(token, 'secret', true, algorithm: custom_hs512_alg) - expect(header).to include('alg' => 'HS512') - end - end - - context 'JWK to verify a signature' do - it 'allows to verify a signature with a JWK' do - payload = { exp: Time.now.to_i + 60, jti: '1234', sub: 'my-subject' } - header = { kid: 'hmac' } - - jwk_json = '{ - "kty": "oct", - "k": "c2VjcmV0", - "alg": "HS256", - "kid": "hmac" - }' - - jwk = JWT::JWK.import(JSON.parse(jwk_json)) - - token = JWT::Token.new(payload: payload, header: header) - token.sign!(key: jwk, algorithm: 'HS256') - - encoded_token = JWT::EncodedToken.new(token.jwt) - expect { encoded_token.verify!(signature: { algorithm: %w[HS256 HS512], key: jwk }) }.not_to raise_error - end - end -end diff --git a/spec/jwt/claims/audience_spec.rb b/spec/jwt/claims/audience_spec.rb deleted file mode 100644 index a7406ef0d..000000000 --- a/spec/jwt/claims/audience_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Claims::Audience do - let(:payload) { { 'nbf' => (Time.now.to_i + 5) } } - - describe '#verify!' do - let(:scalar_aud) { 'ruby-jwt-aud' } - let(:array_aud) { %w[ruby-jwt-aud test-aud ruby-ruby-ruby] } - - subject(:verify!) { described_class.new(expected_audience: expected_audience).verify!(context: SpecSupport::Token.new(payload: payload)) } - - context 'when the singular audience does not match' do - let(:expected_audience) { 'no-match' } - let(:payload) { { 'aud' => scalar_aud } } - - it 'raises JWT::InvalidAudError' do - expect do - subject - end.to raise_error JWT::InvalidAudError - end - end - - context 'when the payload has an array and none match the supplied value' do - let(:expected_audience) { 'no-match' } - let(:payload) { { 'aud' => array_aud } } - - it 'raises JWT::InvalidAudError' do - expect do - subject - end.to raise_error JWT::InvalidAudError - end - end - - context 'when single audience is required' do - let(:expected_audience) { scalar_aud } - let(:payload) { { 'aud' => scalar_aud } } - - it 'passes validation' do - subject - end - end - - context 'when any value in payload matches a single expected' do - let(:expected_audience) { array_aud.first } - let(:payload) { { 'aud' => array_aud } } - - it 'passes validation' do - subject - end - end - - context 'when an array with any value matching the one in the options' do - let(:expected_audience) { array_aud.first } - let(:payload) { { 'aud' => array_aud } } - - it 'passes validation' do - subject - end - end - - context 'when an array with any value matching all in the options' do - let(:expected_audience) { array_aud } - let(:payload) { { 'aud' => array_aud } } - - it 'passes validation' do - subject - end - end - - context 'when a singular audience payload matching any value in the options array' do - let(:expected_audience) { array_aud } - let(:payload) { { 'aud' => scalar_aud } } - - it 'passes validation' do - subject - end - end - end -end diff --git a/spec/jwt/claims/crit_spec.rb b/spec/jwt/claims/crit_spec.rb deleted file mode 100644 index 6b08c455c..000000000 --- a/spec/jwt/claims/crit_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Claims::Crit do - subject(:verify!) { described_class.new(expected_crits: expected_crits).verify!(context: SpecSupport::Token.new(header: header)) } - let(:expected_crits) { [] } - let(:header) { {} } - - context 'when header is missing' do - it 'raises JWT::InvalidCritError' do - expect { verify! }.to raise_error(JWT::InvalidCritError, 'Crit header missing') - end - end - - context 'when header is not an array' do - let(:header) { { 'crit' => 'not_an_array' } } - - it 'raises JWT::InvalidCritError' do - expect { verify! }.to raise_error(JWT::InvalidCritError, 'Crit header should be an array') - end - end - - context 'when header is an array and not containing the expected value' do - let(:header) { { 'crit' => %w[crit1] } } - let(:expected_crits) { %w[crit2] } - it 'raises an InvalidCritError' do - expect { verify! }.to raise_error(JWT::InvalidCritError, 'Crit header missing expected values: crit2') - end - end - - context 'when header is an array containing exactly the expected values' do - let(:header) { { 'crit' => %w[crit1 crit2] } } - let(:expected_crits) { %w[crit1 crit2] } - it 'does not raise an error' do - expect(verify!).to eq(nil) - end - end - - context 'when header is an array containing at least the expected values' do - let(:header) { { 'crit' => %w[crit1 crit2 crit3] } } - let(:expected_crits) { %w[crit1 crit2] } - it 'does not raise an error' do - expect(verify!).to eq(nil) - end - end -end diff --git a/spec/jwt/claims/expiration_spec.rb b/spec/jwt/claims/expiration_spec.rb deleted file mode 100644 index bbd018769..000000000 --- a/spec/jwt/claims/expiration_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Claims::Expiration do - let(:payload) { { 'exp' => (Time.now.to_i + 5) } } - let(:leeway) { 0 } - - subject(:verify!) { described_class.new(leeway: leeway).verify!(context: SpecSupport::Token.new(payload: payload)) } - - context 'when token is expired' do - let(:payload) { { 'exp' => (Time.now.to_i - 5) } } - - it 'must raise JWT::ExpiredSignature when the token has expired' do - expect { verify! }.to(raise_error(JWT::ExpiredSignature)) - end - end - - context 'when token is expired but some leeway is defined' do - let(:payload) { { 'exp' => (Time.now.to_i - 5) } } - let(:leeway) { 10 } - - it 'passes validation' do - verify! - end - end - - context 'when token exp is set to current time' do - let(:payload) { { 'exp' => Time.now.to_i } } - - it 'fails validation' do - expect { verify! }.to(raise_error(JWT::ExpiredSignature)) - end - end - - context 'when token is not a Hash' do - let(:payload) { 'beautyexperts_nbf_iat' } - it 'passes validation' do - verify! - end - end -end diff --git a/spec/jwt/claims/issued_at_spec.rb b/spec/jwt/claims/issued_at_spec.rb deleted file mode 100644 index e5d3f8222..000000000 --- a/spec/jwt/claims/issued_at_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Claims::IssuedAt do - let(:payload) { { 'iat' => Time.now.to_f } } - - subject(:verify!) { described_class.new.verify!(context: SpecSupport::Token.new(payload: payload)) } - - context 'when iat is now' do - it 'passes validation' do - verify! - end - end - - context 'when iat is now as a integer' do - let(:payload) { { 'iat' => Time.now.to_i } } - - it 'passes validation' do - verify! - end - end - context 'when iat is not a number' do - let(:payload) { { 'iat' => 'not_a_number' } } - - it 'fails validation' do - expect { verify! }.to raise_error(JWT::InvalidIatError) - end - end - - context 'when iat is in the future' do - let(:payload) { { 'iat' => Time.now.to_f + 120.0 } } - - it 'fails validation' do - expect { verify! }.to raise_error(JWT::InvalidIatError) - end - end - - context 'when payload is a string containing iat' do - let(:payload) { 'beautyexperts_nbf_iat' } - - it 'passes validation' do - verify! - end - end -end diff --git a/spec/jwt/claims/issuer_spec.rb b/spec/jwt/claims/issuer_spec.rb deleted file mode 100644 index a97e80bc6..000000000 --- a/spec/jwt/claims/issuer_spec.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Claims::Issuer do - let(:issuer) { 'ruby-jwt-gem' } - let(:payload) { { 'iss' => issuer } } - let(:expected_issuers) { 'ruby-jwt-gem' } - - subject(:verify!) { described_class.new(issuers: expected_issuers).verify!(context: SpecSupport::Token.new(payload: payload)) } - - context 'when expected issuer is a string that matches the payload' do - it 'passes validation' do - verify! - end - end - - context 'when expected issuer is a string that does not match the payload' do - let(:issuer) { 'mismatched-issuer' } - it 'raises JWT::InvalidIssuerError' do - expect { verify! }.to raise_error(JWT::InvalidIssuerError, 'Invalid issuer. Expected ["ruby-jwt-gem"], received mismatched-issuer') - end - end - - context 'when payload does not contain any issuer' do - let(:payload) { {} } - it 'raises JWT::InvalidIssuerError' do - expect { verify! }.to raise_error(JWT::InvalidIssuerError, 'Invalid issuer. Expected ["ruby-jwt-gem"], received ') - end - end - - context 'when expected issuer is an array that matches the payload' do - let(:expected_issuers) { ['first', issuer, 'third'] } - it 'passes validation' do - verify! - end - end - - context 'when expected issuer is an array that does not match the payload' do - let(:expected_issuers) { %w[first second] } - it 'raises JWT::InvalidIssuerError' do - expect { verify! }.to raise_error(JWT::InvalidIssuerError, 'Invalid issuer. Expected ["first", "second"], received ruby-jwt-gem') - end - end - - context 'when expected issuer is an array and payload does not have any issuer' do - let(:payload) { {} } - let(:expected_issuers) { %w[first second] } - it 'raises JWT::InvalidIssuerError' do - expect { verify! }.to raise_error(JWT::InvalidIssuerError, 'Invalid issuer. Expected ["first", "second"], received ') - end - end - - context 'when issuer is given as a RegExp' do - let(:issuer) { 'ruby-jwt-gem' } - let(:expected_issuers) { /\A(first|#{issuer}|third)\z/ } - it 'passes validation' do - verify! - end - end - - context 'when issuer is given as a RegExp and does not match the payload' do - let(:issuer) { 'mismatched-issuer' } - let(:expected_issuers) { /\A(first|second)\z/ } - it 'raises JWT::InvalidIssuerError' do - expect { verify! }.to raise_error(JWT::InvalidIssuerError, 'Invalid issuer. Expected [/\A(first|second)\z/], received mismatched-issuer') - end - end - - context 'when issuer is given as a RegExp and payload does not have any issuer' do - let(:payload) { {} } - let(:expected_issuers) { /\A(first|second)\z/ } - it 'raises JWT::InvalidIssuerError' do - expect { verify! }.to raise_error(JWT::InvalidIssuerError, 'Invalid issuer. Expected [/\A(first|second)\z/], received ') - end - end - - context 'when issuer is given as a Proc' do - let(:issuer) { 'ruby-jwt-gem' } - let(:expected_issuers) { ->(iss) { iss.start_with?('ruby') } } - it 'passes validation' do - verify! - end - end - - context 'when issuer is given as a Proc and does not match the payload' do - let(:issuer) { 'mismatched-issuer' } - let(:expected_issuers) { ->(iss) { iss.start_with?('ruby') } } - it 'raises JWT::InvalidIssuerError' do - expect { verify! }.to raise_error(JWT::InvalidIssuerError, /received mismatched-issuer/) - end - end - - context 'when issuer is given as a Proc and payload does not have any issuer' do - let(:payload) { {} } - let(:expected_issuers) { ->(iss) { iss&.start_with?('ruby') } } - it 'raises JWT::InvalidIssuerError' do - expect { verify! }.to raise_error(JWT::InvalidIssuerError, /received /) - end - end - - context 'when issuer is given as a Method instance' do - def issuer_start_with_ruby?(issuer) - issuer&.start_with?('ruby') - end - - let(:issuer) { 'ruby-jwt-gem' } - let(:expected_issuers) { method(:issuer_start_with_ruby?) } - - it 'passes validation' do - verify! - end - end -end diff --git a/spec/jwt/claims/jwt_id_spec.rb b/spec/jwt/claims/jwt_id_spec.rb deleted file mode 100644 index 81a8a44df..000000000 --- a/spec/jwt/claims/jwt_id_spec.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Claims::JwtId do - let(:jti) { 'some-random-uuid-or-whatever' } - let(:payload) { { 'jti' => jti } } - let(:validator) { nil } - - subject(:verify!) { described_class.new(validator: validator).verify!(context: SpecSupport::Token.new(payload: payload)) } - context 'when payload contains a jti' do - it 'passes validation' do - verify! - end - end - - context 'when payload is missing a jti' do - let(:payload) { {} } - it 'raises JWT::InvalidJtiError' do - expect { verify! }.to raise_error(JWT::InvalidJtiError, 'Missing jti') - end - end - - context 'when payload contains a jti that is an empty string' do - let(:jti) { '' } - it 'raises JWT::InvalidJtiError' do - expect { verify! }.to raise_error(JWT::InvalidJtiError, 'Missing jti') - end - end - - context 'when payload contains a jti that is a blank string' do - let(:jti) { ' ' } - it 'raises JWT::InvalidJtiError' do - expect { verify! }.to raise_error(JWT::InvalidJtiError, 'Missing jti') - end - end - - context 'when jti validator is a proc returning false' do - let(:validator) { ->(_jti) { false } } - it 'raises JWT::InvalidJtiError' do - expect { verify! }.to raise_error(JWT::InvalidJtiError, 'Invalid jti') - end - end - - context 'when jti validator is a proc returning true' do - let(:validator) { ->(_jti) { true } } - it 'passes validation' do - verify! - end - end - - context 'when jti validator has 2 args' do - let(:validator) { ->(_jti, _pl) { true } } - it 'passes validation' do - verify! - end - end - - context 'when jti validator has 2 args' do - it 'the second arg is the payload' do - described_class.new(validator: ->(_jti, pl) { expect(pl).to eq(payload) }).verify!(context: SpecSupport::Token.new(payload: payload)) - end - end -end diff --git a/spec/jwt/claims/not_before_spec.rb b/spec/jwt/claims/not_before_spec.rb deleted file mode 100644 index 29677401e..000000000 --- a/spec/jwt/claims/not_before_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Claims::NotBefore do - let(:payload) { { 'nbf' => (Time.now.to_i + 5) } } - - describe '#verify!' do - context 'when nbf is in the future' do - it 'raises JWT::ImmatureSignature' do - expect { described_class.new(leeway: 0).verify!(context: SpecSupport::Token.new(payload: payload)) }.to raise_error JWT::ImmatureSignature - end - end - - context 'when nbf is in the past' do - let(:payload) { { 'nbf' => (Time.now.to_i - 5) } } - - it 'does not raise error' do - expect { described_class.new(leeway: 0).verify!(context: SpecSupport::Token.new(payload: payload)) }.not_to raise_error - end - end - - context 'when leeway is given' do - it 'does not raise error' do - expect { described_class.new(leeway: 10).verify!(context: SpecSupport::Token.new(payload: payload)) }.not_to raise_error - end - end - end -end diff --git a/spec/jwt/claims/numeric_spec.rb b/spec/jwt/claims/numeric_spec.rb deleted file mode 100644 index e2f254670..000000000 --- a/spec/jwt/claims/numeric_spec.rb +++ /dev/null @@ -1,94 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Claims::Numeric do - shared_examples_for 'a NumericDate claim' do |claim| - context "when #{claim} payload is an integer" do - let(:claims) { { claim => 12_345 } } - - it 'does not raise error' do - expect { subject }.not_to raise_error - end - - context 'and key is a string' do - let(:claims) { { claim.to_s => 43.32 } } - - it 'does not raise error' do - expect { subject }.not_to raise_error - end - end - end - - context "when #{claim} payload is a float" do - let(:claims) { { claim => 43.32 } } - - it 'does not raise error' do - expect { subject }.not_to raise_error - end - end - - context "when #{claim} payload is a string" do - let(:claims) { { claim => '1' } } - - it 'raises error' do - expect { subject }.to raise_error JWT::InvalidPayload - end - - context 'and key is a string' do - let(:claims) { { claim.to_s => '1' } } - - it 'raises error' do - expect { subject }.to raise_error JWT::InvalidPayload - end - end - end - - context "when #{claim} payload is a Time object" do - let(:claims) { { claim => Time.now } } - - it 'raises error' do - expect { subject }.to raise_error JWT::InvalidPayload - end - end - - context "when #{claim} payload is a string" do - let(:claims) { { claim => '1' } } - - it 'raises error' do - expect { subject }.to raise_error JWT::InvalidPayload - end - end - end - - let(:validator) { described_class.new } - - describe '#verify!' do - subject { validator.verify!(context: JWT::Claims::VerificationContext.new(payload: claims)) } - context 'exp claim' do - it_should_behave_like 'a NumericDate claim', :exp - end - - context 'iat claim' do - it_should_behave_like 'a NumericDate claim', :iat - end - - context 'nbf claim' do - it_should_behave_like 'a NumericDate claim', :nbf - end - end - - describe 'use via ::JWT::Claims.verify_payload!' do - subject { JWT::Claims.verify_payload!(claims, :numeric) } - - context 'exp claim' do - it_should_behave_like 'a NumericDate claim', :exp - end - - context 'iat claim' do - it_should_behave_like 'a NumericDate claim', :iat - end - - context 'nbf claim' do - it_should_behave_like 'a NumericDate claim', :nbf - end - end -end diff --git a/spec/jwt/claims/required_spec.rb b/spec/jwt/claims/required_spec.rb deleted file mode 100644 index e2c5d7a41..000000000 --- a/spec/jwt/claims/required_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Claims::Required do - let(:payload) { { 'data' => 'value' } } - - subject(:verify!) { described_class.new(required_claims: required_claims).verify!(context: SpecSupport::Token.new(payload: payload)) } - - context 'when payload is missing the required claim' do - let(:required_claims) { ['exp'] } - it 'raises JWT::MissingRequiredClaim' do - expect { verify! }.to raise_error JWT::MissingRequiredClaim, 'Missing required claim exp' - end - end - - context 'when payload has the required claims' do - let(:payload) { { 'exp' => 'exp', 'custom_claim' => true } } - let(:required_claims) { %w[exp custom_claim] } - it 'passes validation' do - verify! - end - end -end diff --git a/spec/jwt/claims/verifier_spec.rb b/spec/jwt/claims/verifier_spec.rb deleted file mode 100644 index 079eb48af..000000000 --- a/spec/jwt/claims/verifier_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Claims::Verifier do - describe '.verify!' do - context 'when all claims are given' do - let(:options) do - [ - :exp, - :nbf, - { iss: 'issuer' }, - :iat, - :jti, - { aud: 'aud' }, - :sub, - :crit, - { required: [] }, - :numeric - ] - end - - it 'verifies all claims' do - token = SpecSupport::Token.new(payload: { 'iss' => 'issuer', 'jti' => 1, 'aud' => 'aud' }, header: { 'crit' => [] }) - expect(described_class.verify!(token, *options)).to eq(nil) - end - end - end -end diff --git a/spec/jwt/claims_spec.rb b/spec/jwt/claims_spec.rb deleted file mode 100644 index f6c96c9e2..000000000 --- a/spec/jwt/claims_spec.rb +++ /dev/null @@ -1,121 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Claims do - let(:payload) { { 'pay' => 'load' } } - describe '.verify_payload!' do - context 'when required_claims is passed' do - it 'raises error' do - expect { described_class.verify_payload!(payload, required: ['exp']) }.to raise_error(JWT::MissingRequiredClaim, 'Missing required claim exp') - end - end - - context 'exp claim' do - let(:payload) { { 'exp' => Time.now.to_i - 10, 'pay' => 'load' } } - - it 'verifies the exp' do - described_class.verify_payload!(payload, required: ['exp']) - expect { described_class.verify_payload!(payload, exp: {}) }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - described_class.verify_payload!(payload, exp: { leeway: 1000 }) - end - - context 'when claims given as symbol' do - it 'validates the claim' do - expect { described_class.verify_payload!(payload, :exp) }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - end - end - - context 'when claims given as a list of symbols' do - it 'validates the claim' do - expect { described_class.verify_payload!(payload, :exp, :nbf) }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - end - end - - context 'when claims given as a list of symbols and hashes' do - it 'validates the claim' do - expect { described_class.verify_payload!(payload, { exp: { leeway: 1000 }, nbf: {} }, :exp, :nbf) }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - end - end - end - end - - describe '.valid_payload?' do - context 'exp claim' do - let(:payload) { { 'exp' => Time.now.to_i - 10, 'pay' => 'load' } } - - context 'when claim is valid' do - it 'returns true' do - expect(described_class.valid_payload?(payload, exp: { leeway: 1000 })).to be(true) - end - end - - context 'when claim is invalid' do - it 'returns false' do - expect(described_class.valid_payload?(payload, :exp)).to be(false) - end - end - end - - context 'various types of params' do - context 'when payload is missing most of the claims' do - it 'raises an error' do - expect do - described_class.verify_payload!(payload, - :nbf, - iss: ['www.host.com', 'https://other.host.com'].freeze, - aud: 'aud', - exp: { leeway: 10 }) - end.to raise_error(JWT::InvalidIssuerError) - end - end - - context 'when payload has everything that is expected of it' do - let(:payload) { { 'iss' => 'www.host.com', 'aud' => 'audience', 'exp' => Time.now.to_i - 10, 'pay' => 'load' } } - - it 'does not raise' do - expect do - described_class.verify_payload!(payload, - :nbf, - iss: ['www.host.com', 'https://other.host.com'].freeze, - aud: 'audience', - exp: { leeway: 11 }) - end.not_to raise_error - end - end - end - end - - describe '.payload_errors' do - context 'exp claim' do - let(:payload) { { 'exp' => Time.now.to_i - 10, 'pay' => 'load' } } - - context 'when claim is valid' do - it 'returns empty array' do - expect(described_class.payload_errors(payload, exp: { leeway: 1000 })).to be_empty - end - end - - context 'when claim is invalid' do - it 'returns array with error objects' do - expect(described_class.payload_errors(payload, :exp).map(&:message)).to eq(['Signature has expired']) - end - end - end - - context 'various types of params' do - let(:payload) { { 'exp' => Time.now.to_i - 10, 'pay' => 'load' } } - - context 'when payload is most of the claims' do - it 'raises an error' do - messages = described_class.payload_errors(payload, - :nbf, - iss: ['www.host.com', 'https://other.host.com'].freeze, - aud: 'aud', - exp: { leeway: 10 }).map(&:message) - expect(messages).to eq(['Invalid issuer. Expected ["www.host.com", "https://other.host.com"], received ', - 'Invalid audience. Expected aud, received ', - 'Signature has expired']) - end - end - end - end -end diff --git a/spec/jwt/concurrent_encode_decode_spec.rb b/spec/jwt/concurrent_encode_decode_spec.rb deleted file mode 100644 index 07809aa1e..000000000 --- a/spec/jwt/concurrent_encode_decode_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWA::Ecdsa do - context 'used across threads for encoding and decoding' do - it 'successfully encodes, decodes, and verifies' do - threads = 10.times.map do - Thread.new do - public_key_pem = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcKuFOqoNEN+TXylz4MVAWREa9yA8\npOF9QgGchnAy6Ad4P7yCpk+R3wCGTDLfNboYqUmbK5Hd9uHszf+EMTi22g==\n-----END PUBLIC KEY-----\n" - private_key_pem = "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiF/iNuQem/yQyd16\nc9shf2Y9vMycOU7g6W6LTmkyj1ehRANCAARwq4U6qg0Q35NfKXPgxUBZERr3IDyk\n4X1CAZyGcDLoB3g/vIKmT5HfAIZMMt81uhipSZsrkd324ezN/4QxOLba\n-----END PRIVATE KEY-----\n" - full_pem = private_key_pem + public_key_pem - curve = OpenSSL::PKey.read(full_pem) - public_key = OpenSSL::PKey::EC.new(public_key_pem) - - 10.times do - input_payload = { 'aud' => 'https://fcm.googleapis.com', 'exp' => (Time.now.to_i + 600), 'sub' => 'mailto:example@example.com' } - input_header = { 'typ' => 'JWT', 'alg' => 'ES256' } - token = JWT.encode(input_payload, curve, 'ES256', input_header) - - output_payload, output_header = JWT.decode(token, public_key, true, { algorithm: 'ES256', verify_expiration: true }) - expect(output_payload).to eq input_payload - expect(output_header).to eq input_header - end - end - end - - threads.each(&:join) - end - end -end diff --git a/spec/jwt/configuration/jwk_configuration_spec.rb b/spec/jwt/configuration/jwk_configuration_spec.rb deleted file mode 100644 index a0ed7220f..000000000 --- a/spec/jwt/configuration/jwk_configuration_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Configuration::JwkConfiguration do - describe '.kid_generator_type=' do - context 'when invalid value is passed' do - it 'raises ArgumentError' do - expect { subject.kid_generator_type = :foo }.to raise_error(ArgumentError, 'foo is not a valid kid generator type.') - end - end - - context 'when valid value is passed' do - it 'sets the generator matching the value' do - subject.kid_generator_type = :rfc7638_thumbprint - expect(subject.kid_generator).to eq(JWT::JWK::Thumbprint) - end - end - end -end diff --git a/spec/jwt/configuration_spec.rb b/spec/jwt/configuration_spec.rb deleted file mode 100644 index 39d09e217..000000000 --- a/spec/jwt/configuration_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT do - describe 'JWT.configure' do - it 'yields the configuration' do - expect { |b| described_class.configure(&b) }.to yield_with_args(described_class.configuration) - end - - it 'allows configuration to be changed via the block' do - expect(described_class.configuration.decode.verify_expiration).to eq(true) - - described_class.configure do |config| - config.decode.verify_expiration = false - end - - expect(described_class.configuration.decode.verify_expiration).to eq(false) - end - end -end diff --git a/spec/jwt/encoded_token_spec.rb b/spec/jwt/encoded_token_spec.rb deleted file mode 100644 index ce38a231c..000000000 --- a/spec/jwt/encoded_token_spec.rb +++ /dev/null @@ -1,478 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::EncodedToken do - let(:payload) { { 'pay' => 'load' } } - let(:header) { {} } - let(:encoded_token) { JWT::Token.new(payload: payload, header: header).tap { |t| t.sign!(algorithm: 'HS256', key: 'secret') }.jwt } - let(:detached_payload_token) do - JWT::Token.new(payload: payload).tap do |t| - t.detach_payload! - t.sign!(algorithm: 'HS256', key: 'secret') - end - end - - subject(:token) { described_class.new(encoded_token) } - - describe '#unverified_payload' do - it { expect(token.unverified_payload).to eq(payload) } - - context 'when payload is detached' do - let(:encoded_token) { detached_payload_token.jwt } - - context 'when payload provided in separate' do - before { token.encoded_payload = detached_payload_token.encoded_payload } - it { expect(token.unverified_payload).to eq(payload) } - end - - context 'when payload is not provided' do - it 'raises decode error' do - expect { token.unverified_payload }.to raise_error(JWT::DecodeError, 'Encoded payload is empty') - end - end - end - - context 'when payload is not encoded and the b64 crit is enabled' do - subject(:token) { described_class.new(encoded_token) } - let(:encoded_token) { 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..signature' } - before { token.encoded_payload = '{"foo": "bar"}' } - - it 'handles the payload encoding' do - expect(token.unverified_payload).to eq({ 'foo' => 'bar' }) - end - end - - context 'when token is the empty string' do - let(:encoded_token) { '' } - - it 'raises decode error' do - expect { token.unverified_payload }.to raise_error(JWT::DecodeError, 'Invalid segment encoding') - end - end - end - - describe '#payload' do - context 'when token is verified using #valid?' do - before do - token.valid?(signature: { algorithm: 'HS256', key: 'secret' }) - end - - it { expect(token.payload).to eq(payload) } - end - - context 'when token is verified using #verify_signature! and #verify_claims!' do - before do - token.verify_signature!(algorithm: 'HS256', key: 'secret') - token.verify_claims! - end - - it { expect(token.payload).to eq(payload) } - end - - context 'when token is checked using #valid_signature? and #valid_claims?' do - before do - token.valid_signature?(algorithm: 'HS256', key: 'secret') - token.valid_claims? - end - - it { expect(token.payload).to eq(payload) } - end - - context 'when token is verified using #verify_signature!' do - before { token.verify_signature!(algorithm: 'HS256', key: 'secret') } - - it 'raises an error' do - expect { token.payload }.to raise_error(JWT::DecodeError, 'Verify the token claims before accessing the payload') - end - end - - context 'when token is verified using #valid_signature? but is not valid' do - before { token.valid_signature?(algorithm: 'HS256', key: 'wrong') } - - it 'raises an error' do - expect { token.payload }.to raise_error(JWT::DecodeError, 'Verify the token signature before accessing the payload') - end - end - - context 'when token is not verified' do - it 'raises an error' do - expect { token.payload }.to raise_error(JWT::DecodeError, 'Verify the token signature before accessing the payload') - end - end - end - - describe '#header' do - it { expect(token.header).to eq({ 'alg' => 'HS256' }) } - - context 'when token is the empty string' do - let(:encoded_token) { '' } - - it 'raises decode error' do - expect { token.header }.to raise_error(JWT::DecodeError, 'Invalid segment encoding') - end - end - end - - describe '#signature' do - it { expect(token.signature).to be_a(String) } - end - - describe '#signing_input' do - it { expect(token.signing_input).to eq('eyJhbGciOiJIUzI1NiJ9.eyJwYXkiOiJsb2FkIn0') } - end - - describe '#verify_signature!' do - context 'when key is valid' do - it 'does not raise' do - expect(token.verify_signature!(algorithm: 'HS256', key: 'secret')).to eq(nil) - end - end - - context 'when key is invalid' do - it 'raises an error' do - expect { token.verify_signature!(algorithm: 'HS256', key: 'wrong') }.to raise_error(JWT::VerificationError, 'Signature verification failed') - end - end - - context 'when key is an array with one valid entry' do - it 'does not raise' do - expect(token.verify_signature!(algorithm: 'HS256', key: %w[wrong secret])).to eq(nil) - end - end - - context 'when algorithm is an empty array' do - it 'raises an error' do - expect { token.verify_signature!(key: 'secret', algorithm: []) }.to raise_error(JWT::VerificationError, 'No algorithm provided') - end - end - - context 'when algorithm is not given' do - it 'raises an error' do - expect { token.verify_signature!(key: 'secret') }.to raise_error(ArgumentError, /missing keyword/) - end - end - - context 'when header has invalid alg value' do - let(:header) { { 'alg' => 'HS123' } } - - it 'does not raise' do - expect(token.header).to eq(header) - expect(token.verify_signature!(algorithm: 'HS256', key: 'secret')).to eq(nil) - end - end - - context 'when payload is detached' do - let(:encoded_token) { detached_payload_token.jwt } - - context 'when payload provided in separate' do - before { token.encoded_payload = detached_payload_token.encoded_payload } - it 'does not raise' do - expect(token.verify_signature!(algorithm: 'HS256', key: 'secret')).to eq(nil) - end - end - - context 'when payload is not provided' do - it 'raises VerificationError' do - expect { token.verify_signature!(algorithm: 'HS256', key: 'secret') }.to raise_error(JWT::VerificationError, 'Signature verification failed') - end - end - end - - context 'when key_finder is given' do - it 'uses key provided by keyfinder' do - expect(token.verify_signature!(algorithm: 'HS256', key_finder: ->(_token) { 'secret' })).to eq(nil) - end - - it 'can utilize an array provided by keyfinder' do - expect(token.verify_signature!(algorithm: 'HS256', key_finder: ->(_token) { %w[wrong secret] })).to eq(nil) - end - end - - context 'when neither key or key_finder is given' do - it 'raises an ArgumentError' do - expect { token.verify_signature!(algorithm: 'HS256') }.to raise_error(ArgumentError, 'Provide either key or key_finder, not both or neither') - end - end - - context 'when both key or key_finder is given' do - it 'raises an ArgumentError' do - expect { token.verify_signature!(algorithm: 'HS256', key: 'key', key_finder: 'finder') }.to raise_error(ArgumentError, 'Provide either key or key_finder, not both or neither') - end - end - - context 'when payload is not encoded' do - let(:encoded_token) { 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY' } - before { token.encoded_payload = '$.02' } - - let(:key) { Base64.urlsafe_decode64('AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow') } - - it 'does not raise' do - expect(token.verify_signature!(algorithm: 'HS256', key: key)).to eq(nil) - end - end - - context 'when JWT::KeyFinder is used as a key_finder' do - let(:jwk) { JWT::JWK.new(test_pkey('rsa-2048-private.pem')) } - let(:encoded_token) do - JWT::Token.new(payload: payload, header: { kid: jwk.kid }) - .tap { |t| t.sign!(algorithm: 'RS256', key: jwk.signing_key) } - .jwt - end - - it 'uses the keys provided by the JWK key finder' do - key_finder = JWT::JWK::KeyFinder.new(jwks: JWT::JWK::Set.new(jwk)) - expect(token.verify_signature!(algorithm: 'RS256', key_finder: key_finder)).to eq(nil) - end - end - - context 'when RSA JWK is given as a key' do - let(:jwk) { JWT::JWK.new(test_pkey('rsa-2048-private.pem'), alg: 'RS256') } - let(:encoded_token) do - JWT::Token.new(payload: payload) - .tap { |t| t.sign!(algorithm: 'RS256', key: jwk.signing_key) } - .jwt - end - - context 'with empty algorithm array provided' do - it 'uses the JWK for verification' do - expect(token.verify_signature!(key: jwk, algorithm: [])).to eq(nil) - end - end - - context 'with algorithms supported by key provided' do - it 'uses the JWK for verification' do - expect(token.verify_signature!(algorithm: %w[RS256 RS512], key: jwk)).to eq(nil) - end - end - - context 'with algorithms not supported by key provided' do - it 'raises JWT::VerificationError' do - expect { token.verify_signature!(algorithm: %w[RS384 RS512], key: jwk) }.to raise_error(JWT::VerificationError, 'Provided JWKs do not support one of the specified algorithms: RS384, RS512') - end - end - end - end - - describe '#verify_claims!' do - context 'when required_claims is passed' do - it 'raises error' do - expect { token.verify_claims!(required: ['exp']) }.to raise_error(JWT::MissingRequiredClaim, 'Missing required claim exp') - end - end - - context 'exp claim' do - let(:payload) { { 'exp' => Time.now.to_i - 10, 'pay' => 'load' } } - - it 'verifies the exp' do - token.verify_claims!(required: ['exp']) - expect { token.verify_claims!(exp: {}) }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - token.verify_claims!(exp: { leeway: 1000 }) - end - - context 'when no claims are provided' do - it 'raises ExpiredSignature error' do - expect { token.verify_claims! }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - end - end - - context 'when claim validation skips verifying the exp claim' do - it 'does not raise' do - expect { token.verify_claims!({}) }.not_to raise_error - end - end - - context 'when claims given as symbol' do - it 'validates the claim' do - expect { token.verify_claims!(:exp) }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - end - end - - context 'when claims given as a list of symbols' do - it 'validates the claim' do - expect { token.verify_claims!(:exp, :nbf) }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - end - end - - context 'when claims given as a list of symbols and hashes' do - it 'validates the claim' do - expect { token.verify_claims!({ exp: { leeway: 1000 }, nbf: {} }, :exp, :nbf) }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - end - end - - context 'when payload is detached' do - let(:encoded_token) { detached_payload_token.jwt } - context 'when payload provided in separate' do - before { token.encoded_payload = detached_payload_token.encoded_payload } - it 'raises claim verification error' do - expect { token.verify_claims!(:exp, :nbf) }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - end - end - context 'when payload is not provided' do - it 'raises decode error' do - expect { token.verify_claims!(:exp, :nbf) }.to raise_error(JWT::DecodeError, 'Encoded payload is empty') - end - end - end - end - - context 'when header contains crits header' do - let(:header) { { crit: ['b64'] } } - - context 'when expected crits are missing' do - it 'raises an error' do - expect { token.verify_claims!(crit: ['other']) }.to raise_error(JWT::InvalidCritError, 'Crit header missing expected values: other') - end - end - - context 'when expected crits are present' do - it 'passes verification' do - expect { token.verify_claims!(crit: ['b64']) }.not_to raise_error - end - end - end - end - - context '#verify!' do - context 'when key is valid' do - it 'does not raise' do - expect(token.verify!(signature: { algorithm: 'HS256', key: 'secret' })).to eq(nil) - end - end - - context 'when key is invalid' do - it 'raises an error' do - expect { token.verify!(signature: { algorithm: 'HS256', key: 'wrong' }) }.to raise_error(JWT::VerificationError, 'Signature verification failed') - end - end - - context 'when claims are invalid' do - let(:payload) { { 'pay' => 'load', exp: Time.now.to_i - 1000 } } - - it 'raises an error' do - expect do - token.verify!(signature: { algorithm: 'HS256', key: 'secret' }, - claims: { exp: { leeway: 900 } }) - end.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - end - end - end - - context '#valid?' do - context 'when key is valid' do - it 'returns true' do - expect(token.valid?(signature: { algorithm: 'HS256', key: 'secret' })).to be(true) - end - end - - context 'when key is invalid' do - it 'returns false' do - expect(token.valid?(signature: { algorithm: 'HS256', key: 'wrong' })).to be(false) - end - end - - context 'when claims are provided as an array' do - it 'returns true' do - expect( - token.valid?(signature: { algorithm: 'HS256', key: 'secret' }, claims: [:exp]) - ).to be(true) - end - end - - context 'when claims are invalid' do - let(:payload) { { 'pay' => 'load', exp: Time.now.to_i - 1000 } } - - it 'returns false' do - expect( - token.valid?(signature: { algorithm: 'HS256', key: 'secret' }, - claims: { exp: { leeway: 900 } }) - ).to be(false) - end - end - end - - describe '#valid_claims?' do - context 'exp claim' do - let(:payload) { { 'exp' => Time.now.to_i - 10, 'pay' => 'load' } } - - context 'when claim is valid' do - it 'returns true' do - expect(token.valid_claims?(exp: { leeway: 1000 })).to be(true) - end - end - - context 'when claim is invalid' do - it 'returns true' do - expect(token.valid_claims?(:exp)).to be(false) - end - end - - context 'when no claims are provided' do - it 'validates the exp claim and returns false' do - expect(token.valid_claims?).to be(false) - end - end - - context 'when claim validation skips verifying the exp claim' do - it 'returns true' do - expect(token.valid_claims?({})).to be(true) - end - end - end - end - - describe '#claim_errors' do - context 'exp claim' do - let(:payload) { { 'exp' => Time.now.to_i - 10, 'pay' => 'load' } } - - context 'when claim is valid' do - it 'returns empty array' do - expect(token.claim_errors(exp: { leeway: 1000 })).to be_empty - end - end - - context 'when claim is invalid' do - it 'returns array with error objects' do - expect(token.claim_errors(:exp).map(&:message)).to eq(['Signature has expired']) - end - end - end - end - - describe 'integration use-cases' do - context 'simple verify HS256 with defaults' do - let(:encoded_token) do - JWT::Token.new(payload: { 'pay' => 'load' }) - .tap { |t| t.sign!(algorithm: 'HS256', key: 'secret_signing_key') } - .jwt - end - - it 'protects the user from unverified payload access' do - token = described_class.new(encoded_token) - - expect(token.unverified_payload).to eq({ 'pay' => 'load' }) - expect(token.header).to eq({ 'alg' => 'HS256' }) - - expect { token.payload }.to raise_error(JWT::DecodeError, 'Verify the token signature before accessing the payload') - - expect(token.valid_signature?(algorithm: 'HS256', key: 'invalid_signing_key')).to be(false) - expect { token.payload }.to raise_error(JWT::DecodeError, 'Verify the token signature before accessing the payload') - - expect(token.valid_signature?(algorithm: 'HS256', key: 'secret_signing_key')).to be(true) - expect { token.payload }.to raise_error(JWT::DecodeError, 'Verify the token claims before accessing the payload') - - expect(token.valid_claims?(iss: 'issuer')).to be(false) - expect { token.payload }.to raise_error(JWT::DecodeError, 'Verify the token claims before accessing the payload') - - expect(token.valid_claims?).to be(true) - expect(token.payload).to eq({ 'pay' => 'load' }) - - token = described_class.new(encoded_token) - - expect(token.valid?(signature: { algorithm: 'HS256', key: 'invalid_signing_key' })).to be(false) - expect { token.payload }.to raise_error(JWT::DecodeError, 'Verify the token signature before accessing the payload') - - expect(token.valid?(signature: { algorithm: 'HS256', key: 'secret_signing_key' })).to be(true) - expect(token.payload).to eq({ 'pay' => 'load' }) - end - end - end -end diff --git a/spec/jwt/jwa/ecdsa_spec.rb b/spec/jwt/jwa/ecdsa_spec.rb deleted file mode 100644 index 4a14d6de7..000000000 --- a/spec/jwt/jwa/ecdsa_spec.rb +++ /dev/null @@ -1,110 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWA::Ecdsa do - describe '.curve_by_name' do - subject { described_class.curve_by_name(curve_name) } - - context 'when secp256r1 is given' do - let(:curve_name) { 'secp256r1' } - it { is_expected.to eq(algorithm: 'ES256', digest: 'sha256') } - end - - context 'when prime256v1 is given' do - let(:curve_name) { 'prime256v1' } - it { is_expected.to eq(algorithm: 'ES256', digest: 'sha256') } - end - - context 'when secp521r1 is given' do - let(:curve_name) { 'secp521r1' } - it { is_expected.to eq(algorithm: 'ES512', digest: 'sha512') } - end - - context 'when secp256k1 is given' do - let(:curve_name) { 'secp256k1' } - it { is_expected.to eq(algorithm: 'ES256K', digest: 'sha256') } - end - - context 'when unknown is given' do - let(:curve_name) { 'unknown' } - it 'raises an error' do - expect { subject }.to raise_error(JWT::UnsupportedEcdsaCurve) - end - end - end - - let(:ecdsa_key) { test_pkey('ec256-private.pem') } - let(:data) { 'test data' } - let(:instance) { described_class.new('ES256', 'sha256') } - let(:signature) { instance.sign(data: data, signing_key: ecdsa_key) } - - describe '#verify' do - context 'when the verification key is valid' do - it 'returns true for a valid signature' do - expect(instance.verify(data: data, signature: signature, verification_key: ecdsa_key)).to be true - end - - it 'returns false for an invalid signature' do - expect(instance.verify(data: data, signature: 'invalid_signature', verification_key: ecdsa_key)).to be false - end - end - - context 'when verification results in a OpenSSL::PKey::PKeyError error' do - it 'raises a JWT::VerificationError' do - allow(ecdsa_key).to receive(:dsa_verify_asn1).and_raise(OpenSSL::PKey::PKeyError.new('Error')) - expect do - instance.verify(data: data, signature: '', verification_key: ecdsa_key) - end.to raise_error(JWT::VerificationError, 'Signature verification raised') - end - end - - context 'when the verification key is not an OpenSSL::PKey::EC instance' do - it 'raises a JWT::DecodeError' do - expect do - instance.verify(data: data, signature: '', verification_key: 'not_a_key') - end.to raise_error(JWT::DecodeError, 'The given key is a String. It has to be an OpenSSL::PKey::EC instance') - end - end - - context 'when the verification key is a point' do - it 'verifies the signature' do - expect(ecdsa_key.public_key).to be_a(OpenSSL::PKey::EC::Point) - expect(instance.verify(data: data, signature: signature, verification_key: ecdsa_key.public_key)).to be(true) - end - end - end - - describe '#sign' do - context 'when the signing key is valid' do - it 'returns a valid signature' do - expect(signature).to be_a(String) - expect(signature.length).to be > 0 - end - end - - context 'when the signing key is a public key' do - it 'raises a JWT::DecodeError' do - public_key = test_pkey('ec256-public.pem') - expect do - instance.sign(data: data, signing_key: public_key) - end.to raise_error(JWT::EncodeError, 'The given key is not a private key') - end - end - - context 'when the signing key is not an OpenSSL::PKey::EC instance' do - it 'raises a JWT::DecodeError' do - expect do - instance.sign(data: data, signing_key: 'not_a_key') - end.to raise_error(JWT::EncodeError, 'The given key is a String. It has to be an OpenSSL::PKey::EC instance') - end - end - - context 'when the signing key is invalid' do - it 'raises a JWT::DecodeError' do - invalid_key = OpenSSL::PKey::EC.generate('sect571r1') - expect do - instance.sign(data: data, signing_key: invalid_key) - end.to raise_error(JWT::DecodeError, "The ECDSA curve 'sect571r1' is not supported") - end - end - end -end diff --git a/spec/jwt/jwa/hmac_spec.rb b/spec/jwt/jwa/hmac_spec.rb deleted file mode 100644 index 20504cdee..000000000 --- a/spec/jwt/jwa/hmac_spec.rb +++ /dev/null @@ -1,128 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWA::Hmac do - let(:instance) { described_class.new('HS256', OpenSSL::Digest::SHA256) } - let(:valid_signature) { [60, 56, 87, 72, 185, 194, 150, 13, 18, 148, 76, 245, 94, 91, 201, 64, 111, 91, 167, 156, 43, 148, 41, 113, 168, 156, 137, 12, 11, 31, 58, 97].pack('C*') } - let(:hmac_secret) { 'secret_key' } - - describe '#sign' do - subject { instance.sign(data: 'test', signing_key: hmac_secret) } - - context 'when signing with a key' do - it { is_expected.to eq(valid_signature) } - end - - # Address OpenSSL 3.0 errors with empty hmac_secret - https://github.com/jwt/ruby-jwt/issues/526 - context 'when nil hmac_secret is passed' do - let(:hmac_secret) { nil } - context 'when OpenSSL 3.0 raises a malloc failure' do - before do - allow(OpenSSL::HMAC).to receive(:digest).and_raise(OpenSSL::HMACError.new('EVP_PKEY_new_mac_key: malloc failure')) - end - - it 'raises JWT::DecodeError' do - expect { subject }.to raise_error(JWT::DecodeError, 'OpenSSL 3.0 does not support nil or empty hmac_secret') - end - end - - context 'when OpenSSL raises any other error' do - before do - allow(OpenSSL::HMAC).to receive(:digest).and_raise(OpenSSL::HMACError.new('Another Random Error')) - end - - it 'raises the original error' do - expect { subject }.to raise_error(OpenSSL::HMACError, 'Another Random Error') - end - end - - context 'when other versions of openssl do not raise an exception' do - let(:response) { Base64.decode64("Q7DO+ZJl+eNMEOqdNQGSbSezn1fG1nRWHYuiNueoGfs=\n") } - before do - allow(OpenSSL::HMAC).to receive(:digest).and_return(response) - end - - it { is_expected.to eql(response) } - end - end - - context 'when blank hmac_secret is passed' do - let(:hmac_secret) { '' } - context 'when OpenSSL 3.0 raises a malloc failure' do - before do - allow(OpenSSL::HMAC).to receive(:digest).and_raise(OpenSSL::HMACError.new('EVP_PKEY_new_mac_key: malloc failure')) - end - - it 'raises JWT::DecodeError' do - expect { subject }.to raise_error(JWT::DecodeError, 'OpenSSL 3.0 does not support nil or empty hmac_secret') - end - end - - context 'when OpenSSL raises any other error' do - before do - allow(OpenSSL::HMAC).to receive(:digest).and_raise(OpenSSL::HMACError.new('Another Random Error')) - end - - it 'raises the original error' do - expect { subject }.to raise_error(OpenSSL::HMACError, 'Another Random Error') - end - end - - context 'when other versions of openssl do not raise an exception' do - let(:response) { Base64.decode64("Q7DO+ZJl+eNMEOqdNQGSbSezn1fG1nRWHYuiNueoGfs=\n") } - before do - allow(OpenSSL::HMAC).to receive(:digest).and_return(response) - end - - it { is_expected.to eql(response) } - end - end - - context 'when hmac_secret is passed' do - let(:hmac_secret) { 'test' } - context 'when OpenSSL 3.0 raises a malloc failure' do - before do - allow(OpenSSL::HMAC).to receive(:digest).and_raise(OpenSSL::HMACError.new('EVP_PKEY_new_mac_key: malloc failure')) - end - - it 'raises the original error' do - expect { subject }.to raise_error(OpenSSL::HMACError, 'EVP_PKEY_new_mac_key: malloc failure') - end - end - - context 'when OpenSSL raises any other error' do - before do - allow(OpenSSL::HMAC).to receive(:digest).and_raise(OpenSSL::HMACError.new('Another Random Error')) - end - - it 'raises the original error' do - expect { subject }.to raise_error(OpenSSL::HMACError, 'Another Random Error') - end - end - - context 'when other versions of openssl do not raise an exception' do - let(:response) { Base64.decode64("iM0hCLU0fZc885zfkFPX3UJwSHbYyam9ji0WglnT3fc=\n") } - before do - allow(OpenSSL::HMAC).to receive(:digest).and_return(response) - end - - it { is_expected.to eql(response) } - end - end - end - - describe '#verify' do - subject { instance.verify(data: 'test', signature: signature, verification_key: hmac_secret) } - - context 'when signature is valid' do - let(:signature) { valid_signature } - - it { is_expected.to be(true) } - end - - context 'when signature is invalid' do - let(:signature) { [60, 56, 87, 72, 185, 194].pack('C*') } - - it { is_expected.to be(false) } - end - end -end diff --git a/spec/jwt/jwa/none_spec.rb b/spec/jwt/jwa/none_spec.rb deleted file mode 100644 index ca5a1094d..000000000 --- a/spec/jwt/jwa/none_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWA::None do - subject { described_class.new } - - describe '#sign' do - it 'returns an empty string' do - expect(subject.sign('data', 'key')).to eq('') - end - end - - describe '#verify' do - it 'returns true' do - expect(subject.verify('data', 'signature', 'key')).to be true - end - end -end diff --git a/spec/jwt/jwa/ps_spec.rb b/spec/jwt/jwa/ps_spec.rb deleted file mode 100644 index 1043e9bc5..000000000 --- a/spec/jwt/jwa/ps_spec.rb +++ /dev/null @@ -1,95 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWA::Ps do - let(:rsa_key) { test_pkey('rsa-2048-private.pem') } - let(:data) { 'test data' } - let(:ps256_instance) { described_class.new('PS256') } - let(:ps384_instance) { described_class.new('PS384') } - let(:ps512_instance) { described_class.new('PS512') } - - before do - skip 'OpenSSL gem missing RSA-PSS support' unless OpenSSL::PKey::RSA.method_defined?(:sign_pss) - end - - describe '#initialize' do - it 'initializes with the correct algorithm and digest' do - expect(ps256_instance.instance_variable_get(:@alg)).to eq('PS256') - expect(ps256_instance.send(:digest_algorithm)).to eq('sha256') - - expect(ps384_instance.instance_variable_get(:@alg)).to eq('PS384') - expect(ps384_instance.send(:digest_algorithm)).to eq('sha384') - - expect(ps512_instance.instance_variable_get(:@alg)).to eq('PS512') - expect(ps512_instance.send(:digest_algorithm)).to eq('sha512') - end - end - - describe '#sign' do - context 'with a valid RSA key' do - it 'signs the data with PS256' do - expect(ps256_instance.sign(data: data, signing_key: rsa_key)).not_to be_nil - end - - it 'signs the data with PS384' do - expect(ps384_instance.sign(data: data, signing_key: rsa_key)).not_to be_nil - end - - it 'signs the data with PS512' do - expect(ps512_instance.sign(data: data, signing_key: rsa_key)).not_to be_nil - end - end - - context 'with an invalid key' do - it 'raises an error' do - expect do - ps256_instance.sign(data: data, signing_key: 'invalid_key') - end.to raise_error(JWT::EncodeError, /The given key is a String. It has to be an OpenSSL::PKey::RSA instance./) - end - end - - context 'with a key length less than 2048 bits' do - let(:rsa_key) { OpenSSL::PKey::RSA.generate(2047) } - - it 'raises an error' do - expect do - ps256_instance.sign(data: data, signing_key: rsa_key) - end.to raise_error(JWT::EncodeError, 'The key length must be greater than or equal to 2048 bits') - end - end - end - - describe '#verify' do - let(:ps256_signature) { ps256_instance.sign(data: data, signing_key: rsa_key) } - let(:ps384_signature) { ps384_instance.sign(data: data, signing_key: rsa_key) } - let(:ps512_signature) { ps512_instance.sign(data: data, signing_key: rsa_key) } - - context 'with a valid RSA key' do - it 'verifies the signature with PS256' do - expect(ps256_instance.verify(data: data, signature: ps256_signature, verification_key: rsa_key)).to be(true) - end - - it 'verifies the signature with PS384' do - expect(ps384_instance.verify(data: data, signature: ps384_signature, verification_key: rsa_key)).to be(true) - end - - it 'verifies the signature with PS512' do - expect(ps512_instance.verify(data: data, signature: ps512_signature, verification_key: rsa_key)).to be(true) - end - end - - context 'with an invalid signature' do - it 'raises a verification error' do - expect(ps256_instance.verify(data: data, signature: 'invalid_signature', verification_key: rsa_key)).to be(false) - end - end - - context 'when verification results in a OpenSSL::PKey::PKeyError error' do - it 'raises a JWT::VerificationError' do - allow(rsa_key).to receive(:verify_pss).and_raise(OpenSSL::PKey::PKeyError.new('Error')) - expect do - ps256_instance.verify(data: data, signature: ps256_signature, verification_key: rsa_key) - end.to raise_error(JWT::VerificationError, 'Signature verification raised') - end - end - end -end diff --git a/spec/jwt/jwa/rsa_spec.rb b/spec/jwt/jwa/rsa_spec.rb deleted file mode 100644 index 318ea8e6e..000000000 --- a/spec/jwt/jwa/rsa_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWA::Rsa do - let(:rsa_key) { test_pkey('rsa-2048-private.pem') } - let(:data) { 'test data' } - let(:rsa_instance) { described_class.new('RS256') } - - describe '#initialize' do - it 'initializes with the correct algorithm and digest' do - expect(rsa_instance.instance_variable_get(:@alg)).to eq('RS256') - expect(rsa_instance.send(:digest)).to eq('SHA256') - end - end - - describe '#sign' do - context 'with a valid RSA key' do - it 'signs the data' do - signature = rsa_instance.sign(data: data, signing_key: rsa_key) - expect(signature).not_to be_nil - end - end - - context 'with a key length less than 2048 bits' do - let(:rsa_key) { OpenSSL::PKey::RSA.generate(2047) } - - it 'raises an error' do - expect do - rsa_instance.sign(data: data, signing_key: rsa_key) - end.to raise_error(JWT::EncodeError, 'The key length must be greater than or equal to 2048 bits') - end - end - - context 'with an invalid key' do - it 'raises an error' do - expect do - rsa_instance.sign(data: data, signing_key: 'invalid_key') - end.to raise_error(JWT::EncodeError, /The given key is a String. It has to be an OpenSSL::PKey::RSA instance/) - end - end - end - - describe '#verify' do - let(:signature) { rsa_instance.sign(data: data, signing_key: rsa_key) } - - context 'with a valid RSA key' do - it 'returns true' do - expect(rsa_instance.verify(data: data, signature: signature, verification_key: rsa_key)).to be(true) - end - end - - context 'with an invalid signature' do - it 'returns false' do - expect(rsa_instance.verify(data: data, signature: 'invalid_signature', verification_key: rsa_key)).to be(false) - end - end - - context 'with an invalid key' do - it 'returns false' do - expect(rsa_instance.verify(data: data, signature: 'invalid_signature', verification_key: OpenSSL::PKey::RSA.generate(2048))).to be(false) - end - end - end -end diff --git a/spec/jwt/jwa/unsupported_spec.rb b/spec/jwt/jwa/unsupported_spec.rb deleted file mode 100644 index ba904096b..000000000 --- a/spec/jwt/jwa/unsupported_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWA::Unsupported do - describe '.sign' do - it 'raises an error for unsupported signing method' do - expect { described_class.sign('data', 'key') }.to raise_error(JWT::EncodeError, 'Unsupported signing method') - end - end - - describe '.verify' do - it 'raises an error for unsupported algorithm' do - expect { described_class.verify('data', 'signature', 'key') }.to raise_error(JWT::VerificationError, 'Algorithm not supported') - end - end -end diff --git a/spec/jwt/jwa_spec.rb b/spec/jwt/jwa_spec.rb deleted file mode 100644 index 7eb59a75a..000000000 --- a/spec/jwt/jwa_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWA do - describe '.resolve_and_sort' do - let(:subject) { described_class.resolve_and_sort(algorithms: algorithms, preferred_algorithm: preferred_algorithm).map(&:alg) } - - context 'when algorithms have the preferred last' do - let(:algorithms) { %w[HS256 HS512 RS512] } - let(:preferred_algorithm) { 'RS512' } - - it 'places the preferred algorithm first' do - is_expected.to eq(%w[RS512 HS256 HS512]) - end - end - - context 'when algorithms have the preferred in the middle' do - let(:algorithms) { %w[HS512 HS256 RS512] } - let(:preferred_algorithm) { 'HS256' } - - it 'places the preferred algorithm first' do - is_expected.to eq(%w[HS256 HS512 RS512]) - end - end - end -end diff --git a/spec/jwt/jwk/decode_with_jwk_spec.rb b/spec/jwt/jwk/decode_with_jwk_spec.rb deleted file mode 100644 index 314f47bca..000000000 --- a/spec/jwt/jwk/decode_with_jwk_spec.rb +++ /dev/null @@ -1,188 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT do - describe '.decode for JWK usecase' do - let(:keypair) { test_pkey('rsa-2048-private.pem') } - let(:jwk) { JWT::JWK.new(keypair) } - let(:valid_key) { jwk.export } - let(:public_jwks) { { keys: [valid_key, { kid: 'not_the_correct_one', kty: 'oct', k: 'secret' }] } } - let(:token_payload) { { 'data' => 'something' } } - let(:token_headers) { { kid: jwk.kid } } - let(:algorithm) { 'RS512' } - let(:signed_token) { described_class.encode(token_payload, jwk.signing_key, algorithm, token_headers) } - - context 'when JWK features are used manually' do - it 'is able to decode the token' do - payload, _header = described_class.decode(signed_token, nil, true, { algorithms: [algorithm] }) do |header, _payload| - JWT::JWK.import(public_jwks[:keys].find { |key| key[:kid] == header['kid'] }).verify_key - end - expect(payload).to eq(token_payload) - end - end - - context 'when jwk keys are given as an array' do - context 'and kid is in the set' do - it 'is able to decode the token' do - payload, _header = described_class.decode(signed_token, nil, true, { algorithms: [algorithm], jwks: public_jwks }) - expect(payload).to eq(token_payload) - end - end - - context 'and kid is not in the set' do - before do - public_jwks[:keys].first[:kid] = 'NOT_A_MATCH' - end - it 'raises an exception' do - expect { described_class.decode(signed_token, nil, true, { algorithms: [algorithm], jwks: public_jwks }) }.to raise_error( - JWT::DecodeError, /Could not find public key for kid .*/ - ) - end - end - - context 'and x5t is in the set' do - let(:x5t) { Base64.urlsafe_encode64(OpenSSL::Digest::SHA1.new(keypair.to_der).digest, padding: false) } - let(:valid_key) { jwk.export.merge({ x5t: x5t }) } - let(:token_headers) { { x5t: x5t } } - it 'is able to decode the token' do - payload, _header = described_class.decode(signed_token, nil, true, { algorithms: [algorithm], jwks: public_jwks, key_fields: [:x5t] }) - expect(payload).to eq(token_payload) - end - end - - context 'and both kid and x5t is in the set' do - let(:x5t) { Base64.urlsafe_encode64(OpenSSL::Digest::SHA1.new(keypair.to_der).digest, padding: false) } - let(:valid_key) { jwk.export.merge({ x5t: x5t }) } - let(:token_headers) { { x5t: x5t, kid: 'NOT_A_MATCH' } } - it 'is able to decode the token based on the priority of the key defined in key_fields' do - payload, _header = described_class.decode(signed_token, nil, true, { algorithms: [algorithm], jwks: public_jwks, key_fields: %i[x5t kid] }) - expect(payload).to eq(token_payload) - end - end - - context 'no keys are found in the set' do - let(:public_jwks) { { keys: [] } } - it 'raises an exception' do - expect { described_class.decode(signed_token, nil, true, { algorithms: [algorithm], jwks: public_jwks }) }.to raise_error( - JWT::DecodeError, /No keys found in jwks/ - ) - end - end - - context 'token does not know the kid' do - let(:token_headers) { {} } - it 'raises an exception' do - expect { described_class.decode(signed_token, nil, true, { algorithms: [algorithm], jwks: public_jwks }) }.to raise_error( - JWT::DecodeError, 'No key id (kid) or x5t found from token headers' - ) - end - end - end - - context 'when jwk keys are loaded using a proc/lambda' do - it 'decodes the token' do - payload, _header = described_class.decode(signed_token, nil, true, { algorithms: [algorithm], jwks: ->(_opts) { public_jwks } }) - expect(payload).to eq(token_payload) - end - end - - context 'when jwk keys are rotated' do - it 'decodes the token' do - key_loader = ->(options) { options[:invalidate] ? public_jwks : { keys: [] } } - payload, _header = described_class.decode(signed_token, nil, true, { algorithms: [algorithm], jwks: key_loader }) - expect(payload).to eq(token_payload) - end - end - - context 'when jwk keys are loaded from JSON with string keys' do - it 'decodes the token' do - key_loader = ->(_options) { JSON.parse(JSON.generate(public_jwks)) } - payload, _header = described_class.decode(signed_token, nil, true, { algorithms: [algorithm], jwks: key_loader }) - expect(payload).to eq(token_payload) - end - end - - context 'when the token kid is nil' do - let(:token_headers) { {} } - context 'and allow_nil_kid is specified' do - it 'decodes the token' do - key_loader = ->(_options) { JSON.parse(JSON.generate(public_jwks)) } - payload, _header = described_class.decode(signed_token, nil, true, { algorithms: ['RS512'], jwks: key_loader, allow_nil_kid: true }) - expect(payload).to eq(token_payload) - end - end - end - - context 'when the token kid is not a string' do - let(:token_headers) { { kid: 5 } } - it 'raises an exception' do - expect { described_class.decode(signed_token, nil, true, { algorithms: ['RS512'], jwks: public_jwks }) }.to raise_error( - JWT::DecodeError, 'Invalid type for kid header parameter' - ) - end - end - - context 'mixing algorithms using kid header' do - let(:hmac_jwk) { JWT::JWK.new('secret') } - let(:rsa_jwk) { JWT::JWK.new(test_pkey('rsa-2048-private.pem')) } - let(:ec_jwk_secp384r1) { JWT::JWK.new(test_pkey('ec384-private.pem')) } - let(:ec_jwk_secp521r1) { JWT::JWK.new(test_pkey('ec384-private.pem')) } - let(:jwks) { { keys: [hmac_jwk.export(include_private: true), rsa_jwk.export, ec_jwk_secp384r1.export, ec_jwk_secp521r1.export] } } - - context 'when RSA key is pointed to as HMAC secret' do - let(:signed_token) { described_class.encode({ 'foo' => 'bar' }, 'is not really relevant in the scenario', 'HS256', { kid: rsa_jwk.kid }) } - - it 'raises JWT::DecodeError' do - expect { described_class.decode(signed_token, nil, true, algorithms: ['HS256'], jwks: jwks) }.to raise_error(JWT::DecodeError, 'HMAC key expected to be a String') - end - end - - context 'when EC key is pointed to as HMAC secret' do - let(:signed_token) { described_class.encode({ 'foo' => 'bar' }, 'is not really relevant in the scenario', 'HS256', { kid: ec_jwk_secp384r1.kid }) } - - it 'raises JWT::DecodeError' do - expect { described_class.decode(signed_token, nil, true, algorithms: ['HS256'], jwks: jwks) }.to raise_error(JWT::DecodeError, 'HMAC key expected to be a String') - end - end - - context 'when EC key is pointed to as RSA public key' do - let(:signed_token) { described_class.encode({ 'foo' => 'bar' }, rsa_jwk.signing_key, algorithm, { kid: ec_jwk_secp384r1.kid }) } - - it 'fails in some way' do - expect { described_class.decode(signed_token, nil, true, algorithms: [algorithm], jwks: jwks) }.to( - raise_error(JWT::VerificationError, 'Signature verification raised') - ) - end - end - - context 'when HMAC secret is pointed to as RSA public key' do - let(:signed_token) { described_class.encode({ 'foo' => 'bar' }, rsa_jwk.signing_key, algorithm, { kid: hmac_jwk.kid }) } - - it 'fails in some way' do - expect { described_class.decode(signed_token, nil, true, algorithms: [algorithm], jwks: jwks) }.to( - raise_error(NoMethodError, /undefined method .*verify/) - ) - end - end - - context 'when HMAC secret is pointed to as EC public key' do - let(:signed_token) { described_class.encode({ 'foo' => 'bar' }, ec_jwk_secp384r1.signing_key, 'ES384', { kid: hmac_jwk.kid }) } - - it 'fails in some way' do - expect { described_class.decode(signed_token, nil, true, algorithms: ['ES384'], jwks: jwks) }.to( - raise_error(JWT::DecodeError, 'The given key is a String. It has to be an OpenSSL::PKey::EC instance') - ) - end - end - - context 'when ES384 key is pointed to as ES512 key' do - let(:signed_token) { described_class.encode({ 'foo' => 'bar' }, ec_jwk_secp384r1.signing_key, 'ES512', { kid: ec_jwk_secp521r1.kid }) } - - it 'fails in some way' do - expect { described_class.decode(signed_token, nil, true, algorithms: ['ES512'], jwks: jwks) }.to( - raise_error(JWT::IncorrectAlgorithm, 'payload algorithm is ES512 but ES384 signing key was provided') - ) - end - end - end - end -end diff --git a/spec/jwt/jwk/ec_spec.rb b/spec/jwt/jwk/ec_spec.rb deleted file mode 100644 index 86349ef87..000000000 --- a/spec/jwt/jwk/ec_spec.rb +++ /dev/null @@ -1,256 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWK::EC do - let(:ec_key) { test_pkey('ec384-private.pem') } - - describe '.new' do - subject { described_class.new(keypair) } - - context 'when a keypair with both keys given' do - let(:keypair) { ec_key } - it 'creates an instance of the class' do - expect(subject).to be_a described_class - expect(subject.private?).to eq true - end - end - - context 'when a keypair with only public key is given' do - let(:keypair) { test_pkey('ec256-public.pem') } - it 'creates an instance of the class' do - expect(subject).to be_a described_class - expect(subject.private?).to eq false - end - end - - context 'when a number is given' do - let(:keypair) { 1234 } - it 'raises an argument error' do - expect { subject }.to raise_error(ArgumentError, 'key must be of type OpenSSL::PKey::EC or Hash with key parameters') - end - end - - context 'when EC with unsupported curve is given' do - let(:keypair) { OpenSSL::PKey::EC.generate('prime239v2') } - it 'raises an error' do - expect { subject }.to raise_error(JWT::JWKError, "Unsupported curve 'prime239v2'") - end - end - end - - describe '#keypair' do - subject(:jwk) { described_class.new(ec_key) } - - it 'returns the key' do - expect(jwk.keypair).to eq(ec_key) - end - end - - describe '#public_key' do - subject(:jwk) { described_class.new(ec_key) } - - it 'returns the key' do - expect(jwk.public_key).to eq(ec_key) - end - end - - describe '#export' do - let(:kid) { nil } - subject { described_class.new(keypair, kid).export } - - context 'when keypair with private key is exported' do - let(:keypair) { ec_key } - it 'returns a hash with the both parts of the key' do - expect(subject).to be_a Hash - expect(subject).to include(:kty, :kid, :x, :y) - - # Exported keys do not currently include private key info, - # event if the in-memory key had that information. This is - # done to match the traditional behavior of RSA JWKs. - ## expect(subject).to include(:d) - end - end - - context 'when keypair with public key is exported' do - let(:keypair) { test_pkey('ec256-public.pem') } - it 'returns a hash with the public parts of the key' do - expect(subject).to be_a Hash - expect(subject).to include(:kty, :kid, :x, :y) - - # Don't include private `d` if not explicitly requested. - expect(subject).not_to include(:d) - end - - context 'when a custom "kid" is provided' do - let(:kid) { 'custom_key_identifier' } - it 'exports it' do - expect(subject[:kid]).to eq 'custom_key_identifier' - end - end - end - - context 'when private key is requested' do - subject { described_class.new(keypair).export(include_private: true) } - let(:keypair) { ec_key } - it 'returns a hash with the both parts of the key' do - expect(subject).to be_a Hash - expect(subject).to include(:kty, :kid, :x, :y) - - # `d` is the private part. - expect(subject).to include(:d) - end - end - - context 'when a common parameter is given' do - let(:parameters) { { use: 'sig' } } - let(:keypair) { ec_key } - subject { described_class.new(keypair, parameters).export } - it 'returns a hash including the common parameter' do - expect(subject).to include(:use) - end - end - end - - describe '#verify' do - let(:data) { 'data_to_sign' } - let(:signature) { jwk.sign(data: data) } - - context 'when jwk is missing the alg parameter' do - let(:jwk) { described_class.new(ec_key) } - - context 'when the signature is valid' do - it 'returns true' do - expect(jwk.verify(data: data, signature: signature)).to be(true) - end - end - end - - context 'when jwk has alg parameter' do - let(:jwk) { described_class.new(ec_key, alg: 'ES384') } - - context 'when the signature is valid' do - it 'returns true' do - expect(jwk.verify(data: data, signature: signature)).to be(true) - end - end - - context 'when the signature is invalid' do - it 'returns false' do - expect(jwk.verify(data: data, signature: 'invalid')).to be(false) - end - end - end - - context 'when the jwk has an invalid alg header' do - let(:rsa) { described_class.new(ec_key, alg: 'INVALID') } - it 'raises JWT::VerificationError' do - expect { rsa.verify(data: data, signature: 'signature') }.to raise_error(JWT::VerificationError, 'Algorithm not supported') - end - end - - context 'when the jwk has none as the alg parameter' do - let(:rsa) { described_class.new(ec_key, alg: 'none') } - it 'raises JWT::JWKError' do - expect { rsa.verify(data: data, signature: 'signature') }.to raise_error(JWT::JWKError, 'none algorithm usage not supported via JWK') - end - end - - context 'when the jwk has HS256 as the alg parameter' do - let(:rsa) { described_class.new(ec_key, alg: 'HS256') } - it 'raises JWT::DecodeError' do - expect { rsa.verify(data: data, signature: 'signature') }.to raise_error(JWT::DecodeError, 'HMAC key expected to be a String') - end - end - end - - describe '.to_openssl_curve' do - context 'when a valid curve name is given' do - it 'returns the corresponding OpenSSL curve name' do - expect(JWT::JWK::EC.to_openssl_curve('P-256')).to eq('prime256v1') - expect(JWT::JWK::EC.to_openssl_curve('P-384')).to eq('secp384r1') - expect(JWT::JWK::EC.to_openssl_curve('P-521')).to eq('secp521r1') - expect(JWT::JWK::EC.to_openssl_curve('P-256K')).to eq('secp256k1') - end - end - context 'when an invalid curve name is given' do - it 'raises an error' do - expect { JWT::JWK::EC.to_openssl_curve('invalid-curve') }.to raise_error(JWT::JWKError, 'Invalid curve provided') - end - end - end - - describe '.import' do - subject { described_class.import(params) } - let(:include_private) { false } - let(:exported_key) { described_class.new(keypair).export(include_private: include_private) } - - %w[P-256 P-384 P-521 P-256K].each do |crv| - context "when crv=#{crv}" do - let(:openssl_curve) { JWT::JWK::EC.to_openssl_curve(crv) } - let(:ec_key) { OpenSSL::PKey::EC.generate(openssl_curve) } - - context 'when keypair is private' do - let(:include_private) { true } - let(:keypair) { ec_key } - let(:params) { exported_key } - - it 'returns a private key' do - expect(subject.private?).to eq true - expect(subject).to be_a described_class - - # Regular export returns only the non-private parts. - public_only = exported_key.reject { |k, _v| k == :d } - expect(subject.export).to eq(public_only) - - # Private export returns the original input. - expect(subject.export(include_private: true)).to eq(exported_key) - end - - context 'with a custom "kid" value' do - let(:exported_key) do - super().merge(kid: 'custom_key_identifier') - end - it 'imports that "kid" value' do - expect(subject.kid).to eq('custom_key_identifier') - end - end - end - - context 'when keypair is public' do - context 'returns a public key' do - let(:keypair) { test_pkey('ec256-public.pem') } - let(:params) { exported_key } - - it 'returns a hash with the public parts of the key' do - expect(subject).to be_a described_class - expect(subject.private?).to eq false - expect(subject.export).to eq(exported_key) - end - end - end - end - - context 'with missing 0-byte at the start of EC coordinates' do - let(:example_keysets) do - [ - '{"kty":"EC","crv":"P-256","x":"0Nv5IKAlkvXuAKmOmFgmrwXKR7qGePOzu_7RXg5msw","y":"FqnPSNutcjfvXNlufwb7nLJuUEnBkbMdZ3P79nY9c3k"}', - '{"kty":"EC","crv":"P-256","x":"xGjPg-7meZamM_yfkGeBUB2eJ5c82Y8vQdXwi5cVGw","y":"9FwKAuJacVyEy71yoVn1u1ETsQoiwF7QfkfXURGxg14"}', - '{"kty":"EC","crv":"P-256","x":"yTvy0bwt5s29mIg1DMq-IjZH4pDgZIN9keEEaSuWZhk","y":"a0nrmd8qz8jpZDgpY82Rgv3vZ5xiJuiAoMIuRlGnaw"}', - '{"kty":"EC","crv":"P-256","x":"yJen7AW4lLUTMH4luDj0wlMNSGCuOBB5R-ZoxlAU_g","y":"aMbA-M6ORHePSatiPVz_Pzu7z2XRnKMzK-HIscpfud8"}', - '{"kty":"EC","crv":"P-256","x":"p_D00Z1ydC7mBIpSKPUUrzVzY9Fr5NMhhGfnf4P9guw","y":"lCqM3B_s04uhm7_91oycBvoWzuQWJCbMoZc46uqHXA"}', - '{"kty":"EC","crv":"P-256","x":"hKS-vxV1bvfZ2xOuHv6Qt3lmHIiArTnhWac31kXw3w","y":"f_UWjrTpmq_oTdfss7YJ-9dEiYw_JC90kwAE-y0Yu-w"}', - '{"kty":"EC","crv":"P-256","x":"3W22hN16OJN1XPpUQuCxtwoBRlf-wGyBNIihQiTmSdI","y":"eUaveaPQ4CpyfY7sfCqEF1DCOoxHdMpPHW15BmUF0w"}', - '{"kty":"EC","crv":"P-256","x":"oq_00cGL3SxUZTA-JvcXALhfQya7elFuC7jcJScN7Bs","y":"1nNPIinv_gQiwStfx7vqs7Vt_MSyzoQDy9sCnZlFfg"}', - '{"crv":"P-521","kty":"EC","x":"AMNQr/q+YGv4GfkEjrXH2N0+hnGes4cCqahJlV39m3aJpqSK+uiAvkRE5SDm2bZBc3YHGzhDzfMTUpnvXwjugUQP","y":"fIwouWsnp44Fjh2gBmO8ZafnpXZwLOCoaT5itu/Q4Z6j3duRfqmDsqyxZueDA3Gaac2LkbWGplT7mg4j7vCuGsw="}' - ] - end - - it 'prepends a 0-byte to either X or Y coordinate so that the keys decode correctly' do - example_keysets.each do |keyset_json| - jwk = described_class.import(JSON.parse(keyset_json)) - expect(jwk).to be_kind_of(JWT::JWK::EC) - end - end - end - end - end -end diff --git a/spec/jwt/jwk/hmac_spec.rb b/spec/jwt/jwk/hmac_spec.rb deleted file mode 100644 index 18c6efaba..000000000 --- a/spec/jwt/jwk/hmac_spec.rb +++ /dev/null @@ -1,165 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWK::HMAC do - let(:hmac_key) { 'secret-key' } - let(:key) { hmac_key } - subject(:jwk) { described_class.new(key) } - - describe '.new' do - context 'when a secret key given' do - it 'creates an instance of the class' do - expect(jwk).to be_a described_class - expect(jwk.private?).to eq true - end - end - - context 'when key is a number' do - let(:key) { 123 } - it 'raises an ArgumentError' do - expect { jwk }.to raise_error(ArgumentError, 'key must be of type String or Hash with key parameters') - end - end - end - - describe '#keypair' do - it 'returns a string' do - expect(jwk.keypair).to eq(key) - end - end - - describe '#export' do - let(:kid) { nil } - - context 'when key is exported' do - let(:key) { hmac_key } - subject { described_class.new(key, kid).export } - it 'returns a hash with the key' do - expect(subject).to be_a Hash - expect(subject).to include(:kty, :kid) - end - end - - context 'when key is exported with private key' do - let(:key) { hmac_key } - subject { described_class.new(key, kid).export(include_private: true) } - it 'returns a hash with the key' do - expect(subject).to be_a Hash - expect(subject).to include(:kty, :kid, :k) - end - end - end - - describe '.import' do - subject { described_class.import(params) } - let(:exported_key) { described_class.new(key).export(include_private: true) } - - context 'when secret key is given' do - let(:key) { hmac_key } - let(:params) { exported_key } - - it 'returns a key' do - expect(subject).to be_a described_class - expect(subject.export(include_private: true)).to eq(exported_key) - end - - context 'with a custom "kid" value' do - let(:exported_key) do - super().merge(kid: 'custom_key_identifier') - end - it 'imports that "kid" value' do - expect(subject.kid).to eq('custom_key_identifier') - end - end - - context 'with a common parameter' do - let(:exported_key) do - super().merge(use: 'sig') - end - it 'imports that common parameter' do - expect(subject[:use]).to eq('sig') - end - end - end - - context 'when example from RFC' do - let(:params) { { kty: 'oct', k: 'AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow' } } - - it 'decodes the k' do - expected_key = "\x03#5K+\x0F\xA5\xBC\x83~\x06ew{\xA6\x8FZ\xB3(\xE6\xF0T\xC9(\xA9\x0F\x84\xB2\xD2P.\xBF\xD3\xFBZ\x92\xD2\x06G\xEF\x96\x8A\xB4\xC3wb=\"=.!r\x05.O\b\xC0\xCD\x9A\xF5g\xD0\x80\xA3".dup.force_encoding('ASCII-8BIT') - expect(subject.verify_key).to eq(expected_key) - end - end - end - - describe '#[]=' do - context 'when k is given' do - it 'raises an error' do - expect { jwk[:k] = 'new_secret' }.to raise_error(ArgumentError, 'cannot overwrite cryptographic key attributes') - end - end - end - - describe '#==' do - it 'is equal to itself' do - other = jwk - expect(jwk == other).to eq true - end - - it 'is equal to a clone of itself' do - other = jwk.clone - expect(jwk == other).to eq true - end - - it 'is not equal to nil' do - other = nil - expect(jwk == other).to eq false - end - - it 'is not equal to boolean true' do - other = true - expect(jwk == other).to eq false - end - - it 'is not equal to a non-key' do - other = Object.new - expect(jwk == other).to eq false - end - - it 'is not equal to a different key' do - other = described_class.new('other-key') - expect(jwk == other).to eq false - end - end - - describe '#<=>' do - it 'is equal to itself' do - other = jwk - expect(jwk <=> other).to eq 0 - end - - it 'is equal to a clone of itself' do - other = jwk.clone - expect(jwk <=> other).to eq 0 - end - - it 'is not comparable to nil' do - other = nil - expect(jwk <=> other).to eq nil - end - - it 'is not comparable to boolean true' do - other = true - expect(jwk <=> other).to eq nil - end - - it 'is not comparable to a non-key' do - other = Object.new - expect(jwk <=> other).to eq nil - end - - it 'is not equal to a different key' do - other = described_class.new('other-key') - expect(jwk <=> other).not_to eq 0 - end - end -end diff --git a/spec/jwt/jwk/rsa_spec.rb b/spec/jwt/jwk/rsa_spec.rb deleted file mode 100644 index 321dafee1..000000000 --- a/spec/jwt/jwk/rsa_spec.rb +++ /dev/null @@ -1,285 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWK::RSA do - let(:rsa_key) { OpenSSL::PKey::RSA.new(2048) } - - describe '.new' do - subject { described_class.new(keypair) } - - context 'when a keypair with both keys given' do - let(:keypair) { rsa_key } - it 'creates an instance of the class' do - expect(subject).to be_a described_class - expect(subject.private?).to eq true - end - end - - context 'when a keypair with only public key is given' do - let(:keypair) { rsa_key.public_key } - it 'creates an instance of the class' do - expect(subject).to be_a described_class - expect(subject.private?).to eq false - end - end - end - - describe '#keypair' do - subject(:jwk) { described_class.new(rsa_key) } - - it 'warns to stderr' do - expect(jwk.keypair).to eq(rsa_key) - end - end - - describe '#export' do - subject { described_class.new(keypair).export } - - context 'when keypair with private key is exported' do - let(:keypair) { rsa_key } - it 'returns a hash with the public parts of the key' do - expect(subject).to be_a Hash - expect(subject).to include(:kty, :n, :e, :kid) - expect(subject).not_to include(:d, :p, :dp, :dq, :qi) - end - end - - context 'when keypair with public key is exported' do - let(:keypair) { rsa_key.public_key } - it 'returns a hash with the public parts of the key' do - expect(subject).to be_a Hash - expect(subject).to include(:kty, :n, :e, :kid) - expect(subject).not_to include(:d, :p, :dp, :dq, :qi) - end - end - - context 'when unsupported keypair is given' do - let(:keypair) { 'key' } - it 'raises an error' do - expect { subject }.to raise_error(ArgumentError) - end - end - - context 'when private key is requested' do - subject { described_class.new(keypair).export(include_private: true) } - let(:keypair) { rsa_key } - it 'returns a hash with the public AND private parts of the key' do - expect(subject).to be_a Hash - expect(subject).to include(:kty, :n, :e, :kid, :d, :p, :q, :dp, :dq, :qi) - end - end - end - - describe '.kid' do - context 'when configuration says to use :rfc7638_thumbprint' do - before do - JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint - end - - it 'generates the kid based on the thumbprint' do - expect(described_class.new(OpenSSL::PKey::RSA.new(2048)).kid.size).to eq(43) - end - end - - context 'when kid is given as a String parameter' do - it 'uses the given kid' do - expect(described_class.new(OpenSSL::PKey::RSA.new(2048), 'given').kid).to eq('given') - end - end - - context 'when kid is given in a hash parameter' do - it 'uses the given kid' do - expect(described_class.new(OpenSSL::PKey::RSA.new(2048), kid: 'given').kid).to eq('given') - end - end - end - - describe '#verify' do - let(:rsa) { described_class.new(rsa_key, alg: 'RS256') } - let(:data) { 'data_to_sign' } - let(:signature) { rsa.sign(data: data) } - - context 'when the signature is valid' do - it 'returns true' do - expect(rsa.verify(data: data, signature: signature)).to be(true) - end - end - - context 'when the signature is invalid' do - it 'returns false' do - expect(rsa.verify(data: data, signature: 'invalid_signature')).to be(false) - end - end - - context 'when the jwk is missing the alg header' do - let(:rsa) { described_class.new(rsa_key) } - it 'raises JWT::JWKError' do - expect { rsa.verify(data: data, signature: 'signature') }.to raise_error(JWT::JWKError, 'Could not resolve the JWA, the "alg" parameter is missing') - end - end - - context 'when the jwk has an invalid alg header' do - let(:rsa) { described_class.new(rsa_key, alg: 'INVALID') } - it 'raises JWT::VerificationError' do - expect { rsa.verify(data: data, signature: 'signature') }.to raise_error(JWT::VerificationError, 'Algorithm not supported') - end - end - - context 'when the jwk has none as the alg parameter' do - let(:rsa) { described_class.new(rsa_key, alg: 'none') } - it 'raises JWT::JWKError' do - expect { rsa.verify(data: data, signature: 'signature') }.to raise_error(JWT::JWKError, 'none algorithm usage not supported via JWK') - end - end - - context 'when the jwk has HS256 as the alg parameter' do - let(:rsa) { described_class.new(rsa_key, alg: 'HS256') } - it 'raises JWT::DecodeError' do - expect { rsa.verify(data: data, signature: 'signature') }.to raise_error(JWT::DecodeError, 'HMAC key expected to be a String') - end - end - end - - describe '.common_parameters' do - context 'when a common parameters hash is given' do - it 'imports the common parameter' do - expect(described_class.new(OpenSSL::PKey::RSA.new(2048), use: 'sig')[:use]).to eq('sig') - end - - it 'converts string keys to symbol keys' do - expect(described_class.new(OpenSSL::PKey::RSA.new(2048), { 'use' => 'sig' })[:use]).to eq('sig') - end - end - end - - describe '.import' do - subject { described_class.import(params) } - let(:exported_key) { described_class.new(rsa_key).export } - - context 'when keypair is imported with symbol keys' do - let(:params) { { kty: 'RSA', e: exported_key[:e], n: exported_key[:n] } } - it 'returns a hash with the public parts of the key' do - expect(subject).to be_a described_class - expect(subject.private?).to eq false - expect(subject.export).to eq(exported_key) - end - end - - context 'when keypair is imported with string keys from JSON' do - let(:params) { { 'kty' => 'RSA', 'e' => exported_key[:e], 'n' => exported_key[:n] } } - it 'returns a hash with the public parts of the key' do - expect(subject).to be_a described_class - expect(subject.private?).to eq false - expect(subject.export).to eq(exported_key) - end - end - - context 'when private key is included in the data' do - let(:exported_key) { described_class.new(rsa_key).export(include_private: true) } - let(:params) { exported_key } - it 'creates a complete keypair' do - expect(subject).to be_a described_class - expect(subject.private?).to eq true - end - end - - context 'when jwk_data is given without e and/or n' do - let(:params) { { kty: 'RSA' } } - it 'raises an error' do - expect { subject }.to raise_error(JWT::JWKError, 'Key format is invalid for RSA') - end - end - end - - shared_examples 'creating an RSA object from complete JWK parameters' do - let(:rsa_parameters) { jwk_parameters.transform_values { |value| described_class.decode_open_ssl_bn(value) } } - let(:all_jwk_parameters) { described_class.new(rsa_key).export(include_private: true) } - - context 'when public parameters (e, n) are given' do - let(:jwk_parameters) { all_jwk_parameters.slice(:e, :n) } - - it 'creates a valid RSA object representing a public key' do - expect(subject).to be_a(OpenSSL::PKey::RSA) - expect(subject.private?).to eq(false) - end - end - - context 'when only e, n, d, p and q are given' do - let(:jwk_parameters) { all_jwk_parameters.slice(:e, :n, :d, :p, :q) } - - it 'raises an error telling all the exponents are required' do - expect { subject }.to raise_error(JWT::JWKError, 'When one of p, q, dp, dq or qi is given all the other optimization parameters also needs to be defined') - end - end - - context 'when all key components n, e, d, p, q, dp, dq, qi are given' do - let(:jwk_parameters) { all_jwk_parameters.slice(:n, :e, :d, :p, :q, :dp, :dq, :qi) } - - it 'creates a valid RSA object representing a public key' do - expect(subject).to be_a(OpenSSL::PKey::RSA) - expect(subject.private?).to eq(true) - end - end - end - - shared_examples 'creating an RSA object from partial JWK parameters' do - context 'when e, n, d is given' do - let(:jwk_parameters) { all_jwk_parameters.slice(:e, :n, :d) } - - before do - skip 'OpenSSL prior to 2.2 does not seem to support partial parameters' if JWT.openssl_version < Gem::Version.new('2.2') - end - - it 'creates a valid RSA object representing a private key' do - expect(subject).to be_a(OpenSSL::PKey::RSA) - expect(subject.private?).to eq(true) - end - - it 'can be used for encryption and decryption' do - expect(subject.private_decrypt(subject.public_encrypt('secret'))).to eq('secret') - end - - it 'can be used for signing and verification' do - data = 'data_to_sign' - signature = subject.sign(OpenSSL::Digest.new('SHA512'), data) - expect(subject.verify(OpenSSL::Digest.new('SHA512'), signature, data)).to eq(true) - end - end - end - - describe '.create_rsa_key_using_der' do - subject(:rsa) { described_class.create_rsa_key_using_der(rsa_parameters) } - - include_examples 'creating an RSA object from complete JWK parameters' - - context 'when e, n, d is given' do - let(:jwk_parameters) { all_jwk_parameters.slice(:e, :n, :d) } - - it 'expects all CRT parameters given and raises error' do - expect { subject }.to raise_error(JWT::JWKError, 'Creating a RSA key with a private key requires the CRT parameters to be defined') - end - end - end - - describe '.create_rsa_key_using_sets' do - before do - skip 'OpenSSL without the RSA#set_key method not supported' unless OpenSSL::PKey::RSA.method_defined?(:set_key) - skip 'OpenSSL 3.0 does not allow mutating objects anymore' if JWT.openssl_3? - end - - subject(:rsa) { described_class.create_rsa_key_using_sets(rsa_parameters) } - - include_examples 'creating an RSA object from complete JWK parameters' - include_examples 'creating an RSA object from partial JWK parameters' - end - - describe '.create_rsa_key_using_accessors' do - before do - skip 'OpenSSL if RSA#d= is not available there is no accessors anymore' unless OpenSSL::PKey::RSA.method_defined?(:d=) - end - - subject(:rsa) { described_class.create_rsa_key_using_accessors(rsa_parameters) } - - include_examples 'creating an RSA object from complete JWK parameters' - include_examples 'creating an RSA object from partial JWK parameters' - end -end diff --git a/spec/jwt/jwk/set_spec.rb b/spec/jwt/jwk/set_spec.rb deleted file mode 100644 index 972f35fff..000000000 --- a/spec/jwt/jwk/set_spec.rb +++ /dev/null @@ -1,140 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWK::Set do - describe '.new' do - it 'can create an empty set' do - expect(described_class.new.keys).to eql([]) - end - - context 'can create a set' do - it 'from a JWK' do - jwk = JWT::JWK.new 'testkey' - expect(described_class.new(jwk).keys).to eql([jwk]) - end - - it 'from a JWKS hash with symbol keys' do - jwks = { keys: [{ kty: 'oct', k: Base64.strict_encode64('testkey') }] } - jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') }) - expect(described_class.new(jwks).keys).to eql([jwk]) - end - - it 'from a JWKS hash with string keys' do - jwks = { 'keys' => [{ 'kty' => 'oct', 'k' => Base64.strict_encode64('testkey') }] } - jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') }) - expect(described_class.new(jwks).keys).to eql([jwk]) - end - - it 'from an array of keys' do - jwk = JWT::JWK.new 'testkey' - expect(described_class.new([jwk]).keys).to eql([jwk]) - end - - it 'from an existing JWT::JWK::Set' do - jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') }) - jwks = described_class.new(jwk) - expect(described_class.new(jwks)).to eql(jwks) - end - end - - it 'raises an error on invalid inputs' do - expect { described_class.new(42) }.to raise_error(ArgumentError) - end - end - - describe '.export' do - it 'exports the JWKS to Hash' do - jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') }) - jwks = described_class.new(jwk) - exported = jwks.export - expect(exported[:keys].size).to eql(1) - expect(exported[:keys][0]).to eql(jwk.export) - end - end - - describe '.eql?' do - it 'correctly classifies equal sets' do - jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') }) - jwks1 = described_class.new(jwk) - jwks2 = described_class.new(jwk) - expect(jwks1).to eql(jwks2) - end - - it 'correctly classifies different sets' do - jwk1 = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') }) - jwk2 = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkex') }) - jwks1 = described_class.new(jwk1) - jwks2 = described_class.new(jwk2) - expect(jwks1).not_to eql(jwks2) - end - end - - # TODO: No idea why this does not work. eql? returns true for the two elements, - # but Array#uniq! doesn't recognize this, despite the documentation saying otherwise - describe '.uniq!' do - it 'filters out equal keys' do - jwk = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') }) - jwk2 = JWT::JWK.new({ kty: 'oct', k: Base64.strict_encode64('testkey') }) - jwks = described_class.new([jwk, jwk2]) - jwks.uniq! - expect(jwks.keys.size).to eql(1) - end - end - - describe '.select!' do - it 'filters the keyset' do - jwks = described_class.new([]) - jwks << JWT::JWK.new(test_pkey('rsa-2048-private.pem')) - jwks << JWT::JWK.new(test_pkey('ec384-private.pem')) - jwks.select! { |k| k[:kty] == 'RSA' } - expect(jwks.size).to eql(1) - expect(jwks.keys[0][:kty]).to eql('RSA') - end - end - - describe '.reject!' do - it 'filters the keyset' do - jwks = described_class.new([]) - jwks << JWT::JWK.new(test_pkey('rsa-2048-private.pem')) - jwks << JWT::JWK.new(test_pkey('ec384-private.pem')) - jwks.reject! { |k| k[:kty] == 'RSA' } - expect(jwks.size).to eql(1) - expect(jwks.keys[0][:kty]).to eql('EC') - end - end - - describe '.merge' do - context 'merges two JWKSs' do - it 'when called via .union' do - jwks1 = described_class.new(JWT::JWK.new(test_pkey('rsa-2048-private.pem'))) - jwks2 = described_class.new(JWT::JWK.new(test_pkey('ec384-private.pem'))) - jwks3 = jwks1.union(jwks2) - expect(jwks1.size).to eql(1) - expect(jwks2.size).to eql(1) - expect(jwks3.size).to eql(2) - expect(jwks3.keys).to include(jwks1.keys[0]) - expect(jwks3.keys).to include(jwks2.keys[0]) - end - - it 'when called via "|" operator' do - jwks1 = described_class.new(JWT::JWK.new(test_pkey('rsa-2048-private.pem'))) - jwks2 = described_class.new(JWT::JWK.new(test_pkey('ec384-private.pem'))) - jwks3 = jwks1 | jwks2 - expect(jwks1.size).to eql(1) - expect(jwks2.size).to eql(1) - expect(jwks3.size).to eql(2) - expect(jwks3.keys).to include(jwks1.keys[0]) - expect(jwks3.keys).to include(jwks2.keys[0]) - end - - it 'when called directly' do - jwks1 = described_class.new(JWT::JWK.new(test_pkey('rsa-2048-private.pem'))) - jwks2 = described_class.new(JWT::JWK.new(test_pkey('ec384-private.pem'))) - jwks3 = jwks1.merge(jwks2) - expect(jwks1.size).to eql(2) - expect(jwks2.size).to eql(1) - expect(jwks3).to eql(jwks1) - expect(jwks3.keys).to include(jwks2.keys[0]) - end - end - end -end diff --git a/spec/jwt/jwk/thumbprint_spec.rb b/spec/jwt/jwk/thumbprint_spec.rb deleted file mode 100644 index 174c9fcee..000000000 --- a/spec/jwt/jwk/thumbprint_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -describe JWT::JWK::Thumbprint do - describe '#to_s' do - let(:jwk_json) { nil } - let(:jwk) { JWT::JWK.import(JSON.parse(jwk_json)) } - - subject(:thumbprint) { described_class.new(jwk).to_s } - - context 'when example from RFC is given' do - let(:jwk_json) do - ' - { - "kty": "RSA", - "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt' \ - 'VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6' \ - '4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD' \ - 'W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9' \ - '1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH' \ - 'aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", - "e": "AQAB", - "alg": "RS256" - } - ' - end - - it { is_expected.to eq('NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs') } - end - - context 'when HMAC key is given' do - let(:jwk_json) do - ' - { - "kty":"oct", - "alg":"HS512", - "k":"B4uZ7IbZTnjdCQjUBXTpzMUznCYj3wdYDZcceeU0mLg" - } - ' - end - - it { is_expected.to eq('wPf4ZF5qlzoFxsGkft4eu1iWcehgAcahZL4XPV4dT-s') } - end - - context 'when EC key is given' do - let(:jwk_json) do - ' - { - "kty":"EC", - "crv":"P-384", - "x":"sbOnPOXPBULpeizfstr8b6b31QmvEnChXJNYBhXlmpGbs3vZtomBxNORYTT9Wylq", - "y":"mfyY4VJDbdKGVjBSIhN9BJEq--6IPuKy3gbIr734n6Xd81lnvKslPwjB-sdGouD6" - } - ' - end - - it { is_expected.to eq('dO52_we59sdR49HsGCpVzlDUQNvT3KxCTGakk4Un8qc') } - end - end -end diff --git a/spec/jwt/jwk_spec.rb b/spec/jwt/jwk_spec.rb deleted file mode 100644 index 135910f2a..000000000 --- a/spec/jwt/jwk_spec.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::JWK do - let(:rsa_key) { test_pkey('rsa-2048-private.pem') } - let(:ec_key) { test_pkey('ec256k-private.pem') } - - describe '.import' do - let(:keypair) { rsa_key.public_key } - let(:exported_key) { described_class.new(keypair).export } - let(:params) { exported_key } - - subject { described_class.import(params) } - - it 'creates a ::JWT::JWK::RSA instance' do - expect(subject).to be_a JWT::JWK::RSA - expect(subject.export).to eq(exported_key) - end - - context 'when number is given' do - let(:params) { 1234 } - it 'raises an error' do - expect { subject }.to raise_error(JWT::JWKError, 'Cannot create JWK from a Integer') - end - end - - context 'parsed from JSON' do - let(:params) { exported_key } - it 'creates a ::JWT::JWK::RSA instance from JSON parsed JWK' do - expect(subject).to be_a JWT::JWK::RSA - expect(subject.export).to eq(exported_key) - end - end - - context 'when keytype is not supported' do - let(:params) { { kty: 'unsupported' } } - - it 'raises an error' do - expect { subject }.to raise_error(JWT::JWKError) - end - end - - context 'when keypair with defined kid is imported' do - it 'returns the predefined kid if jwt_data contains a kid' do - params[:kid] = 'CUSTOM_KID' - expect(subject.export).to eq(params) - end - end - - context 'when a common JWK parameter is specified' do - it 'returns the defined common JWK parameter' do - params[:use] = 'sig' - expect(subject.export).to eq(params) - end - end - end - - describe '.new' do - let(:options) { nil } - subject { described_class.new(keypair, options) } - - context 'when RSA key is given' do - let(:keypair) { rsa_key } - it { is_expected.to be_a JWT::JWK::RSA } - end - - context 'when secret key is given' do - let(:keypair) { 'secret-key' } - it { is_expected.to be_a JWT::JWK::HMAC } - end - - context 'when EC key is given' do - let(:keypair) { ec_key } - it { is_expected.to be_a JWT::JWK::EC } - end - - context 'when kid is given' do - let(:keypair) { rsa_key } - let(:options) { 'CUSTOM_KID' } - it 'sets the kid' do - expect(subject.kid).to eq(options) - end - end - - context 'when a common parameter is given' do - subject { described_class.new(keypair, params) } - let(:keypair) { rsa_key } - let(:params) { { 'use' => 'sig' } } - it 'sets the common parameter' do - expect(subject[:use]).to eq('sig') - end - end - end - - describe '.[]' do - let(:params) { { use: 'sig' } } - let(:keypair) { rsa_key } - subject { described_class.new(keypair, params) } - - it 'allows to read common parameters via the key-accessor' do - expect(subject[:use]).to eq('sig') - end - - it 'allows to set common parameters via the key-accessor' do - subject[:use] = 'enc' - expect(subject[:use]).to eq('enc') - end - - it 'rejects key parameters as keys via the key-accessor' do - expect { subject[:kty] = 'something' }.to raise_error(ArgumentError) - end - end -end diff --git a/spec/jwt/jwt_spec.rb b/spec/jwt/jwt_spec.rb deleted file mode 100644 index 1bc14e0e5..000000000 --- a/spec/jwt/jwt_spec.rb +++ /dev/null @@ -1,949 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT do - let(:payload) { { 'user_id' => 'some@user.tld' } } - - let(:data) do - { - :empty_token => 'e30K.e30K.e30K', - :empty_token_2_segment => 'e30K.e30K.', - :invalid_header_token => 'W10.e30K.e30K', - :secret => 'My$ecretK3y', - :rsa_private => test_pkey('rsa-2048-private.pem'), - :rsa_public => test_pkey('rsa-2048-public.pem'), - :wrong_rsa_private => test_pkey('rsa-2048-wrong-public.pem'), - :wrong_rsa_public => test_pkey('rsa-2048-wrong-public.pem'), - 'ES256_private' => test_pkey('ec256-private.pem'), - 'ES256_public' => test_pkey('ec256-public.pem'), - 'ES256_private_v2' => test_pkey('ec256-private-v2.pem'), - 'ES256_public_v2' => test_pkey('ec256-public-v2.pem'), - 'ES384_private' => test_pkey('ec384-private.pem'), - 'ES384_public' => test_pkey('ec384-public.pem'), - 'ES512_private' => test_pkey('ec512-private.pem'), - 'ES512_public' => test_pkey('ec512-public.pem'), - 'ES256K_private' => test_pkey('ec256k-private.pem'), - 'ES256K_public' => test_pkey('ec256k-public.pem'), - 'NONE' => 'eyJhbGciOiJub25lIn0.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.', - 'HS256' => 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.kWOVtIOpWcG7JnyJG0qOkTDbOy636XrrQhMm_8JrRQ8', - 'HS384' => 'eyJhbGciOiJIUzM4NCJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.VuV4j4A1HKhWxCNzEcwc9qVF3frrEu-BRLzvYPkbWO0LENRGy5dOiBQ34remM3XH', - 'HS512' => 'eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.8zNtCBTJIZTHpZ-BkhR-6sZY1K85Nm5YCKqV3AxRdsBJDt_RR-REH2db4T3Y0uQwNknhrCnZGvhNHrvhDwV1kA', - 'RS256' => 'eyJhbGciOiJSUzI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.eSXvWP4GViiwUALj_-qTxU68I1oM0XjgDsCZBBUri2Ghh9d75QkVDoZ_v872GaqunN5A5xcnBK0-cOq-CR6OwibgJWfOt69GNzw5RrOfQ2mz3QI3NYEq080nF69h8BeqkiaXhI24Q51joEgfa9aj5Y-oitLAmtDPYTm7vTcdGufd6AwD3_3jajKBwkh0LPSeMtbe_5EyS94nFoEF9OQuhJYjUmp7agsBVa8FFEjVw5jEgVqkvERSj5hSY4nEiCAomdVxIKBfykyi0d12cgjhI7mBFwWkPku8XIPGZ7N8vpiSLdM68BnUqIK5qR7NAhtvT7iyLFgOqhZNUQ6Ret5VpQ', - 'RS384' => 'eyJhbGciOiJSUzM4NCJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.Sfgk56moPghtsjaP4so6tOy3I553mgwX-5gByMC6dX8lpeWgsxSeAd_K8IyO7u4lwYOL0DSftnqO1HEOuN1AKyBbDvaTXz3u2xNA2x4NYLdW4AZA6ritbYcKLO5BHTXw5ueMbtA1jjGXP0zI_aK2iJTMBmB8SCF88RYBUH01Tyf4PlLj98pGL-v3prZd6kZkIeRJ3326h04hslcB5HQKmgeBk24QNLIoIC-CD329HPjJ7TtGx01lj-ehTBnwVbBGzYFAyoalV5KgvL_MDOfWPr1OYHnR5s_Fm6_3Vg4u6lBljvHOrmv4Nfx7d8HLgbo8CwH4qn1wm6VQCtuDd-uhRg', - 'RS512' => 'eyJhbGciOiJSUzUxMiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.LIIAUEuCkGNdpYguOO5LoW4rZ7ED2POJrB0pmEAAchyTdIK4HKh1jcLxc6KyGwZv40njCgub3y72q6vcQTn7oD0zWFCVQRIDW1911Ii2hRNHuigiPUnrnZh1OQ6z65VZRU6GKs8omoBGU9vrClBU0ODqYE16KxYmE_0n4Xw2h3D_L1LF0IAOtDWKBRDa3QHwZRM9sHsHNsBuD5ye9KzDYN1YALXj64LBfA-DoCKfpVAm9NkRPOyzjR2X2C3TomOSJgqWIVHJucudKDDAZyEbO4RA5pI-UFYy1370p9bRajvtDyoBuLDCzoSkMyQ4L2DnLhx5CbWcnD7Cd3GUmnjjTA', - 'ES256' => '', - 'ES384' => '', - 'ES512' => '', - 'PS256' => '', - 'PS384' => '', - 'PS512' => '' - } - end - - context 'alg: NONE' do - let(:alg) { 'none' } - let(:encoded_token) { data['NONE'] } - - it 'should generate a valid token' do - token = JWT.encode payload, nil, alg - - expect(token).to eq encoded_token - end - - context 'decoding without verification' do - it 'should decode a valid token' do - jwt_payload, header = JWT.decode encoded_token, nil, false - - expect(header['alg']).to eq alg - expect(jwt_payload).to eq payload - end - end - - context 'decoding with verification' do - context 'without specifying the none algorithm' do - it 'should fail to decode the token' do - expect do - JWT.decode encoded_token, nil, true - end.to raise_error JWT::IncorrectAlgorithm - end - end - - context 'specifying the none algorithm' do - context 'when the claims are valid' do - it 'should decode the token' do - jwt_payload, header = JWT.decode encoded_token, nil, true, { algorithms: 'none' } - - expect(header['alg']).to eq 'none' - expect(jwt_payload).to eq payload - end - end - - context 'when the claims are invalid' do - let(:encoded_token) { JWT.encode({ exp: 0 }, nil, 'none') } - it 'should fail to decode the token' do - expect do - JWT.decode encoded_token, nil, true - end.to raise_error JWT::DecodeError - end - end - end - end - end - - %w[HS256 HS384 HS512].each do |alg| - context "alg: #{alg}" do - it 'should generate a valid token' do - token = JWT.encode payload, data[:secret], alg - - expect(token).to eq data[alg] - end - - it 'should decode a valid token' do - jwt_payload, header = JWT.decode data[alg], data[:secret], true, algorithm: alg - - expect(header['alg']).to eq alg - expect(jwt_payload).to eq payload - end - - it 'wrong secret should raise JWT::DecodeError' do - expect do - JWT.decode data[alg], 'wrong_secret', true, algorithm: alg - end.to raise_error JWT::VerificationError - end - - it 'wrong secret and verify = false should not raise JWT::DecodeError' do - expect do - JWT.decode data[alg], 'wrong_secret', false - end.not_to raise_error - end - end - end - - %w[RS256 RS384 RS512].each do |alg| - context "alg: #{alg}" do - it 'should generate a valid token' do - token = JWT.encode payload, data[:rsa_private], alg - - expect(token).to eq data[alg] - end - - it 'should decode a valid token' do - jwt_payload, header = JWT.decode data[alg], data[:rsa_public], true, algorithm: alg - - expect(header['alg']).to eq alg - expect(jwt_payload).to eq payload - end - - it 'should decode a valid token using algorithm hash string key' do - jwt_payload, header = JWT.decode data[alg], data[:rsa_public], true, 'algorithm' => alg - - expect(header['alg']).to eq alg - expect(jwt_payload).to eq payload - end - - it 'wrong key should raise JWT::DecodeError' do - key = test_pkey('rsa-2048-wrong-public.pem') - - expect do - JWT.decode data[alg], key, true, algorithm: alg - end.to raise_error JWT::DecodeError - end - - it 'wrong key and verify = false should not raise JWT::DecodeError' do - key = test_pkey('rsa-2048-wrong-public.pem') - - expect do - JWT.decode data[alg], key, false - end.not_to raise_error - end - end - end - - %w[ES256 ES384 ES512 ES256K].each do |alg| - before do - skip 'OpenSSL gem missing RSA-PSS support' unless OpenSSL::PKey::RSA.method_defined?(:sign_pss) - end - - context "alg: #{alg}" do - before(:each) do - data[alg] = JWT.encode(payload, data["#{alg}_private"], alg) - end - - let(:wrong_key) { test_pkey('ec256-wrong-public.pem') } - - it 'should generate a valid token' do - jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"], true, algorithm: alg - - expect(header['alg']).to eq alg - expect(jwt_payload).to eq payload - end - - it 'should decode a valid token' do - jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"], true, algorithm: alg - - expect(header['alg']).to eq alg - expect(jwt_payload).to eq payload - end - - it 'wrong key should raise JWT::DecodeError' do - expect do - JWT.decode data[alg], wrong_key - end.to raise_error JWT::DecodeError - end - - it 'wrong key and verify = false should not raise JWT::DecodeError' do - expect do - JWT.decode data[alg], wrong_key, false - end.not_to raise_error - end - end - end - - %w[PS256 PS384 PS512].each do |alg| - context "alg: #{alg}" do - before(:each) do - data[alg] = JWT.encode payload, data[:rsa_private], alg - end - - let(:wrong_key) { data[:wrong_rsa_public] } - - it 'should generate a valid token' do - token = data[alg] - - header, body, signature = token.split('.') - - expect(header).to eql(Base64.strict_encode64({ alg: alg }.to_json)) - expect(body).to eql(Base64.strict_encode64(payload.to_json)) - - # Validate signature is made of up header and body of JWT - translated_alg = alg.gsub('PS', 'sha') - valid_signature = data[:rsa_public].verify_pss( - translated_alg, - JWT::Base64.url_decode(signature), - [header, body].join('.'), - salt_length: :auto, - mgf1_hash: translated_alg - ) - expect(valid_signature).to be true - end - - it 'should decode a valid token' do - jwt_payload, header = JWT.decode data[alg], data[:rsa_public], true, algorithm: alg - - expect(header['alg']).to eq alg - expect(jwt_payload).to eq payload - end - - it 'wrong key should raise JWT::DecodeError' do - expect do - JWT.decode data[alg], wrong_key - end.to raise_error JWT::DecodeError - end - - it 'wrong key and verify = false should not raise JWT::DecodeError' do - expect do - JWT.decode data[alg], wrong_key, false - end.not_to raise_error - end - end - end - - context 'Invalid' do - it 'algorithm should raise DecodeError' do - expect do - JWT.encode payload, 'secret', 'HS255' - end.to raise_error JWT::EncodeError - end - - it 'raises "No verification key available" error' do - token = JWT.encode({}, 'foo') - expect { JWT.decode(token, nil, true) }.to raise_error(JWT::DecodeError, 'No verification key available') - end - - it 'ECDSA curve_name should raise JWT::IncorrectAlgorithm' do - key = OpenSSL::PKey::EC.generate('secp256k1') - - expect do - JWT.encode payload, key, 'ES256' - end.to raise_error JWT::IncorrectAlgorithm - - token = JWT.encode payload, data['ES256_private'], 'ES256' - - expect do - JWT.decode token, key - end.to raise_error JWT::IncorrectAlgorithm - end - end - - context 'Verify' do - context 'when key given as an array with multiple possible keys' do - let(:payload) { { 'data' => 'data' } } - let(:token) { JWT.encode(payload, secret, 'HS256') } - let(:secret) { 'hmac_secret' } - - it 'should be able to verify signature when block returns multiple keys' do - decoded_token = JWT.decode(token, nil, true, { algorithm: 'HS256' }) do - ['not_the_secret', secret] - end - expect(decoded_token.first).to eq(payload) - end - - it 'should be able to verify signature when multiple keys given as a parameter' do - decoded_token = JWT.decode(token, ['not_the_secret', secret], true, { algorithm: 'HS256' }) - expect(decoded_token.first).to eq(payload) - end - - it 'should fail if only invalid keys are given' do - expect do - JWT.decode(token, %w[not_the_secret not_the_secret_2], true, { algorithm: 'HS256' }) - end.to raise_error(JWT::VerificationError, 'Signature verification failed') - end - end - - context 'when encoded payload is used to extract key through find_key' do - it 'should be able to find a key using the block passed to decode' do - payload_data = { key: 'secret' } - token = JWT.encode payload_data, data[:secret], 'HS256' - - expect do - JWT.decode(token, nil, true, { algorithm: 'HS256' }) do |_headers, payload| - data[payload['key'].to_sym] - end - end.not_to raise_error - end - - it 'should be able to verify signature when block returns multiple keys' do - iss = 'My_Awesome_Company' - iss_payload = { data: 'data', iss: iss } - - secrets = { iss => ['hmac_secret2', data[:secret]] } - - token = JWT.encode iss_payload, data[:secret], 'HS256' - - expect do - JWT.decode(token, nil, true, { iss: iss, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload| - secrets[payload['iss']] - end - end.not_to raise_error - end - - it 'should be able to find a key using the block passed to decode with iss verification' do - iss = 'My_Awesome_Company' - iss_payload = { data: 'data', iss: iss } - - secrets = { iss => data[:secret] } - - token = JWT.encode iss_payload, data[:secret], 'HS256' - - expect do - JWT.decode(token, nil, true, { iss: iss, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload| - secrets[payload['iss']] - end - end.not_to raise_error - end - - it 'should be able to verify signature when block returns multiple keys with iss verification' do - iss = 'My_Awesome_Company' - iss_payload = { data: 'data', iss: iss } - - secrets = { iss => ['hmac_secret2', data[:secret]] } - - token = JWT.encode iss_payload, data[:secret], 'HS256' - - expect do - JWT.decode(token, nil, true, { iss: iss, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload| - secrets[payload['iss']] - end - end.not_to raise_error - end - - it 'should be able to find a key using a block with multiple issuers' do - issuers = %w[My_Awesome_Company1 My_Awesome_Company2] - iss_payload = { data: 'data', iss: issuers.first } - - secrets = { issuers.first => data[:secret], issuers.last => 'hmac_secret2' } - - token = JWT.encode iss_payload, data[:secret], 'HS256' - - expect do - JWT.decode(token, nil, true, { iss: issuers, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload| - secrets[payload['iss']] - end - end.not_to raise_error - end - - it 'should be able to verify signature when block returns multiple keys with multiple issuers' do - issuers = %w[My_Awesome_Company1 My_Awesome_Company2] - iss_payload = { data: 'data', iss: issuers.first } - - secrets = { issuers.first => [data[:secret], 'hmac_secret1'], issuers.last => 'hmac_secret2' } - - token = JWT.encode iss_payload, data[:secret], 'HS256' - - expect do - JWT.decode(token, nil, true, { iss: issuers, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload| - secrets[payload['iss']] - end - end.not_to raise_error - end - end - - context 'algorithm' do - it 'should raise JWT::IncorrectAlgorithm on mismatch' do - token = JWT.encode payload, data[:secret], 'HS256' - - expect do - JWT.decode token, data[:secret], true, algorithm: 'HS384' - end.to raise_error JWT::IncorrectAlgorithm - - expect do - JWT.decode token, data[:secret], true, algorithm: 'HS256' - end.not_to raise_error - end - - it 'should raise JWT::IncorrectAlgorithm on mismatch prior to kid public key network call' do - token = JWT.encode payload, data[:rsa_private], 'RS256' - - expect do - JWT.decode(token, nil, true, { algorithms: ['RS384'] }) do |_, _| - # unsuccessful keyfinder public key network call here - end - end.to raise_error JWT::IncorrectAlgorithm - - expect do - JWT.decode(token, nil, true, { 'algorithms' => ['RS384'] }) do |_, _| - # unsuccessful keyfinder public key network call here - end - end.to raise_error JWT::IncorrectAlgorithm - end - - it 'raises error when keyfinder does not find anything' do - token = JWT.encode(payload, 'secret', 'HS256') - - expect do - JWT.decode(token, nil, true, algorithm: 'HS256') do - nil - end - end.to raise_error JWT::DecodeError, 'No verification key available' - end - - it 'should raise JWT::IncorrectAlgorithm when algorithms array does not contain algorithm' do - token = JWT.encode payload, data[:secret], 'HS512' - - expect do - JWT.decode token, data[:secret], true, algorithms: ['HS384'] - end.to raise_error JWT::IncorrectAlgorithm - - expect do - JWT.decode token, data[:secret], true, 'algorithms' => ['HS384'] - end.to raise_error JWT::IncorrectAlgorithm - - expect do - JWT.decode token, data[:secret], true, algorithms: %w[HS512 HS384] - end.not_to raise_error - - expect do - JWT.decode token, data[:secret], true, 'algorithms' => %w[HS512 HS384] - end.not_to raise_error - end - - context 'no algorithm provided' do - it 'should use the default decode algorithm' do - token = JWT.encode payload, data[:rsa_public].to_s - - jwt_payload, header = JWT.decode token, data[:rsa_public].to_s - - expect(header['alg']).to eq 'HS256' - expect(jwt_payload).to eq payload - end - end - - context 'token is missing algorithm' do - it 'should raise JWT::IncorrectAlgorithm' do - expect do - JWT.decode data[:empty_token] - end.to raise_error JWT::IncorrectAlgorithm - end - - context 'invalid header format' do - it 'should raise JWT::DecodeError' do - expect do - JWT.decode data[:invalid_header_token] - end.to raise_error JWT::DecodeError - end - end - - context '2-segment token' do - it 'should raise JWT::IncorrectAlgorithm' do - expect do - JWT.decode data[:empty_token_2_segment] - end.to raise_error JWT::DecodeError - end - end - end - end - - context 'issuer claim' do - let(:iss) { 'ruby-jwt-gem' } - let(:invalid_token) { JWT.encode payload, data[:secret] } - - let(:token) do - iss_payload = payload.merge(iss: iss) - JWT.encode iss_payload, data[:secret] - end - - it 'if verify_iss is set to false (default option) should not raise JWT::InvalidIssuerError' do - expect do - JWT.decode token, data[:secret], true, iss: iss, algorithm: 'HS256' - end.not_to raise_error - end - - context 'when verify_iss is set to true and no issues given' do - it 'does not raise' do - expect do - JWT.decode(token, data[:secret], true, verify_iss: true, algorithm: 'HS256') - end.not_to raise_error - end - end - end - - context 'audience claim' do - let(:token) { JWT.encode(payload, data[:secret]) } - - context 'when verify_aud is set to true and no audience given' do - it 'does not raise' do - expect do - JWT.decode(token, data[:secret], true, verify_aud: true, algorithm: 'HS256') - end.not_to raise_error - end - end - end - - context 'claim verification order' do - let(:token) { JWT.encode({ nbf: Time.now.to_i + 100 }, 'secret') } - - context 'when two claims are invalid' do - it 'depends on the order of the parameters what error is raised' do - expect { JWT.decode(token, 'secret', true, { verify_jti: true, verify_not_before: true }) }.to raise_error(JWT::ImmatureSignature, 'Signature nbf has not been reached') - end - end - end - end - - context 'a token with no segments' do - it 'raises JWT::DecodeError' do - expect { JWT.decode('ThisIsNotAValidJWTToken', nil, true) }.to raise_error(JWT::DecodeError, 'Not enough or too many segments') - end - end - - context 'a token with not enough segments' do - it 'raises JWT::DecodeError' do - token = JWT.encode('ThisIsNotAValidJWTToken', 'secret').split('.').slice(1, 2).join - expect { JWT.decode(token, nil, true) }.to raise_error(JWT::DecodeError, 'Not enough or too many segments') - end - end - - context 'a token with not too many segments' do - it 'raises JWT::DecodeError' do - expect { JWT.decode('ThisIsNotAValidJWTToken.second.third.signature', nil, true) }.to raise_error(JWT::DecodeError, 'Not enough or too many segments') - end - end - - context 'a token with invalid Base64 segments' do - it 'raises JWT::Base64DecodeError' do - expect { JWT.decode('hello.there.world') }.to raise_error(JWT::Base64DecodeError, 'Invalid base64 encoding') - end - end - - context 'a token with two segments but does not require verifying' do - it 'raises something else than "Not enough or too many segments"' do - expect { JWT.decode('ThisIsNotAValidJWTToken.second', nil, false) }.to raise_error(JWT::Base64DecodeError, 'Invalid base64 encoding') - end - end - - it 'should not verify token even if the payload has claims' do - head = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9' - load = 'eyJ1c2VyX2lkIjo1NCwiZXhwIjoxNTA0MzkwODA0fQ' - sign = 'Skpi6FfYMbZ-DwW9ocyRIosNMdPMAIWRLYxRO68GTQk' - - expect do - JWT.decode([head, load, sign].join('.'), '', false) - end.not_to raise_error - end - - it 'should not raise InvalidPayload exception if payload is an array' do - expect do - JWT.encode(%w[my payload], 'secret') - end.not_to raise_error - end - - it 'should encode string payloads' do - expect do - JWT.encode 'Hello World', 'secret' - end.not_to raise_error - end - - context 'when the alg value is given as a header parameter' do - it 'overrides the actual algorithm used' do - headers = JSON.parse(JWT::Base64.url_decode(JWT.encode('Hello World', 'secret', 'HS256', { alg: 'HS123' }).split('.').first)) - expect(headers['alg']).to eq('HS123') - end - - it 'should generate the same token' do - expect(JWT.encode('Hello World', 'secret', 'HS256', { alg: 'HS256' })).to eq JWT.encode('Hello World', 'secret', 'HS256') - end - end - - context 'when hmac algorithm is used without secret key' do - it 'encodes payload' do - pending 'Different behaviour on OpenSSL 3.0 (https://github.com/openssl/openssl/issues/13089)' if JWT.openssl_3_hmac_empty_key_regression? - payload = { a: 1, b: 'b' } - - token = JWT.encode(payload, '', 'HS256') - - expect do - token_without_secret = JWT.encode(payload, nil, 'HS256') - expect(token).to eq(token_without_secret) - end.not_to raise_error - end - end - - context 'algorithm case insensitivity' do - let(:payload) { { 'a' => 1, 'b' => 'b' } } - - it 'ignores algorithm casing during encode/decode' do - enc = JWT.encode(payload, 'secret', 'hs256') - expect(JWT.decode(enc, 'secret')).to eq([payload, { 'alg' => 'HS256' }]) - - enc = JWT.encode(payload, data[:rsa_private], 'rs512') - expect(JWT.decode(enc, data[:rsa_public], true, algorithm: 'RS512')).to eq([payload, { 'alg' => 'RS512' }]) - - enc = JWT.encode(payload, data[:rsa_private], 'RS512') - expect(JWT.decode(enc, data[:rsa_public], true, algorithm: 'rs512')).to eq([payload, { 'alg' => 'RS512' }]) - end - - it 'raises error for invalid algorithm' do - expect do - JWT.encode(payload, '', 'xyz') - end.to raise_error(JWT::EncodeError) - end - end - - describe '::JWT.decode with verify_iat parameter' do - let!(:time_now) { Time.now } - let(:token) { JWT.encode({ pay: 'load', iat: iat }, 'secret', 'HS256') } - - subject(:decoded_token) { JWT.decode(token, 'secret', true, verify_iat: true) } - - before { allow(Time).to receive(:now) { time_now } } - - context 'when iat is exactly the same as Time.now and iat is given as a float' do - let(:iat) { time_now.to_f } - it 'considers iat valid' do - expect(decoded_token).to be_an(Array) - end - end - - context 'when iat is exactly the same as Time.now and iat is given as floored integer' do - let(:iat) { time_now.to_f.floor } - it 'considers iat valid' do - expect(decoded_token).to be_an(Array) - end - end - - context 'when iat is 1 second before Time.now' do - let(:iat) { time_now.to_i + 1 } - it 'raises an error' do - expect { decoded_token }.to raise_error(JWT::InvalidIatError, 'Invalid iat') - end - end - end - - describe '::JWT.decode with x5c parameter' do - let(:alg) { 'RS256' } - let(:root_certificates) { [instance_double('OpenSSL::X509::Certificate')] } - let(:key_finder) { instance_double('::JWT::X5cKeyFinder') } - - before do - expect(JWT::X5cKeyFinder).to receive(:new).with(root_certificates, nil).and_return(key_finder) - expect(key_finder).to receive(:from).and_return(data[:rsa_public]) - end - subject(:decoded_token) { JWT.decode(data[alg], nil, true, algorithm: alg, x5c: { root_certificates: root_certificates }) } - - it 'calls X5cKeyFinder#from to verify the signature and return the payload' do - jwt_payload, header = decoded_token - - expect(header['alg']).to eq alg - expect(jwt_payload).to eq payload - end - end - - describe 'when keyfinder given with 1 argument' do - let(:token) { JWT.encode(payload, 'HS256', 'HS256') } - it 'decodes the token' do - expect(JWT.decode(token, nil, true, algorithm: 'HS256') { |header| header['alg'] }).to include(payload) - end - end - - describe 'when keyfinder given with 2 arguments' do - let(:token) { JWT.encode(payload, payload['user_id'], 'HS256') } - it 'decodes the token' do - expect(JWT.decode(token, nil, true, algorithm: 'HS256') { |_header, payload| payload['user_id'] }).to include(payload) - end - end - - describe 'when keyfinder given with 3 arguments' do - let(:token) { JWT.encode(payload, 'HS256', 'HS256') } - it 'decodes the token but does not pass the payload' do - expect(JWT.decode(token, nil, true, algorithm: 'HS256') do |header, token_payload, nothing| - expect(token_payload).to eq(nil) # This behaviour is not correct, the payload should be available in the keyfinder - expect(nothing).to eq(nil) - header['alg'] - end).to include(payload) - end - end - - describe 'when none token is and decoding without key and with verification' do - let(:none_token) { JWT.encode(payload, nil, 'none') } - it 'decodes the token' do - expect(JWT.decode(none_token, nil, true, algorithms: 'none')).to eq([payload, { 'alg' => 'none' }]) - end - end - - describe 'when none token is decoded with a key given' do - let(:none_token) { JWT.encode(payload, nil, 'none') } - it 'decodes the token' do - expect(JWT.decode(none_token, 'key', true, algorithms: 'none')).to eq([payload, { 'alg' => 'none' }]) - end - end - - describe 'when none token is decoded without verify' do - let(:none_token) { JWT.encode(payload, nil, 'none') } - it 'decodes the token' do - expect(JWT.decode(none_token, 'key', false)).to eq([payload, { 'alg' => 'none' }]) - end - end - - describe 'when token signed with nil and decoded with nil' do - let(:no_key_token) { JWT.encode(payload, nil, 'HS512') } - it 'raises JWT::DecodeError' do - pending 'Different behaviour on OpenSSL 3.0 (https://github.com/openssl/openssl/issues/13089)' if JWT.openssl_3_hmac_empty_key_regression? - expect { JWT.decode(no_key_token, nil, true, algorithms: 'HS512') }.to raise_error(JWT::DecodeError, 'No verification key available') - end - end - - context 'when token ends with a newline char' do - let(:token) { "#{JWT.encode(payload, 'secret', 'HS256')}\n" } - it 'raises an error' do - expect { JWT.decode(token, 'secret', true, algorithm: 'HS256') }.to raise_error(JWT::Base64DecodeError, 'Invalid base64 encoding') - end - end - - context 'when token ends with a newline char and strict_decoding enabled' do - let(:token) { "#{JWT.encode(payload, 'secret', 'HS256')}\n" } - before do - JWT.configuration.strict_base64_decoding = true - end - - it 'raises JWT::DecodeError' do - expect { JWT.decode(token, 'secret', true, algorithm: 'HS256') }.to raise_error(JWT::DecodeError, 'Invalid base64 encoding') - end - end - - context 'when multiple algorithms given' do - let(:token) { JWT.encode(payload, 'secret', 'HS256') } - - it 'starts trying with the algorithm referred in the header' do - allow(JWT::JWA::Rsa).to receive(:verify) - JWT.decode(token, 'secret', true, algorithm: %w[RS512 HS256]) - expect(JWT::JWA::Rsa).not_to have_received(:verify) - end - end - - context 'when keyfinder resolves to multiple keys and multiple algorithms given' do - let(:iss_key_mappings) do - { - 'ES256' => [data['ES256_public_v2'], data['ES256_public']], - 'HS256' => data['HS256'] - } - end - - context 'with issue with ES256 keys' do - it 'tries until the first match' do - token = JWT.encode(payload, data['ES256_private'], 'ES256', 'iss' => 'ES256') - result = JWT.decode(token, nil, true, algorithm: %w[ES256 HS256]) do |header, _| - iss_key_mappings[header['iss']] - end - - expect(result).to include(payload) - end - - it 'tries until the first match' do - token = JWT.encode(payload, data['ES256_private_v2'], 'ES256', 'iss' => 'ES256') - result = JWT.decode(token, nil, true, algorithm: %w[ES256 HS256]) do |header, _| - iss_key_mappings[header['iss']] - end - - expect(result).to include(payload) - end - end - - context 'with issue with HS256 keys' do - it 'tries until the first match' do - token = JWT.encode(payload, data['HS256'], 'HS256', 'iss' => 'HS256') - result = JWT.decode(token, nil, true, algorithm: %w[ES256 HS256]) do |header, _| - iss_key_mappings[header['iss']] - end - - expect(result).to include(payload) - end - end - end - - context 'when token is missing the alg header' do - let(:token) { 'e30.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.DIKUOt1lwwzWSPBf508IYqk0KzC2PL97OZc6pECzE1I' } - - it 'raises JWT::IncorrectAlgorithm error' do - expect { JWT.decode(token, 'secret', true, algorithm: 'HS256') }.to raise_error(JWT::IncorrectAlgorithm, 'Token is missing alg header') - end - end - - context 'when token has null as the alg header' do - let(:token) { 'eyJhbGciOm51bGx9.eyJwYXkiOiJsb2FkIn0.pizVPWJMK-GUuXXEcQD_faZGnZqz_6wKZpoGO4RdqbY' } - it 'raises JWT::IncorrectAlgorithm error' do - expect { JWT.decode(token, 'secret', true, algorithm: 'HS256') }.to raise_error(JWT::IncorrectAlgorithm, 'Token is missing alg header') - end - end - - context 'when the alg is invalid' do - let(:token) { 'eyJhbGciOiJIUzI1NiJ9.eyJwYXkiOiJsb2FkIn0.ZpAhTTtuo-CmbgT6-95NaM_wFckKeyI157baZ29H41o' } - - it 'raises JWT::IncorrectAlgorithm error' do - expect { JWT.decode(token, 'secret', true, algorithm: 'invalid-HS256') }.to raise_error(JWT::IncorrectAlgorithm, 'Expected a different algorithm') - end - end - - context 'when algorithm is a custom class' do - let(:custom_algorithm) do - Class.new do - include JWT::JWA::SigningAlgorithm - - def initialize(signature: 'custom_signature', alg: 'custom') - @signature = signature - @alg = alg - end - - def sign(*) - @signature - end - - def verify(data:, signature:, verification_key:) # rubocop:disable Lint/UnusedMethodArgument - signature == @signature - end - end - end - - let(:token) { JWT.encode(payload, 'secret', custom_algorithm.new) } - let(:expected_token) { 'eyJhbGciOiJjdXN0b20ifQ.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.Y3VzdG9tX3NpZ25hdHVyZQ' } - - it 'can be used for encoding' do - expect(token).to eq(expected_token) - end - - it 'can be used for decoding' do - expect(JWT.decode(token, 'secret', true, algorithm: custom_algorithm.new)).to eq([payload, { 'alg' => 'custom' }]) - end - - context 'when multiple custom algorithms are given for decoding' do - it 'tries until the first match' do - expect(JWT.decode(token, 'secret', true, algorithms: [custom_algorithm.new(signature: 'not_this'), custom_algorithm.new])).to eq([payload, { 'alg' => 'custom' }]) - end - end - - context 'when class has custom header method' do - before do - custom_algorithm.class_eval do - def header(*) - { 'alg' => alg, 'foo' => 'bar' } - end - end - end - - it 'uses the provided header' do - expect(JWT.decode(token, 'secret', true, algorithm: custom_algorithm.new)).to eq([payload, { 'alg' => 'custom', 'foo' => 'bar' }]) - end - end - - context 'when class is not utilizing the ::JWT::JWA::SigningAlgorithm module' do - let(:custom_algorithm) do - Class.new do - attr_reader :alg - - def initialize(signature: 'custom_signature', alg: 'custom') - @signature = signature - @alg = alg - end - - def header(*) - { 'alg' => @alg, 'foo' => 'bar' } - end - - def sign(*) - @signature - end - - def verify(*) - true - end - end - end - - it 'raises an error' do - expect { token }.to raise_error(ArgumentError, 'Custom algorithms are required to include JWT::JWA::SigningAlgorithm') - end - end - - context 'when alg is not matching' do - it 'fails the validation process' do - expect { JWT.decode(token, 'secret', true, algorithms: custom_algorithm.new(alg: 'not_a_match')) }.to raise_error(JWT::IncorrectAlgorithm, 'Expected a different algorithm') - end - end - - context 'when signature is not matching' do - it 'fails the validation process' do - expect { JWT.decode(token, 'secret', true, algorithms: custom_algorithm.new(signature: 'not_a_match')) }.to raise_error(JWT::VerificationError, 'Signature verification failed') - end - end - - context 'when #sign method is missing' do - before do - custom_algorithm.instance_eval do - remove_method :sign - end - end - - it 'raises an error on encoding' do - expect { token }.to raise_error(JWT::EncodeError, /missing the sign method/) - end - - it 'allows decoding' do - expect(JWT.decode(expected_token, 'secret', true, algorithm: custom_algorithm.new)).to eq([payload, { 'alg' => 'custom' }]) - end - end - - context 'when #verify method is missing' do - before do - custom_algorithm.instance_eval do - remove_method :verify - end - end - - it 'can be used for encoding' do - expect(token).to eq(expected_token) - end - - it 'raises error on decoding' do - expect { JWT.decode(expected_token, 'secret', true, algorithm: custom_algorithm.new) }.to raise_error(JWT::DecodeError, /missing the verify method/) - end - end - end -end diff --git a/spec/jwt/token_spec.rb b/spec/jwt/token_spec.rb deleted file mode 100644 index 3f4438419..000000000 --- a/spec/jwt/token_spec.rb +++ /dev/null @@ -1,151 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::Token do - let(:payload) { { 'pay' => 'load' } } - let(:header) { {} } - - subject(:token) { described_class.new(payload: payload, header: header) } - - describe '#sign!' do - it 'signs the token' do - token.sign!(algorithm: 'HS256', key: 'secret') - - expect(JWT::EncodedToken.new(token.jwt).valid_signature?(algorithm: 'HS256', key: 'secret')).to be(true) - end - - context 'when signed twice' do - before do - token.sign!(algorithm: 'HS256', key: 'secret') - end - - it 'raises' do - expect { token.sign!(algorithm: 'HS256', key: 'secret') }.to raise_error(JWT::EncodeError) - end - end - - context 'when RSA JWK is given as key' do - let(:jwk) { JWT::JWK::RSA.new(OpenSSL::PKey::RSA.new(2048), alg: 'RS256') } - - it 'signs the token' do - token.sign!(key: jwk, algorithm: []) # any algorithm is fine here - - expect(JWT::EncodedToken.new(token.jwt).valid_signature?(algorithm: 'RS256', key: jwk.verify_key)).to be(true) - end - - context 'with algorithm provided in sign call' do - it 'signs the token' do - token.sign!(algorithm: %w[RS256 RS512], key: jwk) - - expect(JWT::EncodedToken.new(token.jwt).valid_signature?(algorithm: 'RS256', key: jwk.verify_key)).to be(true) - end - end - - context 'with mismatching algorithm provided in sign call' do - it 'signs the token' do - expect { token.sign!(algorithm: %w[RS384 RS512], key: jwk) }.to raise_error(JWT::DecodeError, 'Provided JWKs do not support one of the specified algorithms: RS384, RS512') - end - end - end - - context 'when string key is given but not algorithm' do - it 'raises an error' do - expect { token.sign!(key: 'secret') }.to raise_error(ArgumentError, /missing keyword/) - end - end - end - - context 'when EC JWK is given as key' do - let(:jwk) { JWT::JWK::EC.new(test_pkey('ec384-private.pem')) } - - it 'signs the token' do - token.sign!(key: jwk, algorithm: []) - - expect(JWT::EncodedToken.new(token.jwt).valid_signature?(algorithm: [], key: jwk)).to be(true) - end - end - - describe '#jwt' do - context 'when token is signed' do - before do - token.sign!(algorithm: 'HS256', key: 'secret') - end - - it 'returns a signed and encoded token' do - expect(token.jwt).to eq('eyJhbGciOiJIUzI1NiJ9.eyJwYXkiOiJsb2FkIn0.UEhDY1Qlj29ammxuVRA_-gBah4qTy5FngIWg0yEAlC0') - expect(JWT.decode(token.jwt, 'secret', true, algorithm: 'HS256')).to eq([{ 'pay' => 'load' }, { 'alg' => 'HS256' }]) - end - end - - context 'when token is not signed' do - it 'returns a signed and encoded token' do - expect { token.jwt }.to raise_error(JWT::EncodeError) - end - end - - context 'when alg is given in header' do - let(:header) { { 'alg' => 'HS123' } } - - before do - token.sign!(algorithm: 'HS256', key: 'secret') - end - - it 'returns a signed and encoded token' do - expect(JWT::EncodedToken.new(token.jwt).header).to eq({ 'alg' => 'HS123' }) - end - end - end - - describe '#detach_payload!' do - context 'before token is signed' do - it 'detaches the payload' do - token.detach_payload! - token.sign!(algorithm: 'HS256', key: 'secret') - expect(token.jwt).to eq('eyJhbGciOiJIUzI1NiJ9..UEhDY1Qlj29ammxuVRA_-gBah4qTy5FngIWg0yEAlC0') - end - end - end - - describe '#verify_claims!' do - context 'when required_claims is passed' do - it 'raises error' do - expect { token.verify_claims!(required: ['exp']) }.to raise_error(JWT::MissingRequiredClaim, 'Missing required claim exp') - end - end - end - - describe '#valid_claims?' do - context 'exp claim' do - let(:payload) { { 'exp' => Time.now.to_i - 10, 'pay' => 'load' } } - - context 'when claim is valid' do - it 'returns true' do - expect(token.valid_claims?(exp: { leeway: 1000 })).to be(true) - end - end - - context 'when claim is invalid' do - it 'returns true' do - expect(token.valid_claims?(:exp)).to be(false) - end - end - end - end - - describe '#claim_errors' do - context 'exp claim' do - let(:payload) { { 'exp' => Time.now.to_i - 10, 'pay' => 'load' } } - - context 'when claim is valid' do - it 'returns empty array' do - expect(token.claim_errors(exp: { leeway: 1000 })).to be_empty - end - end - - context 'when claim is invalid' do - it 'returns array with error objects' do - expect(token.claim_errors(:exp).map(&:message)).to eq(['Signature has expired']) - end - end - end - end -end diff --git a/spec/jwt/version_spec.rb b/spec/jwt/version_spec.rb deleted file mode 100644 index e7611c6e5..000000000 --- a/spec/jwt/version_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT do - describe '.gem_version' do - it 'returns the gem version' do - expect(described_class.gem_version).to eq(Gem::Version.new(JWT::VERSION::STRING)) - end - end - describe 'VERSION constants' do - it 'has a MAJOR version' do - expect(JWT::VERSION::MAJOR).to be_a(Integer) - end - - it 'has a MINOR version' do - expect(JWT::VERSION::MINOR).to be_a(Integer) - end - - it 'has a TINY version' do - expect(JWT::VERSION::TINY).to be_a(Integer) - end - - it 'has a PRE version' do - expect(JWT::VERSION::PRE).to be_a(String).or be_nil - end - - it 'has a STRING version' do - expect(JWT::VERSION::STRING).to be_a(String) - end - end -end diff --git a/spec/jwt/x5c_key_finder_spec.rb b/spec/jwt/x5c_key_finder_spec.rb deleted file mode 100644 index 7ebfc4f74..000000000 --- a/spec/jwt/x5c_key_finder_spec.rb +++ /dev/null @@ -1,200 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe JWT::X5cKeyFinder do - let(:root_key) { test_pkey('rsa-2048-private.pem') } - let(:root_dn) { OpenSSL::X509::Name.parse('/DC=org/DC=fake-ca/CN=Fake CA') } - let(:root_certificate) { generate_root_cert(root_dn, root_key) } - let(:leaf_key) { generate_key } - let(:leaf_dn) { OpenSSL::X509::Name.parse('/DC=org/DC=fake/CN=Fake') } - let(:leaf_serial) { 2 } - let(:leaf_not_after) { Time.now + 3600 } - let(:leaf_signing_key) { root_key } - let(:leaf_certificate) do - cert = generate_cert( - leaf_dn, - leaf_key.public_key, - leaf_serial, - issuer: root_certificate, - not_after: leaf_not_after - ) - ef = OpenSSL::X509::ExtensionFactory.new - ef.config = OpenSSL::Config.parse(leaf_cdp) - ef.subject_certificate = cert - cert.add_extension(ef.create_extension('crlDistributionPoints', '@crlDistPts')) - cert.sign(leaf_signing_key, 'sha256') - cert - end - let(:leaf_cdp) { <<-_CNF_ } - [crlDistPts] - URI.1 = http://www.example.com/crl - _CNF_ - - let(:crl) { issue_crl([], issuer: root_certificate, issuer_key: root_key) } - - let(:x5c_header) { [Base64.strict_encode64(leaf_certificate.to_der)] } - subject(:keyfinder) { described_class.new([root_certificate], [crl]).from(x5c_header) } - - it 'returns the public key from a certificate that is signed by trusted roots and not revoked' do - expect(keyfinder).to be_a(OpenSSL::PKey::RSA) - expect(keyfinder.public_key.to_der).to eq(leaf_certificate.public_key.to_der) - end - - context 'already parsed certificates' do - let(:x5c_header) { [leaf_certificate] } - - it 'returns the public key from a certificate that is signed by trusted roots and not revoked' do - expect(keyfinder).to be_a(OpenSSL::PKey::RSA) - expect(keyfinder.public_key.to_der).to eq(leaf_certificate.public_key.to_der) - end - end - - context '::JWT.decode' do - let(:token_payload) { { 'data' => 'something' } } - let(:encoded_token) { JWT.encode(token_payload, leaf_key, 'RS256', { 'x5c' => x5c_header }) } - let(:decoded_payload) do - JWT.decode(encoded_token, nil, true, algorithms: ['RS256'], x5c: { root_certificates: [root_certificate], crls: [crl] }).first - end - - it 'returns the encoded payload after successful certificate path verification' do - expect(decoded_payload).to eq(token_payload) - end - end - - context 'certificate' do - context 'expired' do - let(:leaf_not_after) { Time.now - 3600 } - - it 'raises an error' do - error = 'Certificate verification failed: certificate has expired. Certificate subject: /DC=org/DC=fake/CN=Fake.' - expect { keyfinder }.to raise_error(JWT::VerificationError, error) - end - end - - context 'signature could not be verified with the given trusted roots' do - let(:leaf_signing_key) { generate_key } - - it 'raises an error' do - error = 'Certificate verification failed: certificate signature failure. Certificate subject: /DC=org/DC=fake/CN=Fake.' - expect { keyfinder }.to raise_error(JWT::VerificationError, error) - end - end - - context 'could not be chained to a trusted root certificate' do - context 'given an array' do - subject(:keyfinder) { described_class.new([], [crl]).from(x5c_header) } - - it 'raises a verification error' do - error = 'Certificate verification failed: unable to get local issuer certificate. Certificate subject: /DC=org/DC=fake/CN=Fake.' - expect { keyfinder }.to raise_error(JWT::VerificationError, error) - end - end - - context 'given nil' do - subject(:keyfinder) { described_class.new(nil, [crl]).from(x5c_header) } - - it 'raises a decode error' do - error = 'Root certificates must be specified' - expect { keyfinder }.to raise_error(ArgumentError, error) - end - end - end - - context 'revoked' do - let(:revocation) { [leaf_serial, Time.now - 60, 1] } - let(:crl) { issue_crl([revocation], issuer: root_certificate, issuer_key: root_key) } - - it 'raises an error' do - error = 'Certificate verification failed: certificate revoked. Certificate subject: /DC=org/DC=fake/CN=Fake.' - expect { keyfinder }.to raise_error(JWT::VerificationError, error) - end - end - end - - context 'CRL' do - context 'expired' do - let(:next_up) { Time.now - 60 } - let(:crl) { issue_crl([], next_up: next_up, issuer: root_certificate, issuer_key: root_key) } - - it 'raises an error' do - error = 'Certificate verification failed: CRL has expired. Certificate subject: /DC=org/DC=fake/CN=Fake.' - expect { keyfinder }.to raise_error(JWT::VerificationError, error) - end - end - - context 'signature could not be verified with the given trusted roots' do - let(:crl) { issue_crl([], issuer: root_certificate, issuer_key: generate_key) } - - it 'raises an error' do - error = 'Certificate verification failed: CRL signature failure. Certificate subject: /DC=org/DC=fake/CN=Fake.' - expect { keyfinder }.to raise_error(JWT::VerificationError, error) - end - end - - context 'not given' do - subject(:keyfinder) { described_class.new([root_certificate], nil).from(x5c_header) } - - it 'raises an error' do - error = 'Certificate verification failed: unable to get certificate CRL. Certificate subject: /DC=org/DC=fake/CN=Fake.' - expect { keyfinder }.to raise_error(JWT::VerificationError, error) - end - end - end - - private - - def generate_key - OpenSSL::PKey::RSA.new(2048) - end - - def generate_root_cert(root_dn, root_key) - cert = generate_cert(root_dn, root_key, 1) - ef = OpenSSL::X509::ExtensionFactory.new - cert.add_extension(ef.create_extension('basicConstraints', 'CA:TRUE', true)) - cert.sign(root_key, 'sha256') - cert - end - - def generate_cert(subject, key, serial, issuer: nil, not_after: nil) - cert = OpenSSL::X509::Certificate.new - issuer ||= cert - cert.version = 2 - cert.serial = serial - cert.subject = subject - cert.issuer = issuer.subject - cert.public_key = key - now = Time.now - cert.not_before = now - 3600 - cert.not_after = not_after || (now + 3600) - cert - end - - def issue_crl(revocations, issuer:, issuer_key:, next_up: nil) - crl = OpenSSL::X509::CRL.new - crl.issuer = issuer.subject - crl.version = 1 - now = Time.now - crl.last_update = now - 3600 - crl.next_update = next_up || (now + 3600) - - revocations.each do |rserial, time, reason_code| - revoked = build_revoked(rserial, time, reason_code) - crl.add_revoked(revoked) - end - - crlnum = OpenSSL::ASN1::Integer(1) - crl.add_extension(OpenSSL::X509::Extension.new('crlNumber', crlnum)) - - crl.sign(issuer_key, 'sha256') - crl - end - - def build_revoked(rserial, time, reason_code) - revoked = OpenSSL::X509::Revoked.new - revoked.serial = rserial - revoked.time = time - enum = OpenSSL::ASN1::Enumerated(reason_code) - ext = OpenSSL::X509::Extension.new('CRLReason', enum) - revoked.add_extension(ext) - revoked - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index c1992e730..000000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require 'rspec' -require 'simplecov' -require 'jwt' - -require_relative 'spec_support/test_keys' -require_relative 'spec_support/token' - -puts "OpenSSL::VERSION: #{OpenSSL::VERSION}" -puts "OpenSSL::OPENSSL_VERSION: #{OpenSSL::OPENSSL_VERSION}" -puts "OpenSSL::OPENSSL_LIBRARY_VERSION: #{OpenSSL::OPENSSL_LIBRARY_VERSION}\n\n" - -RSpec.configure do |config| - config.expect_with :rspec do |c| - c.syntax = :expect - end - config.include(SpecSupport::TestKeys) - - config.before(:example) do - JWT.configuration.reset! - JWT.configuration.deprecation_warnings = :warn - end - - config.run_all_when_everything_filtered = true - config.filter_run :focus - config.order = 'random' -end diff --git a/spec/spec_support/test_keys.rb b/spec/spec_support/test_keys.rb deleted file mode 100644 index 8931e2e04..000000000 --- a/spec/spec_support/test_keys.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module SpecSupport - module TestKeys - KEY_FIXTURE_PATH = File.join(__dir__, '..', 'fixtures', 'keys') - - def test_pkey(key) - TestKeys.keys[key] ||= read_pkey(key) - end - - def read_pkey(key) - OpenSSL::PKey.read(File.read(File.join(KEY_FIXTURE_PATH, key))) - end - - def self.keys - @keys ||= {} - end - end -end diff --git a/spec/spec_support/token.rb b/spec/spec_support/token.rb deleted file mode 100644 index e076aae1a..000000000 --- a/spec/spec_support/token.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module SpecSupport - Token = Struct.new(:payload, :header, keyword_init: true) -end diff --git a/top-level-namespace.html b/top-level-namespace.html new file mode 100644 index 000000000..7214198e3 --- /dev/null +++ b/top-level-namespace.html @@ -0,0 +1,110 @@ + + + + + + + Top Level Namespace + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
        + + +

        Top Level Namespace + + + +

        +
        + + + + + + + + + + + +
        + +

        Defined Under Namespace

        +

        + + + Modules: JWT + + + + +

        + + + + + + + + + +
        + + + +
        + + \ No newline at end of file