Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preperations for version 2.x #49

Merged
merged 35 commits into from
Jan 26, 2015
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a133496
Prepare splitting code into JWT, JWS and JWA
excpt Dec 24, 2014
b2631a0
Dropping mri 1.8 support, update configuration
excpt Dec 24, 2014
c21a0c9
Remove obsolete gems
excpt Dec 24, 2014
fe71b94
JWA HMAC implementation
excpt Dec 24, 2014
deb7dcb
JWA HMAC implementation
excpt Dec 24, 2014
2717351
JWA RSASSA implementation
excpt Dec 24, 2014
92ea937
JWA ECDSA implementation [in theory]
excpt Dec 25, 2014
af564c1
Enable travis-ci docker containers
excpt Dec 25, 2014
277115e
JWA NONE implementation
excpt Dec 25, 2014
816dbe5
Drop old ideas, restart at HS256 only
excpt Dec 27, 2014
e694fc4
Improve JWT::DecodeError exception output
excpt Dec 27, 2014
9a20006
Add plain token handling
excpt Dec 27, 2014
a7d4fde
Add custom header test, remove unused variables
excpt Dec 27, 2014
707b8e8
Remove old code
excpt Dec 27, 2014
d2e01d4
Initial JWA Implementation
excpt Dec 27, 2014
1575512
Add first JWA HMAC logic
excpt Dec 28, 2014
275638f
Extend JWA HMAC, break tests
excpt Dec 29, 2014
5f660b8
Add Base64 encode and decode handling
excpt Dec 29, 2014
7036713
Clean up code, add test for not implemented algorithms
excpt Dec 29, 2014
64aa4f4
Fix some tests
excpt Dec 30, 2014
621aaa6
Remove jwa folder from prepare-test script
excpt Dec 31, 2014
e432867
Remove skipped jwa folder on mkdir
excpt Dec 31, 2014
9338632
Add new keys for tests
excpt Dec 31, 2014
d799c92
Add basic JWA RSASSA SHA implementation
excpt Dec 31, 2014
bd372df
Add test cases for wrong keys in RSASSA verification
excpt Jan 1, 2015
0b0ff16
Integrate JWA into JWT
excpt Jan 1, 2015
1be475d
Add SimpleCov config, improve coverage to 100%
excpt Jan 1, 2015
ada171f
Bunp version to 2.0.0.pre
excpt Jan 1, 2015
10562e5
Add badges to README.md
excpt Jan 1, 2015
33b65b9
Add ruby 2.2.0 for travis-ci testing
excpt Jan 1, 2015
5a0eb5b
Update Gemfile, remove codeclimate from travis ci
excpt Jan 1, 2015
e0b2e6c
Update README.md
excpt Jan 2, 2015
94b6788
Update README.md
excpt Jan 2, 2015
7eef40e
Update README.md
excpt Jan 2, 2015
8088bb1
Add codeclimate reporting fix provided by @jonathancadepowers
excpt Jan 3, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/.idea
/coverage
/tmp
*.iml
jwt.gemspec
pkg
Gemfile.lock
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--color
15 changes: 9 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
sudo: false
language: ruby
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- 2.0.0
- 2.1.0
- jruby
- rbx
- ree
script: "bundle exec rake test"
- 2.1.5
- 2.2.0
script: "bundle exec rspec"
before_install:
- gem update --system
- gem --version
before_script:
- bin/prepare-test.sh
11 changes: 4 additions & 7 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
source "https://rubygems.org"
source 'https://rubygems.org'

gem 'json', '>= 1.2.4'
gem 'multi_json', '~> 1.0', :platforms => :ruby_18
gem 'jruby-openssl', :platforms => :jruby

gem 'rubysl', '~> 2.0', :platforms => :rbx
gem 'json'

group :development do
gem 'echoe', '>= 4.6.3'
end

group :test, :development do
gem 'rake'
gem 'rspec', '~> 3'
gem 'rspec'
gem 'codeclimate-test-reporter', require: false
end
6 changes: 0 additions & 6 deletions Manifest

This file was deleted.

