Skip to content

Commit

Permalink
Merge pull request #130 from tpickett66/hash-keys
Browse files Browse the repository at this point in the history
Allow string hash keys in validation configurations
  • Loading branch information
excpt committed Feb 24, 2016
2 parents d4fca40 + a4d0473 commit 04120f6
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 243 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@ If you have further questions releated to development or usage, join us: [ruby-j

## Installing

### Using Rubygems:
```bash
sudo gem install jwt
```

### Using Bundler:
Add the following to your Gemfile
```
gem 'jwt'
```
And run `bundle install`

## Algorithms and Usage

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/).
Expand Down Expand Up @@ -328,16 +336,16 @@ end

# Development and Tests

We depend on [Echoe](http://rubygems.org/gems/echoe) for defining gemspec and performing releases to rubygems.org, which can be done with
We depend on [Bundler](http://rubygems.org/gems/bundler) for defining gemspec and performing releases to rubygems.org, which can be done with

```bash
rake release
```

The tests are written with rspec. Given you have rake and rspec, you can run tests with
The tests are written with rspec. Given you have installed the dependencies via bundler, you can run tests with

```bash
rake test
bundle exec rspec
```

**If you want a release cut with your PR, please include a version bump according to [Semantic Versioning](http://semver.org/)**
Expand Down
11 changes: 1 addition & 10 deletions lib/jwt.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
require 'base64'
require 'openssl'
require 'jwt/decode'
require 'jwt/error'
require 'jwt/json'

# JSON Web Token implementation
#
# Should be up to date with the latest spec:
# https://tools.ietf.org/html/rfc7519#section-4.1.5
module JWT
class DecodeError < StandardError; end
class VerificationError < DecodeError; end
class ExpiredSignature < DecodeError; end
class IncorrectAlgorithm < DecodeError; end
class ImmatureSignature < DecodeError; end
class InvalidIssuerError < DecodeError; end
class InvalidIatError < DecodeError; end
class InvalidAudError < DecodeError; end
class InvalidSubError < DecodeError; end
class InvalidJtiError < DecodeError; end
extend JWT::Json

NAMED_CURVES = {
Expand Down
12 changes: 12 additions & 0 deletions lib/jwt/error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module JWT
class DecodeError < StandardError; end
class VerificationError < DecodeError; end
class ExpiredSignature < DecodeError; end
class IncorrectAlgorithm < DecodeError; end
class ImmatureSignature < DecodeError; end
class InvalidIssuerError < DecodeError; end
class InvalidIatError < DecodeError; end
class InvalidAudError < DecodeError; end
class InvalidSubError < DecodeError; end
class InvalidJtiError < DecodeError; end
end
107 changes: 66 additions & 41 deletions lib/jwt/verify.rb
Original file line number Diff line number Diff line change
@@ -1,73 +1,98 @@
require 'jwt/error'

module JWT
# JWT verify methods
module Verify
def self.verify_expiration(payload, options)
return unless payload.include?('exp')

if payload['exp'].to_i < (Time.now.to_i - options[:leeway])
fail(JWT::ExpiredSignature, 'Signature has expired')
class Verify
class << self
%i[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub].each do |method_name|
define_method method_name do |payload, options|
new(payload, options).send(method_name)
end
end
end

def self.verify_not_before(payload, options)
return unless payload.include?('nbf')

if payload['nbf'].to_i > (Time.now.to_i + options[:leeway])
fail(JWT::ImmatureSignature, 'Signature nbf has not been reached')
end
def initialize(payload, options)
@payload = payload
@options = options
end

def self.verify_iss(payload, options)
return unless options[:iss]
def verify_aud
return unless options_aud = extract_option(:aud)

if payload['iss'].to_s != options[:iss].to_s
if @payload['aud'].is_a?(Array)
fail(
JWT::InvalidIssuerError,
"Invalid issuer. Expected #{options[:iss]}, received #{payload['iss'] || '<none>'}"
)
JWT::InvalidAudError,
'Invalid audience'
) unless @payload['aud'].include?(options_aud.to_s)
else
fail(
JWT::InvalidAudError,
"Invalid audience. Expected #{options_aud}, received #{@payload['aud'] || '<none>'}"
) unless @payload['aud'].to_s == options_aud.to_s
end
end

def self.verify_iat(payload, options)
return unless payload.include?('iat')
def verify_expiration
return unless @payload.include?('exp')

if !(payload['iat'].is_a?(Integer)) || payload['iat'].to_i > (Time.now.to_i + options[:leeway])
fail(JWT::InvalidIatError, 'Invalid iat')
if @payload['exp'].to_i < (Time.now.to_i - leeway)
fail(JWT::ExpiredSignature, 'Signature has expired')
end
end

def self.verify_jti(payload, _options)
if _options[:verify_jti].class == Proc
fail(JWT::InvalidJtiError, 'Invalid jti') unless _options[:verify_jti].call(payload['jti'])
else
fail(JWT::InvalidJtiError, 'Missing jti') if payload['jti'].to_s == ''
def verify_iat
return unless @payload.include?('iat')

if !(@payload['iat'].is_a?(Integer)) || @payload['iat'].to_i > (Time.now.to_i + leeway)
fail(JWT::InvalidIatError, 'Invalid iat')
end
end

def self.verify_aud(payload, options)
return unless options[:aud]
def verify_iss
return unless options_iss = extract_option(:iss)

if payload[:aud].is_a?(Array)
if @payload['iss'].to_s != options_iss.to_s
fail(
JWT::InvalidAudError,
'Invalid audience'
) unless payload['aud'].include?(options[:aud].to_s)
JWT::InvalidIssuerError,
"Invalid issuer. Expected #{options_iss}, received #{@payload['iss'] || '<none>'}"
)
end
end

def verify_jti
options_verify_jti = extract_option(:verify_jti)
if options_verify_jti.respond_to?(:call)
fail(JWT::InvalidJtiError, 'Invalid jti') unless options_verify_jti.call(@payload['jti'])
else
fail(
JWT::InvalidAudError,
"Invalid audience. Expected #{options[:aud]}, received #{payload['aud'] || '<none>'}"
) unless payload['aud'].to_s == options[:aud].to_s
fail(JWT::InvalidJtiError, 'Missing jti') if @payload['jti'].to_s.strip.empty?
end
end

def self.verify_sub(payload, options)
return unless options[:sub]
def verify_not_before
return unless @payload.include?('nbf')

if @payload['nbf'].to_i > (Time.now.to_i + leeway)
fail(JWT::ImmatureSignature, 'Signature nbf has not been reached')
end
end

def verify_sub
return unless options_sub = extract_option(:sub)

fail(
JWT::InvalidSubError,
"Invalid subject. Expected #{options[:sub]}, received #{payload['sub'] || '<none>'}"
) unless payload['sub'].to_s == options[:sub].to_s
"Invalid subject. Expected #{options_sub}, received #{@payload['sub'] || '<none>'}"
) unless @payload['sub'].to_s == options_sub.to_s
end

private

def extract_option(key)
@options.values_at(key.to_sym, key.to_s).compact.first
end

def leeway
extract_option :leeway
end
end
end
2 changes: 1 addition & 1 deletion lib/jwt/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module VERSION
# tiny version
TINY = 3
# alpha, beta, etc. tag
PRE = 'dev'
PRE = nil

# Build version string
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
Expand Down
1 change: 1 addition & 0 deletions ruby-jwt.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'simplecov'
spec.add_development_dependency 'simplecov-json'
spec.add_development_dependency 'codeclimate-test-reporter'
spec.add_development_dependency 'byebug'
end
Loading

0 comments on commit 04120f6

Please sign in to comment.