Imported Upstream version 1.6.2

parents
## MAC OS
.DS_Store
## TEXTMATE
*.tmproj
tmtags
## EMACS
*~
\#*
.\#*
## VIM
*.swp
## PROJECT::GENERAL
coverage*
rdoc
pkg
Gemfile.lock
## PROJECT::SPECIFIC
.class
[submodule "spec/helpers/json-jwt-nimbus"]
path = spec/helpers/json-jwt-nimbus
url = git://github.com/nov/json-jwt-nimbus.git
--color
--format=documentation
before_install:
- gem install bundler
- git submodule update --init --recursive
rvm:
- 2.0
- 2.1
- 2.2
- 2.3.0
\ No newline at end of file
source "http://rubygems.org"
gemspec
\ No newline at end of file
Copyright (c) 2011 nov matake
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.
# JSON::JWT
JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and JSON Web Key) in Ruby
[![Build Status](https://secure.travis-ci.org/nov/json-jwt.png)](http://travis-ci.org/nov/json-jwt)
## Installation
```
gem install json-jwt
```
## Resources
* View Source on GitHub (https://github.com/nov/json-jwt)
* Report Issues on GitHub (https://github.com/nov/json-jwt/issues)
* Documentation on GitHub (https://github.com/nov/json-jwt/wiki)
## Examples
```ruby
require 'json/jwt'
private_key = OpenSSL::PKey::RSA.new <<-PEM
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAyBKIFSH8dP6bDkGBziB6RXTTfZVTaaNSWNtIzDmgRFi6FbLo
:
-----END RSA PRIVATE KEY-----
PEM
public_key = OpenSSL::PKey::RSA.new <<-PEM
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyBKIFSH8dP6bDkGBziB6
:
-----END PUBLIC KEY-----
PEM
# Sign & Encode
claim = {
iss: 'nov',
exp: 1.week.from_now,
nbf: Time.now
}
jws = JSON::JWT.new(claim).sign(private_key, :RS256)
jws.to_s
# Decode & Verify
input = "jwt_header.jwt_claims.jwt_signature"
JSON::JWT.decode(input, public_key)
```
For more details, read [Documentation Wiki](https://github.com/nov/json-jwt/wiki).
## Note on Patches/Pull Requests
* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a
future version unintentionally.
* Commit, do not mess with rakefile, version, or history.
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
* Send me a pull request. Bonus points for topic branches.
## Copyright
Copyright (c) 2011 nov matake. See LICENSE for details.
require 'bundler'
Bundler::GemHelper.install_tasks
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
namespace :coverage do
desc "Open coverage report"
task :report do
require 'simplecov'
`open "#{File.join SimpleCov.coverage_path, 'index.html'}"`
end
end
task :spec do
Rake::Task[:'coverage:report'].invoke unless ENV['TRAVIS_RUBY_VERSION']
end
task default: :spec
\ No newline at end of file
1.6.2
\ No newline at end of file
Gem::Specification.new do |gem|
gem.name = "json-jwt"
gem.version = File.read("VERSION")
gem.authors = ["nov matake"]
gem.email = ["nov@matake.jp"]
gem.homepage = "https://github.com/nov/json-jwt"
gem.summary = %q{JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and JSON Web Key) in Ruby}
gem.description = %q{JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and JSON Web Key) in Ruby}
gem.license = 'MIT'
gem.files = `git ls-files`.split("\n")
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
gem.require_paths = ["lib"]
gem.add_runtime_dependency "multi_json", ">= 1.3"
gem.add_runtime_dependency "url_safe_base64"
gem.add_runtime_dependency "activesupport"
gem.add_runtime_dependency "bindata"
gem.add_runtime_dependency "securecompare"
gem.add_development_dependency "rake", ">= 0.8"
gem.add_development_dependency "simplecov"
gem.add_development_dependency "rspec"
gem.add_development_dependency 'rspec-its'
end
\ No newline at end of file
require 'securecompare'
module JSON
module JOSE
extend ActiveSupport::Concern
included do
extend ClassMethods
include SecureCompare
register_header_keys :alg, :jku, :jwk, :x5u, :x5t, :x5c, :kid, :typ, :cty, :crit
alias_method :algorithm, :alg
attr_accessor :header
def header
@header ||= {}
end
def content_type
@content_type ||= 'application/jose'
end
end
def with_jwk_support(key)
case key
when JSON::JWK
key.to_key
when JSON::JWK::Set
key.detect do |jwk|
jwk[:kid] && jwk[:kid] == kid
end.try(:to_key) or raise JWK::Set::KidNotFound
else
key
end
end
module ClassMethods
def register_header_keys(*keys)
keys.each do |header_key|
define_method header_key do
self.header[header_key]
end
define_method "#{header_key}=" do |value|
self.header[header_key] = value
end
end
end
def decode(input, key_or_secret = nil)
if input.is_a? Hash
decode_json_serialized input, key_or_secret
else
decode_compact_serialized input, key_or_secret
end
rescue MultiJson::DecodeError
raise JWT::InvalidFormat.new("Invalid JSON Format")
end
end
end
end
\ No newline at end of file
require 'securerandom'
require 'bindata'
module JSON
class JWE
class InvalidFormat < JWT::InvalidFormat; end
class DecryptionFailed < JWT::VerificationFailed; end
class UnexpectedAlgorithm < JWT::UnexpectedAlgorithm; end
NUM_OF_SEGMENTS = 5
include JOSE
attr_accessor(
:public_key_or_secret, :private_key_or_secret,
:plain_text, :cipher_text, :authentication_tag, :iv, :auth_data,
:content_encryption_key, :jwe_encrypted_key, :encryption_key, :mac_key
)
register_header_keys :enc, :epk, :zip, :apu, :apv
alias_method :encryption_method, :enc
def initialize(input = nil)
self.plain_text = input.to_s
end
def encrypt!(public_key_or_secret)
self.public_key_or_secret = with_jwk_support public_key_or_secret
cipher.encrypt
generate_cipher_keys!
self.cipher_text = cipher.update(plain_text) + cipher.final
self
end
def decrypt!(private_key_or_secret)
self.private_key_or_secret = with_jwk_support private_key_or_secret
cipher.decrypt
restore_cipher_keys!
self.plain_text = cipher.update(cipher_text) + cipher.final
verify_cbc_authentication_tag! if cbc?
self
end
def to_s
[
header.to_json,
jwe_encrypted_key,
iv,
cipher_text,
authentication_tag
].collect do |segment|
UrlSafeBase64.encode64 segment.to_s
end.join('.')
end
def as_json(options = {})
case options[:syntax]
when :general
{
protected: UrlSafeBase64.encode64(header.to_json),
recipients: [{
encrypted_key: UrlSafeBase64.encode64(jwe_encrypted_key)
}],
iv: UrlSafeBase64.encode64(iv),
ciphertext: UrlSafeBase64.encode64(cipher_text),
tag: UrlSafeBase64.encode64(authentication_tag)
}
else
{
protected: UrlSafeBase64.encode64(header.to_json),
encrypted_key: UrlSafeBase64.encode64(jwe_encrypted_key),
iv: UrlSafeBase64.encode64(iv),
ciphertext: UrlSafeBase64.encode64(cipher_text),
tag: UrlSafeBase64.encode64(authentication_tag)
}
end
end
private
# common
def gcm_supported?
RUBY_VERSION >= '2.0.0' && OpenSSL::OPENSSL_VERSION >= 'OpenSSL 1.0.1'
end
def gcm?
[:A128GCM, :A256GCM].include? encryption_method.try(:to_sym)
end
def cbc?
[:'A128CBC-HS256', :'A256CBC-HS512'].include? encryption_method.try(:to_sym)
end
def dir?
:dir == algorithm.try(:to_sym)
end
def cipher
@cipher ||= if gcm? && !gcm_supported?
raise UnexpectedAlgorithm.new('AEC GCM requires Ruby 2.0+ and OpenSSL 1.0.1c+') if gcm? && !gcm_supported?
else
OpenSSL::Cipher.new cipher_name
end
end
def cipher_name
case encryption_method.try(:to_sym)
when :A128GCM
'aes-128-gcm'
when :A256GCM
'aes-256-gcm'
when :'A128CBC-HS256'
'aes-128-cbc'
when :'A256CBC-HS512'
'aes-256-cbc'
else
raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm')
end
end
def sha_size
case encryption_method.try(:to_sym)
when :'A128CBC-HS256'
256
when :'A256CBC-HS512'
512
else
raise UnexpectedAlgorithm.new('Unknown Hash Size')
end
end
def sha_digest
OpenSSL::Digest.new "SHA#{sha_size}"
end
def derive_encryption_and_mac_keys_cbc!
self.mac_key, self.encryption_key = content_encryption_key.unpack("a#{content_encryption_key.length / 2}" * 2)
self
end
def derive_encryption_and_mac_keys_gcm!
self.encryption_key = content_encryption_key
self.mac_key = :wont_be_used
self
end
# encryption
def jwe_encrypted_key
@jwe_encrypted_key ||= case algorithm.try(:to_sym)
when :RSA1_5
public_key_or_secret.public_encrypt content_encryption_key
when :'RSA-OAEP'
public_key_or_secret.public_encrypt content_encryption_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
when :A128KW
raise NotImplementedError.new('A128KW not supported yet')
when :A256KW
raise NotImplementedError.new('A256KW not supported yet')
when :dir
''
when :'ECDH-ES'
raise NotImplementedError.new('ECDH-ES not supported yet')
when :'ECDH-ES+A128KW'
raise NotImplementedError.new('ECDH-ES+A128KW not supported yet')
when :'ECDH-ES+A256KW'
raise NotImplementedError.new('ECDH-ES+A256KW not supported yet')
else
raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm')
end
end
def generate_cipher_keys!
case
when gcm?
generate_gcm_keys!
when cbc?
generate_cbc_keys!
end
cipher.key = encryption_key
self.iv = cipher.random_iv
self.auth_data = UrlSafeBase64.encode64 header.to_json
if gcm?
cipher.auth_data = self.auth_data
end
self
end
def generate_gcm_keys!
self.content_encryption_key ||= if dir?
public_key_or_secret
else
cipher.random_key
end
derive_encryption_and_mac_keys_gcm!
self
end
def generate_cbc_keys!
self.content_encryption_key ||= if dir?
public_key_or_secret
else
SecureRandom.random_bytes sha_size / 8
end
derive_encryption_and_mac_keys_cbc!
self
end
def authentication_tag
@authentication_tag ||= case
when gcm?
cipher.auth_tag
when cbc?
secured_input = [
auth_data,
iv,
cipher_text,
BinData::Uint64be.new(auth_data.length * 8).to_binary_s
].join
OpenSSL::HMAC.digest(
sha_digest, mac_key, secured_input
)[0, sha_size / 2 / 8]
end
end
# decryption
def decrypt_content_encryption_key
case algorithm.try(:to_sym)
when :RSA1_5
private_key_or_secret.private_decrypt jwe_encrypted_key
when :'RSA-OAEP'
private_key_or_secret.private_decrypt jwe_encrypted_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
when :A128KW
raise NotImplementedError.new('A128KW not supported yet')
when :A256KW
raise NotImplementedError.new('A256KW not supported yet')
when :dir
private_key_or_secret
when :'ECDH-ES'
raise NotImplementedError.new('ECDH-ES not supported yet')
when :'ECDH-ES+A128KW'
raise NotImplementedError.new('ECDH-ES+A128KW not supported yet')
when :'ECDH-ES+A256KW'
raise NotImplementedError.new('ECDH-ES+A256KW not supported yet')
else
raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm')
end
end
def restore_cipher_keys!
self.content_encryption_key = decrypt_content_encryption_key
case
when gcm?
derive_encryption_and_mac_keys_gcm!
when cbc?
derive_encryption_and_mac_keys_cbc!
end
cipher.key = encryption_key
cipher.iv = iv # NOTE: 'iv' has to be set after 'key' for GCM
if gcm?
cipher.auth_tag = authentication_tag
cipher.auth_data = auth_data
end
end
def verify_cbc_authentication_tag!
secured_input = [
auth_data,
iv,
cipher_text,
BinData::Uint64be.new(auth_data.length * 8).to_binary_s
].join
expected_authentication_tag = OpenSSL::HMAC.digest(
sha_digest, mac_key, secured_input
)[0, sha_size / 2 / 8]
unless secure_compare(authentication_tag, expected_authentication_tag)
raise DecryptionFailed.new('Invalid authentication tag')
end
end
class << self
def decode_compact_serialized(input, private_key_or_secret)
unless input.count('.') + 1 == NUM_OF_SEGMENTS
raise InvalidFormat.new("Invalid JWE Format. JWE should include #{NUM_OF_SEGMENTS} segments.")
end
jwe = new
_header_json_, jwe.jwe_encrypted_key, jwe.iv, jwe.cipher_text, jwe.authentication_tag = input.split('.').collect do |segment|
UrlSafeBase64.decode64 segment
end
jwe.auth_data = input.split('.').first
jwe.header = MultiJson.load(_header_json_).with_indifferent_access
jwe.decrypt! private_key_or_secret unless private_key_or_secret == :skip_decryption
jwe
end
def decode_json_serialized(input, private_key_or_secret)
input = input.with_indifferent_access
jwe_encrypted_key = if input[:recipients].present?
input[:recipients].first[:encrypted_key]
else
input[:encrypted_key]
end
compact_serialized = [
input[:protected],
jwe_encrypted_key,
input[:iv],
input[:ciphertext],
input[:tag]
].join('.')
decode_compact_serialized compact_serialized, private_key_or_secret
end
end
end
end
\ No newline at end of file
module JSON
class JWK < ActiveSupport::HashWithIndifferentAccess
class UnknownAlgorithm < JWT::Exception; end
def initialize(params = {}, ex_params = {})
case params
when OpenSSL::PKey::RSA, OpenSSL::PKey::EC
super params.to_jwk(ex_params)
when OpenSSL::PKey::PKey
raise UnknownAlgorithm.new('Unknown Key Type')
when String
super(
k: params,
kty: :oct
)
merge! ex_params
else
super params
merge! ex_params
end
self[:kid] ||= thumbprint rescue nil #ignore
end
def content_type
'application/jwk+json'
end
def thumbprint(digest = OpenSSL::Digest::SHA256.new)
digest = case digest
when OpenSSL::Digest
digest
when String, Symbol
OpenSSL::Digest.new digest.to_s
else
raise UnknownAlgorithm.new('Unknown Digest Algorithm')
end
UrlSafeBase64.encode64 digest.digest(normalize.to_json)
end
def to_key
case
when rsa?
to_rsa_key
when ec?
to_ec_key
when oct?
self[:k]
else
raise UnknownAlgorithm.new('Unknown Key Type')
end
end
private
def rsa?
self[:kty].try(:to_sym) == :RSA
end
def ec?
self[:kty].try(:to_sym) == :EC
end
def oct?
self[:kty].try(:to_sym) == :oct
end
def normalize
case
when rsa?
{
e: self[:e],
kty: self[:kty],
n: self[:n]
}
when ec?
{
crv: self[:crv],
kty: self[:kty],
x: self[:x],
y: self[:y]
}
when oct?
{
k: self[:k],
kty: self[:kty]
}
else
raise UnknownAlgorithm.new('Unknown Key Type')
end
end
def to_rsa_key
e, n, d, p, q = [:e, :n, :d, :p, :q].collect do |key|
if self[key]
OpenSSL::BN.new UrlSafeBase64.decode64(self[key]), 2
end
end
key = OpenSSL::PKey::RSA.new
key.e = e
key.n = n
key.d = d if d
key.p = p if p
key.q = q if q
key
end
def to_ec_key
curve_name = case self[:crv].try(:to_sym)
when :'P-256'
'prime256v1'
when :'P-384'
'secp384r1'
when :'P-521'
'secp521r1'
else
raise UnknownAlgorithm.new('Unknown EC Curve')
end
x, y, d = [:x, :y, :d].collect do |key|
if self[key]
OpenSSL::BN.new UrlSafeBase64.decode64(self[key]), 2
end
end
key = OpenSSL::PKey::EC.new curve_name
key.private_key = d if d
key.public_key = OpenSSL::PKey::EC::Point.new(
OpenSSL::PKey::EC::Group.new(curve_name),
OpenSSL::BN.new(['04' + x.to_s(16) + y.to_s(16)].pack('H*'), 2)
)
key
end
end
end
\ No newline at end of file
module JSON
class JWK
module JWKizable
module RSA
def to_jwk(ex_params = {})
params = {
kty: :RSA,
e: UrlSafeBase64.encode64(e.to_s(2)),
n: UrlSafeBase64.encode64(n.to_s(2))
}.merge ex_params
if private?
params.merge!(
d: UrlSafeBase64.encode64(d.to_s(2)),
p: UrlSafeBase64.encode64(p.to_s(2)),
q: UrlSafeBase64.encode64(q.to_s(2))
)
end
JWK.new params
end
end
module EC
def to_jwk(ex_params = {})
params = {
kty: :EC,
crv: curve_name,
x: UrlSafeBase64.encode64(coordinates[:x].to_s(2)),
y: UrlSafeBase64.encode64(coordinates[:y].to_s(2))
}.merge ex_params
params[:d] = UrlSafeBase64.encode64(coordinates[:d].to_s(2)) if private_key?
JWK.new params
end
private
def curve_name
case group.curve_name
when 'prime256v1'
:'P-256'
when 'secp384r1'
:'P-384'
when 'secp521r1'
:'P-521'
else
raise UnknownAlgorithm.new('Unknown EC Curve')
end
end
def coordinates
unless @coordinates
hex = public_key.to_bn.to_s(16)
data_len = hex.length - 2