Skip to content

Commit

Permalink
Merge pull request #49 from excpt/master
Browse files Browse the repository at this point in the history
Preperations for version 2.x
  • Loading branch information
excpt committed Jan 26, 2015
2 parents d46fa82 + 8088bb1 commit 8f6191f
Show file tree
Hide file tree
Showing 20 changed files with 601 additions and 441 deletions.
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')
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

0 comments on commit 8f6191f

Please sign in to comment.