jwt 1.5.2 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +13 -5
- data/.codeclimate.yml +20 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/README.md +29 -11
- data/Rakefile +1 -18
- data/lib/jwt.rb +19 -75
- data/lib/jwt/decode.rb +56 -0
- data/lib/jwt/error.rb +12 -0
- data/lib/jwt/json.rb +9 -25
- data/lib/jwt/verify.rb +98 -0
- data/lib/jwt/version.rb +23 -0
- data/ruby-jwt.gemspec +29 -0
- data/spec/fixtures/certs/ec256-private.pem +8 -0
- data/spec/fixtures/certs/ec256-public.pem +4 -0
- data/spec/fixtures/certs/ec256-wrong-private.pem +8 -0
- data/spec/fixtures/certs/ec256-wrong-public.pem +4 -0
- data/spec/fixtures/certs/ec384-private.pem +9 -0
- data/spec/fixtures/certs/ec384-public.pem +5 -0
- data/spec/fixtures/certs/ec384-wrong-private.pem +9 -0
- data/spec/fixtures/certs/ec384-wrong-public.pem +5 -0
- data/spec/fixtures/certs/ec512-private.pem +10 -0
- data/spec/fixtures/certs/ec512-public.pem +6 -0
- data/spec/fixtures/certs/ec512-wrong-private.pem +10 -0
- data/spec/fixtures/certs/ec512-wrong-public.pem +6 -0
- data/spec/fixtures/certs/rsa-1024-private.pem +15 -0
- data/spec/fixtures/certs/rsa-1024-public.pem +6 -0
- data/spec/fixtures/certs/rsa-2048-private.pem +27 -0
- data/spec/fixtures/certs/rsa-2048-public.pem +9 -0
- data/spec/fixtures/certs/rsa-2048-wrong-private.pem +27 -0
- data/spec/fixtures/certs/rsa-2048-wrong-public.pem +9 -0
- data/spec/fixtures/certs/rsa-4096-private.pem +51 -0
- data/spec/fixtures/certs/rsa-4096-public.pem +14 -0
- data/spec/jwt/verify_spec.rb +175 -0
- data/spec/jwt_spec.rb +1 -181
- data/spec/spec_helper.rb +2 -3
- metadata +145 -28
- data/jwt.gemspec +0 -34
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,15 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
             | 
| 2 | 
            +
            !binary "U0hBMQ==":
         | 
| 3 | 
            +
              metadata.gz: !binary |-
         | 
| 4 | 
            +
                NDM3ZjQ5OWVjMGQ3NDYxZWRmZjAxNTQzZmU5YjlhODg4YzcwY2QzMg==
         | 
| 5 | 
            +
              data.tar.gz: !binary |-
         | 
| 6 | 
            +
                YmM3YWU5NTkxNDEzOGQyMTAzMTIyYzVmNWNhY2ZlMWU2NTFlZjliNQ==
         | 
| 5 7 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
             | 
| 8 | 
            +
              metadata.gz: !binary |-
         | 
| 9 | 
            +
                NzA3NWQ4ZjQ4OWEyNTY5ZjE5NGYzMjBhZDkzMmZhOTdmNzcwMmMxNWI5MmYz
         | 
| 10 | 
            +
                N2E3MmE5NmQ1ZjlhZTU2ZDc3NDYxYzIxZjhkMjJjOGE1NDI5MDI4MmVmN2Fi
         | 
| 11 | 
            +
                ZGExYWMzOGI3ZDAxNWE2NzdhOWRjNjkzZjAxMjRmMGM0NTIwZDU=
         | 
| 12 | 
            +
              data.tar.gz: !binary |-
         | 
| 13 | 
            +
                OGQxM2IyM2E1ZTUzM2QzZjBlMmZiYzBiMGU4OGM5YjI5NTU0YjA2ZWQ3MDY3
         | 
| 14 | 
            +
                MjQ0ZDMxNTEzMWE0NzUzYjAxOGQ2MTAwZTFiMmU5YmYzZDFjYTVhNTdhOGVm
         | 
| 15 | 
            +
                N2Q3Mjk0ODMxYWI3NDg3M2IwYzA5MmMwYTgzNzhjM2U5YTJkODI=
         | 
    
        data/.codeclimate.yml
    ADDED
    
    | @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            engines:
         | 