106 changes: 75 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# JWT
A Ruby implementation of [JSON Web Token draft 06](http://self-issued.info/docs/draft-jones-json-web-token-06.html).

## Installing

sudo gem install jwt

## Usage
# WARNING: This is the 2.x branch of ruby-jwt, currently under heavy development

JWT.encode({"some" => "payload"}, "secret")
# JWT

Note the resulting JWT will not be encrypted, but verifiable with a secret key.
[![Build Status](https://travis-ci.org/excpt/ruby-jwt.svg?branch=refactoring)](https://travis-ci.org/excpt/ruby-jwt)
[![Codeship Status for excpt/moments](https://codeship.com/projects/f94b6890-6dc3-0132-8f82-52110d7a425c/status?branch=master)](https://codeship.com/projects/54274)
[![Code Climate](https://codeclimate.com/github/excpt/ruby-jwt/badges/gpa.svg)](https://codeclimate.com/github/excpt/ruby-jwt)
[![Test Coverage](https://codeclimate.com/github/excpt/ruby-jwt/badges/coverage.svg)](https://codeclimate.com/github/excpt/ruby-jwt)

JWT.decode("someJWTstring", "secret")
## Goal

If the secret is wrong, it will raise a `JWT::DecodeError` telling you as such. You can still get at the payload by setting the verify argument to false.
The goal is to implement the complete JWT spec including the underlying specs JWS, JWA, JWK and JWE.

JWT.decode("someJWTstring", nil, false)
* [JSON Web Token (JWT)](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32)
* [JSON Web Signature (JWS)](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-39)
* [JSON Web Encryption (JWE)](https://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-39)
* [JSON Web Key (JWK)](https://tools.ietf.org/html/draft-ietf-jose-json-web-key-39)
* [JSON Web Algorithms (JWA)](https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-39)

## Algorithms

Expand All @@ -33,16 +33,50 @@ The JWT spec supports several algorithms for cryptographic signing. This library
* RS384 - RSA using SHA-384 hash algorithm
* RS512 - RSA using SHA-512 hash algorithm

**NONE**

* NONE - No signature

## Installing

```bash
sudo gem install jwt
```

## Usage

Signing a JSON Web Token.

```ruby
JWT.encode({"some" => "payload"}, "secret")
```

Note: The resulting JWT will not be encrypted, but verifiable with a secret key.

```ruby
JWT.decode("someJWTstring", "secret")
```

If the secret is wrong, it will raise a `JWT::DecodeError` telling you as such. You can still get at the payload by setting the verify argument to false.

```ruby
JWT.decode("someJWTstring", nil, false)
```

Change the algorithm with by setting it in encode:

JWT.encode({"some" => "payload"}, "secret", "HS512")
```ruby
JWT.encode({"some" => "payload"}, "secret", "HS512")
```

**Plaintext**

We also support unsigned plaintext JWTs as introduced by draft 03 by explicitly specifying `nil` as the key and algorithm:

jwt = JWT.encode({"some" => "payload"}, nil, nil)
JWT.decode(jwt, nil, nil)
```ruby
jwt = JWT.encode({"some" => "payload"}, nil, nil)
JWT.decode(jwt, nil, nil)
```

## Support for reserved claim names
JSON Web Token defines some reserved claim names and defines how they should be
Expand All @@ -64,18 +98,21 @@ From [draft 01 of the JWT spec](http://self-issued.info/docs/draft-jones-json-we

You pass the expiration time as a UTC UNIX timestamp (an int). For example:

JWT.encode({"exp": 1371720939}, "secret")

JWT.encode({"exp": Time.now.to_i()}, "secret")
```ruby
JWT.encode({"exp": 1371720939}, "secret")
JWT.encode({"exp": Time.now.to_i()}, "secret")
```

Expiration time is automatically verified in `JWT.decode()` and raises
`JWT::ExpiredSignature` if the expiration time is in the past:

begin
JWT.decode("JWT_STRING", "secret")
rescue JWT::ExpiredSignature
# Signature has expired
end
```ruby
begin
JWT.decode("JWT_STRING", "secret")
rescue JWT::ExpiredSignature
# Signature has expired
end
```

Expiration time will be compared to the current UTC time (as given by
`Time.now.to_i`), so be sure to use a UTC timestamp or datetime in encoding.
Expand All @@ -88,21 +125,27 @@ For example, if you have a JWT payload with a expiration time set to 30 seconds
after creation but you know that sometimes you will process it after 30 seconds,
you can set a leeway of 10 seconds in order to have some margin:

jwt_payload = JWT.encode({'exp': Time.now.to_i + 30}, 'secret')
sleep(32)
# jwt_payload is now expired
# But with some leeway, it will still validate
JWT.decode(jwt_payload, 'secret', true, leeway=10)
```ruby
jwt_payload = JWT.encode({'exp': Time.now.to_i + 30}, 'secret')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm just nitpicking here but in the code blocks throughout the README you're using different hash syntaxes, double and single quotes etc.
Since Ruby 1.8 is removed in that PR how about replacing all of these with the short notation?
(e.g. { exp: Time.now.to_i })

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. I will change this and update the PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. :)

sleep(32)
# jwt_payload is now expired
# But with some leeway, it will still validate
JWT.decode(jwt_payload, 'secret', true, leeway=10)
```

## 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

rake release
```bash
rake release
```

The tests are written with rspec. Given you have rake and rspec, you can run tests with

rake test
```bash
rake test
```

**If you want a release cut with your PR, please include a version bump according to [Semantic Versioning](http://semver.org/)**

Expand All @@ -118,6 +161,7 @@ The tests are written with rspec. Given you have rake and rspec, you can run tes
* Ariel Salomon (Oscil8)
* Paul Battley <pbattley@gmail.com>
* Zane Shannon [@zshannon](https://github.com/zshannon)
* Tim Rudat <timrudat@gmail.com> [@excpt](https://github.com/excpt)

## License

Expand Down
18 changes: 9 additions & 9 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ require 'rubygems'
require 'rake'
require 'echoe'

Echoe.new('jwt', '1.2.0') do |p|
p.description = "JSON Web Token implementation in Ruby"
p.url = "http://github.com/progrium/ruby-jwt"
p.author = "Jeff Lindsay"
p.email = "progrium@gmail.com"
p.ignore_pattern = ["tmp/*"]
p.development_dependencies = ["echoe >=4.6.3"]
p.licenses = "MIT"
Echoe.new('jwt', '2.0.0.pre') do |p|
p.description = 'JSON Web Token implementation in Ruby'
p.url = 'http://github.com/progrium/ruby-jwt'
p.author = 'Jeff Lindsay'
p.email = 'progrium@gmail.com'
p.ignore_pattern = ['tmp/*']
p.development_dependencies = ['echoe >=4.6.3']
p.licenses = 'MIT'
end

task :test do
sh "rspec spec/jwt_spec.rb"
sh 'bundle exec rspec'
end
29 changes: 29 additions & 0 deletions bin/prepare-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/sh

rm -rf tmp/certs/*.pem

mkdir -p tmp/certs

# RSA KEYS
openssl genrsa 1024 > tmp/certs/rsa-1024-private.pem
openssl rsa -in tmp/certs/rsa-1024-private.pem -pubout > tmp/certs/rsa-1024-public.pem
openssl genrsa 2048 > tmp/certs/rsa-2048-private.pem
openssl genrsa 2048 > tmp/certs/rsa-2048-wrong-private.pem
openssl rsa -in tmp/certs/rsa-2048-private.pem -pubout > tmp/certs/rsa-2048-public.pem
openssl rsa -in tmp/certs/rsa-2048-wrong-private.pem -pubout > tmp/certs/rsa-2048-wrong-public.pem
openssl genrsa 4096 > tmp/certs/rsa-4096-private.pem
openssl rsa -in tmp/certs/rsa-4096-private.pem -pubout > tmp/certs/rsa-4096-public.pem

# ECDSA KEYS
openssl ecparam -out tmp/certs/ec256-private.pem -name secp256k1 -genkey
openssl ecparam -out tmp/certs/ec256-wrong-private.pem -name secp256k1 -genkey
openssl ecparam -out tmp/certs/ec384-private.pem -name secp384r1 -genkey
openssl ecparam -out tmp/certs/ec384-wrong-private.pem -name secp384r1 -genkey
openssl ecparam -out tmp/certs/ec512-private.pem -name secp521r1 -genkey
openssl ecparam -out tmp/certs/ec512-wrong-private.pem -name secp521r1 -genkey
openssl ec -in tmp/certs/ec256-private.pem -pubout > tmp/certs/ec256-public.pem
openssl ec -in tmp/certs/ec256-wrong-private.pem -pubout > tmp/certs/ec256-wrong-public.pem
openssl ec -in tmp/certs/ec384-private.pem -pubout > tmp/certs/ec384-public.pem
openssl ec -in tmp/certs/ec384-wrong-private.pem -pubout > tmp/certs/ec384-wrong-public.pem
openssl ec -in tmp/certs/ec512-private.pem -pubout > tmp/certs/ec512-public.pem
openssl ec -in tmp/certs/ec512-wrong-private.pem -pubout > tmp/certs/ec512-wrong-public.pem
76 changes: 76 additions & 0 deletions lib/jwa.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require 'jwa/hmac'
require 'jwa/none'
require 'jwa/rsassa'

module JWA
extend self

# The complete list of signing algorithms defined in the IETF JSON Web Algorithms (JWA) version 38
# https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-38#section-3.1
ALGORITHMS = %w(HS256 HS384 HS512 RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512 none)

# raises if the payload is not a string
class InvalidPayloadFormat < ArgumentError
end

# raises if a algorithm is called that is not defined in the specs
# Info: all algorithms a case-sensitive
class InvalidAlgorithm < ArgumentError
end

# raises if a secret or key is required but not provided in order to sign the data
class MissingSecretOrKey < ArgumentError
end

# raises if a part of code is not implemented
class NotImplemented < ArgumentError
end

def sign(algorithm, data, secret_or_private_key = '')
algo, bits = validate_algorithm algorithm
validate_data data

case algo
when 'HS'
JWA::HMAC.new(bits).sign(data, secret_or_private_key)
when 'RS'
JWA::RSASSA.new(bits).sign(data, secret_or_private_key)
when 'none'
JWA::NONE.new.sign()
else
raise JWA::NotImplemented.new("JWA: #{algorithm} is not implemented yet.")
end
end

def verify(algorithm, data, signature, secret_or_public_key = '')
algo, bits = validate_algorithm algorithm
validate_data data

case algo
when 'HS'
JWA::HMAC.new(bits).verify(data, signature, secret_or_public_key)
when 'RS'
JWA::RSASSA.new(bits).verify(data, signature, secret_or_public_key)
when 'none'
JWA::NONE.new.verify()
else
raise JWA::NotImplemented.new("JWA: #{algorithm} is not implemented yet.")
end
end

def validate_algorithm(algorithm)
raise JWA::InvalidAlgorithm.new("JWA: Given algorithm [#{algorithm.to_s}] is not part of the JWS supported algorithms.") unless ALGORITHMS.include? algorithm

match = algorithm.match(/(HS|RS|ES|PS|none)(\d+)?/)

[match[1], match[2]]
end

private :validate_algorithm

def validate_data(data)
raise JWA::InvalidPayloadFormat.new('JWA: Given data is not a string.') unless data.is_a? String
end

private :validate_data
end
23 changes: 23 additions & 0 deletions lib/jwa/hmac.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module JWA
class HMAC
def initialize(bits)
@bits = bits
end

def sign(data, secret)
validate_secret secret

signature = OpenSSL::HMAC.digest OpenSSL::Digest.new("sha#{@bits}"), secret, data

JWT::Base64.encode signature
end

def verify(data, signature, secret)
signature === sign(data, secret)
end

def validate_secret(secret)
raise JWA::MissingSecretOrKey.new('JWA: HMAC signing always requires a secret to be set.') if secret.length == 0
end
end
end
11 changes: 11 additions & 0 deletions lib/jwa/none.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module JWA
class NONE
def sign
''
end

def verify
true
end
end
end
Loading