| 2 | 
            +
              rubocop:
         | 
| 3 | 
            +
                enabled: true
         | 
| 4 | 
            +
              golint:
         | 
| 5 | 
            +
                enabled: false
         | 
| 6 | 
            +
              gofmt:
         | 
| 7 | 
            +
                enabled: false
         | 
| 8 | 
            +
              eslint:
         | 
| 9 | 
            +
                enabled: false
         | 
| 10 | 
            +
              csslint:
         | 
| 11 | 
            +
                enabled: false
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ratings:
         | 
| 14 | 
            +
              paths:
         | 
| 15 | 
            +
                - lib/**
         | 
| 16 | 
            +
                - "**.rb"
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            exclude_paths:
         | 
| 19 | 
            +
              - spec/**/*
         | 
| 20 | 
            +
              - vendor/**/*
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.rspec
    ADDED
    
    
    
        data/.rubocop.yml
    ADDED
    
    
    
        data/.travis.yml
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -1,14 +1,33 @@ | |
| 1 1 | 
             
            # JWT
         | 
| 2 | 
            -
             | 
| 2 | 
            +
             | 
| 3 | 
            +
            [](https://travis-ci.org/jwt/ruby-jwt)
         | 
| 4 | 
            +
            [](https://codeclimate.com/github/jwt/ruby-jwt)
         | 
| 5 | 
            +
            [](https://codeclimate.com/github/jwt/ruby-jwt/coverage)
         | 
| 6 | 
            +
            [](https://codeclimate.com/github/jwt/ruby-jwt)
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            A pure ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard.
         | 
| 3 9 |  | 
| 4 10 | 
             
            If you have further questions releated to development or usage, join us: [ruby-jwt google group](https://groups.google.com/forum/#!forum/ruby-jwt).
         | 
| 5 11 |  | 
| 12 | 
            +
            ## Announcements
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            * Ruby 1.9.3 support will be dropped by December 31st, 2016.
         | 
| 15 | 
            +
            * Version 1.5.3 yanked. See: #132 and #133
         | 
| 16 | 
            +
             | 
| 6 17 | 
             
            ## Installing
         | 
| 7 18 |  | 
| 19 | 
            +
            ### Using Rubygems:
         | 
| 8 20 | 
             
            ```bash
         | 
| 9 21 | 
             
            sudo gem install jwt
         | 
| 10 22 | 
             
            ```
         | 
| 11 23 |  | 
| 24 | 
            +
            ### Using Bundler:
         | 
| 25 | 
            +
            Add the following to your Gemfile
         | 
| 26 | 
            +
            ```
         | 
| 27 | 
            +
            gem 'jwt'
         | 
| 28 | 
            +
            ```
         | 
| 29 | 
            +
            And run `bundle install`
         | 
| 30 | 
            +
             | 
| 12 31 | 
             
            ## Algorithms and Usage
         | 
| 13 32 |  | 
| 14 33 | 
             
            The JWT spec supports NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms for cryptographic signing. Currently the jwt gem supports NONE, HMAC, RSASSA and ECDSA. If you are using cryptographic signing, you need to specify the algorithm in the options hash whenever you call JWT.decode to ensure that an attacker [cannot bypass the algorithm verification step](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/).
         | 
| @@ -227,7 +246,7 @@ token = JWT.encode iss_payload, hmac_secret, 'HS256' | |
| 227 246 |  | 
| 228 247 | 
             
            begin
         | 
| 229 248 | 
             
              # Add iss to the validation to check if the token has been manipulated
         | 
| 230 | 
            -
              decoded_token = JWT.decode token, hmac_secret, true, {  | 
| 249 | 
            +
              decoded_token = JWT.decode token, hmac_secret, true, { :iss => iss, :verify_iss => true, :algorithm => 'HS256' }
         | 
| 231 250 | 
             
            rescue JWT::InvalidIssuerError
         | 
| 232 251 | 
             
              # Handle invalid token, e.g. logout user or deny access
         | 
| 233 252 | 
             
            end
         | 
| @@ -247,7 +266,7 @@ token = JWT.encode aud_payload, hmac_secret, 'HS256' | |
| 247 266 |  | 
| 248 267 | 
             
            begin
         | 
| 249 268 | 
             
              # Add aud to the validation to check if the token has been manipulated
         | 
| 250 | 
            -
              decoded_token = JWT.decode token, hmac_secret, true, {  | 
| 269 | 
            +
              decoded_token = JWT.decode token, hmac_secret, true, { :aud => aud, :verify_aud => true, :algorithm => 'HS256' }
         | 
| 251 270 | 
             
            rescue JWT::InvalidAudError
         | 
| 252 271 | 
             
              # Handle invalid token, e.g. logout user or deny access
         | 
| 253 272 | 
             
              puts 'Audience Error'
         | 
| @@ -261,8 +280,6 @@ From [Oauth JSON Web Token 4.1.7. "jti" (JWT ID) Claim](https://tools.ietf.org/h | |
| 261 280 | 
             
            > 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.
         | 
| 262 281 |  | 
| 263 282 | 
             
            ```ruby
         | 
| 264 | 
            -
            # in order to use JTI you have to add iat
         | 
| 265 | 
            -
            iat = Time.now.to_i
         | 
| 266 283 | 
             
            # Use the secret and iat to create a unique key per request to prevent replay attacks
         | 
| 267 284 | 
             
            jti_raw = [hmac_secret, iat].join(':').to_s
         | 
| 268 285 | 
             
            jti = Digest::MD5.hexdigest(jti_raw)
         | 
| @@ -271,9 +288,10 @@ jti_payload = { :data => 'data', :iat => iat, :jti => jti } | |
| 271 288 | 
             
            token = JWT.encode jti_payload, hmac_secret, 'HS256'
         | 
| 272 289 |  | 
| 273 290 | 
             
            begin
         | 
| 274 | 
            -
              #  | 
| 275 | 
            -
              decoded_token = JWT.decode token, hmac_secret, true, {  | 
| 276 | 
            -
              #  | 
| 291 | 
            +
              # If :verify_jti is true, validation will pass if a JTI is present
         | 
| 292 | 
            +
              #decoded_token = JWT.decode token, hmac_secret, true, { :verify_jti => true, :algorithm => 'HS256' }
         | 
| 293 | 
            +
              # Alternatively, pass a proc with your own code to check if the JTI has already been used
         | 
| 294 | 
            +
              decoded_token = JWT.decode token, hmac_secret, true, { :verify_jti => proc { |jti| my_validation_method(jti) }, :algorithm => 'HS256' }
         | 
| 277 295 | 
             
            rescue JWT::InvalidJtiError
         | 
| 278 296 | 
             
              # Handle invalid token, e.g. logout user or deny access
         | 
| 279 297 | 
             
              puts 'Error'
         | 
| @@ -323,16 +341,16 @@ end | |
| 323 341 |  | 
| 324 342 | 
             
            # Development and Tests
         | 
| 325 343 |  | 
| 326 | 
            -
            We depend on [ | 
| 344 | 
            +
            We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec and performing releases to rubygems.org, which can be done with
         | 
| 327 345 |  | 
| 328 346 | 
             
            ```bash
         | 
| 329 347 | 
             
            rake release
         | 
| 330 348 | 
             
            ```
         | 
| 331 349 |  | 
| 332 | 
            -
            The tests are written with rspec. Given you have  | 
| 350 | 
            +
            The tests are written with rspec. Given you have installed the dependencies via bundler, you can run tests with
         | 
| 333 351 |  | 
| 334 352 | 
             
            ```bash
         | 
| 335 | 
            -
             | 
| 353 | 
            +
            bundle exec rspec
         | 
| 336 354 | 
             
            ```
         | 
| 337 355 |  | 
| 338 356 | 
             
            **If you want a release cut with your PR, please include a version bump according to [Semantic Versioning](http://semver.org/)**
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -1,18 +1 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            require 'rubygems'
         | 
| 3 | 
            -
            require 'rake'
         | 
| 4 | 
            -
            require 'echoe'
         | 
| 5 | 
            -
             | 
| 6 | 
            -
            Echoe.new('jwt', '1.5.2') do |p|
         | 
| 7 | 
            -
              p.description = 'JSON Web Token implementation in Ruby'
         | 
| 8 | 
            -
              p.url = 'http://github.com/progrium/ruby-jwt'
         | 
| 9 | 
            -
              p.author = 'Jeff Lindsay'
         | 
| 10 | 
            -
              p.email = 'progrium@gmail.com'
         | 
| 11 | 
            -
              p.ignore_pattern = ['tmp/*']
         | 
| 12 | 
            -
              p.development_dependencies = ['echoe >=4.6.3']
         | 
| 13 | 
            -
              p.licenses = 'MIT'
         | 
| 14 | 
            -
            end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
            task :test do
         | 
| 17 | 
            -
              sh 'rspec spec/jwt_spec.rb'
         | 
| 18 | 
            -
            end
         | 
| 1 | 
            +
            require 'bundler/gem_tasks'
         | 
    
        data/lib/jwt.rb
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 | 
            -
            # encoding: utf-8
         | 
| 2 | 
            -
             | 
| 3 1 | 
             
            require 'base64'
         | 
| 4 2 | 
             
            require 'openssl'
         | 
| 3 | 
            +
            require 'jwt/decode'
         | 
| 4 | 
            +
            require 'jwt/error'
         | 
| 5 5 | 
             
            require 'jwt/json'
         | 
| 6 6 |  | 
| 7 7 | 
             
            # JSON Web Token implementation
         | 
| @@ -9,16 +9,6 @@ require 'jwt/json' | |
| 9 9 | 
             
            # Should be up to date with the latest spec:
         | 
| 10 10 | 
             
            # https://tools.ietf.org/html/rfc7519#section-4.1.5
         | 
| 11 11 | 
             
            module JWT
         | 
| 12 | 
            -
              class DecodeError < StandardError; end
         | 
| 13 | 
            -
              class VerificationError < DecodeError; end
         | 
| 14 | 
            -
              class ExpiredSignature < DecodeError; end
         | 
| 15 | 
            -
              class IncorrectAlgorithm < DecodeError; end
         | 
| 16 | 
            -
              class ImmatureSignature < DecodeError; end
         | 
| 17 | 
            -
              class InvalidIssuerError < DecodeError; end
         | 
| 18 | 
            -
              class InvalidIatError < DecodeError; end
         | 
| 19 | 
            -
              class InvalidAudError < DecodeError; end
         | 
| 20 | 
            -
              class InvalidSubError < DecodeError; end
         | 
| 21 | 
            -
              class InvalidJtiError < DecodeError; end
         | 
| 22 12 | 
             
              extend JWT::Json
         | 
| 23 13 |  | 
| 24 14 | 
             
              NAMED_CURVES = {
         | 
| @@ -73,11 +63,6 @@ module JWT | |
| 73 63 | 
             
                OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
         | 
| 74 64 | 
             
              end
         | 
| 75 65 |  | 
| 76 | 
            -
              def base64url_decode(str)
         | 
| 77 | 
            -
                str += '=' * (4 - str.length.modulo(4))
         | 
| 78 | 
            -
                Base64.decode64(str.tr('-_', '+/'))
         | 
| 79 | 
            -
              end
         | 
| 80 | 
            -
             | 
| 81 66 | 
             
              def base64url_encode(str)
         | 
| 82 67 | 
             
                Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
         | 
| 83 68 | 
             
              end
         | 
| @@ -109,34 +94,10 @@ module JWT | |
| 109 94 | 
             
                segments.join('.')
         | 
| 110 95 | 
             
              end
         | 
| 111 96 |  | 
| 112 | 
            -
              def  | 
| 113 | 
            -
                segments = jwt.split('.')
         | 
| 114 | 
            -
                required_num_segments = verify ? [3] : [2, 3]
         | 
| 115 | 
            -
                fail(JWT::DecodeError, 'Not enough or too many segments') unless required_num_segments.include? segments.length
         | 
| 116 | 
            -
                segments
         | 
| 117 | 
            -
              end
         | 
| 118 | 
            -
             | 
| 119 | 
            -
              def decode_header_and_payload(header_segment, payload_segment)
         | 
| 120 | 
            -
                header = decode_json(base64url_decode(header_segment))
         | 
| 121 | 
            -
                payload = decode_json(base64url_decode(payload_segment))
         | 
| 122 | 
            -
                [header, payload]
         | 
| 123 | 
            -
              end
         | 
| 124 | 
            -
             | 
| 125 | 
            -
              def decoded_segments(jwt, verify = true)
         | 
| 126 | 
            -
                header_segment, payload_segment, crypto_segment = raw_segments(jwt, verify)
         | 
| 127 | 
            -
                header, payload = decode_header_and_payload(header_segment, payload_segment)
         | 
| 128 | 
            -
                signature = base64url_decode(crypto_segment.to_s) if verify
         | 
| 129 | 
            -
                signing_input = [header_segment, payload_segment].join('.')
         | 
| 130 | 
            -
                [header, payload, signature, signing_input]
         | 
| 131 | 
            -
              end
         | 
| 132 | 
            -
             | 
| 133 | 
            -
              def decode(jwt, key = nil, verify = true, options = {}, &keyfinder)
         | 
| 97 | 
            +
              def decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
         | 
| 134 98 | 
             
                fail(JWT::DecodeError, 'Nil JSON web token') unless jwt
         | 
| 135 99 |  | 
| 136 | 
            -
                 | 
| 137 | 
            -
                fail(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
         | 
| 138 | 
            -
             | 
| 139 | 
            -
                default_options = {
         | 
| 100 | 
            +
                options = {
         | 
| 140 101 | 
             
                  verify_expiration: true,
         | 
| 141 102 | 
             
                  verify_not_before: true,
         | 
| 142 103 | 
             
                  verify_iss: false,
         | 
| @@ -147,43 +108,22 @@ module JWT | |
| 147 108 | 
             
                  leeway: 0
         | 
| 148 109 | 
             
                }
         | 
| 149 110 |  | 
| 150 | 
            -
                 | 
| 111 | 
            +
                merged_options = options.merge(custom_options)
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                decoder = Decode.new jwt, key, verify, merged_options, &keyfinder
         | 
| 114 | 
            +
                header, payload, signature, signing_input = decoder.decode_segments
         | 
| 115 | 
            +
                decoder.verify
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                fail(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
         | 
| 151 118 |  | 
| 152 119 | 
             
                if verify
         | 
| 153 120 | 
             
                  algo, key = signature_algorithm_and_key(header, key, &keyfinder)
         | 
| 154 | 
            -
                  if  | 
| 121 | 
            +
                  if merged_options[:algorithm] && algo != merged_options[:algorithm]
         | 
| 155 122 | 
             
                    fail JWT::IncorrectAlgorithm, 'Expected a different algorithm'
         | 
| 156 123 | 
             
                  end
         | 
| 157 124 | 
             
                  verify_signature(algo, key, signing_input, signature)
         | 
| 158 125 | 
             
                end
         | 
| 159 126 |  | 
| 160 | 
            -
                if options[:verify_expiration] && payload.include?('exp')
         | 
| 161 | 
            -
                  fail(JWT::ExpiredSignature, 'Signature has expired') unless payload['exp'].to_i > (Time.now.to_i - options[:leeway])
         | 
| 162 | 
            -
                end
         | 
| 163 | 
            -
                if options[:verify_not_before] && payload.include?('nbf')
         | 
| 164 | 
            -
                  fail(JWT::ImmatureSignature, 'Signature nbf has not been reached') unless payload['nbf'].to_i <= (Time.now.to_i + options[:leeway])
         | 
| 165 | 
            -
                end
         | 
| 166 | 
            -
                if options[:verify_iss] && options[:iss]
         | 
| 167 | 
            -
                  fail(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options[:iss]}, received #{payload['iss'] || '<none>'}") unless payload['iss'].to_s == options[:iss].to_s
         | 
| 168 | 
            -
                end
         | 
| 169 | 
            -
                if options[:verify_iat] && payload.include?('iat')
         | 
| 170 | 
            -
                  fail(JWT::InvalidIatError, 'Invalid iat') unless payload['iat'].is_a?(Integer) && payload['iat'].to_i <= (Time.now.to_i + options[:leeway])
         | 
| 171 | 
            -
                end
         | 
| 172 | 
            -
                if options[:verify_aud] && options[:aud]
         | 
| 173 | 
            -
                  if payload[:aud].is_a?(Array)
         | 
| 174 | 
            -
                    fail(JWT::InvalidAudError, 'Invalid audience') unless payload['aud'].include?(options[:aud].to_s)
         | 
| 175 | 
            -
                  else
         | 
| 176 | 
            -
                    fail(JWT::InvalidAudError, "Invalid audience. Expected #{options[:aud]}, received #{payload['aud'] || '<none>'}") unless payload['aud'].to_s == options[:aud].to_s
         | 
| 177 | 
            -
                  end
         | 
| 178 | 
            -
                end
         | 
| 179 | 
            -
                if options[:verify_sub] && options.include?(:sub)
         | 
| 180 | 
            -
                  fail(JWT::InvalidSubError, "Invalid subject. Expected #{options[:sub]}, received #{payload['sub'] || '<none>'}") unless payload['sub'].to_s == options[:sub].to_s
         | 
| 181 | 
            -
                end
         | 
| 182 | 
            -
                if options[:verify_jti] && payload.include?('jti')
         | 
| 183 | 
            -
                  fail(JWT::InvalidJtiError, 'need iat for verify jwt id') unless payload.include?('iat')
         | 
| 184 | 
            -
                  fail(JWT::InvalidJtiError, 'Not a uniq jwt id') unless options[:jti].to_s == Digest::MD5.hexdigest("#{key}:#{payload['iat']}")
         | 
| 185 | 
            -
                end
         | 
| 186 | 
            -
             | 
| 187 127 | 
             
                [payload, header]
         | 
| 188 128 | 
             
              end
         | 
| 189 129 |  | 
| @@ -194,11 +134,11 @@ module JWT | |
| 194 134 |  | 
| 195 135 | 
             
              def verify_signature(algo, key, signing_input, signature)
         | 
| 196 136 | 
             
                if %w(HS256 HS384 HS512).include?(algo)
         | 
| 197 | 
            -
                  fail(JWT::VerificationError, 'Signature verification  | 
| 137 | 
            +
                  fail(JWT::VerificationError, 'Signature verification raised') unless secure_compare(signature, sign_hmac(algo, signing_input, key))
         | 
| 198 138 | 
             
                elsif %w(RS256 RS384 RS512).include?(algo)
         | 
| 199 | 
            -
                  fail(JWT::VerificationError, 'Signature verification  | 
| 139 | 
            +
                  fail(JWT::VerificationError, 'Signature verification raised') unless verify_rsa(algo, key, signing_input, signature)
         | 
| 200 140 | 
             
                elsif %w(ES256 ES384 ES512).include?(algo)
         | 
| 201 | 
            -
                  fail(JWT::VerificationError, 'Signature verification  | 
| 141 | 
            +
                  fail(JWT::VerificationError, 'Signature verification raised') unless verify_ecdsa(algo, key, signing_input, signature)
         | 
| 202 142 | 
             
                else
         | 
| 203 143 | 
             
                  fail JWT::VerificationError, 'Algorithm not supported'
         | 
| 204 144 | 
             
                end
         | 
| @@ -230,4 +170,8 @@ module JWT | |
| 230 170 | 
             
                byte_size = (public_key.group.degree + 7) / 8
         | 
| 231 171 | 
             
                OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
         | 
| 232 172 | 
             
              end
         | 
| 173 | 
            +
             | 
| 174 | 
            +
              def base64url_decode(str)
         | 
| 175 | 
            +
                Decode.base64url_decode(str)
         | 
| 176 | 
            +
              end
         | 
| 233 177 | 
             
            end
         | 
    
        data/lib/jwt/decode.rb
    ADDED
    
    | @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            require 'jwt/json'
         | 
| 2 | 
            +
            require 'jwt/verify'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            # JWT::Decode module
         | 
| 5 | 
            +
            module JWT
         | 
| 6 | 
            +
              extend JWT::Json
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              # Decoding logic for JWT
         | 
| 9 | 
            +
              class Decode
         | 
| 10 | 
            +
                attr_reader :header, :payload, :signature
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def initialize(jwt, key, verify, options, &keyfinder)
         | 
| 13 | 
            +
                  @jwt = jwt
         | 
| 14 | 
            +
                  @key = key
         | 
| 15 | 
            +
                  @verify = verify
         | 
| 16 | 
            +
                  @options = options
         | 
| 17 | 
            +
                  @keyfinder = keyfinder
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def decode_segments
         | 
| 21 | 
            +
                  header_segment, payload_segment, crypto_segment = raw_segments(@jwt, @verify)
         | 
| 22 | 
            +
                  @header, @payload = decode_header_and_payload(header_segment, payload_segment)
         | 
| 23 | 
            +
                  @signature = Decode.base64url_decode(crypto_segment.to_s) if @verify
         | 
| 24 | 
            +
                  signing_input = [header_segment, payload_segment].join('.')
         | 
| 25 | 
            +
                  [@header, @payload, @signature, signing_input]
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def raw_segments(jwt, verify)
         | 
| 29 | 
            +
                  segments = jwt.split('.')
         | 
| 30 | 
            +
                  required_num_segments = verify ? [3] : [2, 3]
         | 
| 31 | 
            +
                  fail(JWT::DecodeError, 'Not enough or too many segments') unless required_num_segments.include? segments.length
         | 
| 32 | 
            +
                  segments
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
                private :raw_segments
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def decode_header_and_payload(header_segment, payload_segment)
         | 
| 37 | 
            +
                  header = JWT.decode_json(Decode.base64url_decode(header_segment))
         | 
| 38 | 
            +
                  payload = JWT.decode_json(Decode.base64url_decode(payload_segment))
         | 
| 39 | 
            +
                  [header, payload]
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
                private :decode_header_and_payload
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def self.base64url_decode(str)
         | 
| 44 | 
            +
                  str += '=' * (4 - str.length.modulo(4))
         | 
| 45 | 
            +
                  Base64.decode64(str.tr('-_', '+/'))
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def verify
         | 
| 49 | 
            +
                  @options.each do |key, val|
         | 
| 50 | 
            +
                    next unless key.to_s.match(/verify/)
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    Verify.send(key, payload, @options) if val
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
    
        data/lib/jwt/error.rb
    ADDED
    
    | @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            module JWT
         | 
| 2 | 
            +
              class DecodeError < StandardError; end
         | 
| 3 | 
            +
              class VerificationError < DecodeError; end
         | 
| 4 | 
            +
              class ExpiredSignature < DecodeError; end
         | 
| 5 | 
            +
              class IncorrectAlgorithm < DecodeError; end
         | 
| 6 | 
            +
              class ImmatureSignature < DecodeError; end
         | 
| 7 | 
            +
              class InvalidIssuerError < DecodeError; end
         | 
| 8 | 
            +
              class InvalidIatError < DecodeError; end
         | 
| 9 | 
            +
              class InvalidAudError < DecodeError; end
         | 
| 10 | 
            +
              class InvalidSubError < DecodeError; end
         | 
| 11 | 
            +
              class InvalidJtiError < DecodeError; end
         | 
| 12 | 
            +
            end
         | 
    
        data/lib/jwt/json.rb
    CHANGED
    
    | @@ -1,32 +1,16 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            require 'json'
         | 
| 2 | 
            +
             | 
| 2 3 | 
             
            module JWT
         | 
| 3 4 | 
             
              # JSON fallback implementation or ruby 1.8.x
         | 
| 4 5 | 
             
              module Json
         | 
| 5 | 
            -
                 | 
| 6 | 
            -
                   | 
| 7 | 
            -
             | 
| 8 | 
            -
                   | 
| 9 | 
            -
             | 
| 10 | 
            -
                  rescue JSON::ParserError
         | 
| 11 | 
            -
                    raise JWT::DecodeError, 'Invalid segment encoding'
         | 
| 12 | 
            -
                  end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                  def encode_json(raw)
         | 
| 15 | 
            -
                    JSON.generate(raw)
         | 
| 16 | 
            -
                  end
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                else
         | 
| 19 | 
            -
                  require 'multi_json'
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                  def decode_json(encoded)
         | 
| 22 | 
            -
                    MultiJson.decode(encoded)
         | 
| 23 | 
            -
                  rescue MultiJson::LoadError
         | 
| 24 | 
            -
                    raise JWT::DecodeError, 'Invalid segment encoding'
         | 
| 25 | 
            -
                  end
         | 
| 6 | 
            +
                def decode_json(encoded)
         | 
| 7 | 
            +
                  JSON.parse(encoded)
         | 
| 8 | 
            +
                rescue JSON::ParserError
         | 
| 9 | 
            +
                  raise JWT::DecodeError, 'Invalid segment encoding'
         | 
| 10 | 
            +
                end
         | 
| 26 11 |  | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
                  end
         | 
| 12 | 
            +
                def encode_json(raw)
         | 
| 13 | 
            +
                  JSON.generate(raw)
         | 
| 30 14 | 
             
                end
         | 
| 31 15 | 
             
              end
         | 
| 32 16 | 
             
            end
         |