pax_global_header00006660000000000000000000000064136650406240014520gustar00rootroot0000000000000052 comment=26731e0d933b7d9470f781d06079d842036ca640 json-jwt-1.13.0/000077500000000000000000000000001366504062400133555ustar00rootroot00000000000000json-jwt-1.13.0/.gitignore000066400000000000000000000002471366504062400153500ustar00rootroot00000000000000## MAC OS .DS_Store ## TEXTMATE *.tmproj tmtags ## EMACS *~ \#* .\#* ## VIM *.swp ## PROJECT::GENERAL coverage* rdoc pkg Gemfile.lock ## PROJECT::SPECIFIC .class json-jwt-1.13.0/.gitmodules000066400000000000000000000002001366504062400155220ustar00rootroot00000000000000[submodule "spec/helpers/json-jwt-nimbus"] path = spec/helpers/json-jwt-nimbus url = git://github.com/nov/json-jwt-nimbus.git json-jwt-1.13.0/.rspec000066400000000000000000000000371366504062400144720ustar00rootroot00000000000000--color --format=documentation json-jwt-1.13.0/.travis.yml000066400000000000000000000002141366504062400154630ustar00rootroot00000000000000before_install: - gem install bundler - git submodule update --init --recursive rvm: - 2.5.8 - 2.6.6 - 2.7.1 jdk: - openjdk11 json-jwt-1.13.0/Gemfile000066400000000000000000000000451366504062400146470ustar00rootroot00000000000000source "http://rubygems.org" gemspecjson-jwt-1.13.0/LICENSE000066400000000000000000000020361366504062400143630ustar00rootroot00000000000000Copyright (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-1.13.0/README.md000066400000000000000000000032251366504062400146360ustar00rootroot00000000000000# 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. json-jwt-1.13.0/Rakefile000066400000000000000000000006171366504062400150260ustar00rootroot00000000000000require '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: :specjson-jwt-1.13.0/VERSION000066400000000000000000000000061366504062400144210ustar00rootroot000000000000001.13.0json-jwt-1.13.0/json-jwt.gemspec000066400000000000000000000020641366504062400164770ustar00rootroot00000000000000Gem::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").reject do |f| f.match(%r{^(test|spec|features)/}) end gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } gem.require_paths = ['lib'] gem.required_ruby_version = '>= 2.4' gem.add_runtime_dependency 'activesupport', '>= 4.2' gem.add_runtime_dependency 'bindata' gem.add_runtime_dependency 'aes_key_wrap' gem.add_development_dependency 'rake' gem.add_development_dependency 'simplecov' gem.add_development_dependency 'rspec' gem.add_development_dependency 'rspec-its' end json-jwt-1.13.0/lib/000077500000000000000000000000001366504062400141235ustar00rootroot00000000000000json-jwt-1.13.0/lib/json/000077500000000000000000000000001366504062400150745ustar00rootroot00000000000000json-jwt-1.13.0/lib/json/jose.rb000066400000000000000000000036701366504062400163670ustar00rootroot00000000000000require 'active_support/security_utils' module JSON module JOSE extend ActiveSupport::Concern included do extend ClassMethods register_header_keys :alg, :jku, :jwk, :x5u, :x5t, :x5c, :kid, :typ, :cty, :crit # NOTE: not used anymore in this gem, but keeping in case developers are calling it. alias_method :algorithm, :alg attr_writer :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&.to_key or raise JWK::Set::KidNotFound else key end end def secure_compare(a, b) if ActiveSupport::SecurityUtils.respond_to?(:fixed_length_secure_compare) begin ActiveSupport::SecurityUtils.fixed_length_secure_compare(a, b) rescue ArgumentError false end else ActiveSupport::SecurityUtils.secure_compare(a, b) 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, algorithms = nil, encryption_methods = nil, allow_blank_payload = false) if input.is_a? Hash decode_json_serialized input, key_or_secret, algorithms, encryption_methods, allow_blank_payload else decode_compact_serialized input, key_or_secret, algorithms, encryption_methods, allow_blank_payload end rescue JSON::ParserError, ArgumentError raise JWT::InvalidFormat.new("Invalid JSON Format") end end end end json-jwt-1.13.0/lib/json/jwe.rb000066400000000000000000000224161366504062400162130ustar00rootroot00000000000000require 'securerandom' require 'bindata' require 'aes_key_wrap' 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, :iv, :auth_data, :content_encryption_key, :encryption_key, :mac_key ) attr_writer :jwe_encrypted_key, :authentication_tag 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 self.content_encryption_key = generate_content_encryption_key self.mac_key, self.encryption_key = derive_encryption_and_mac_keys cipher.key = encryption_key self.iv = cipher.random_iv # NOTE: 'iv' has to be set after 'key' for GCM self.auth_data = Base64.urlsafe_encode64 header.to_json, padding: false cipher.auth_data = auth_data if gcm? self.cipher_text = cipher.update(plain_text) + cipher.final self end def decrypt!(private_key_or_secret, algorithms = nil, encryption_methods = nil) raise UnexpectedAlgorithm.new('Unexpected alg header') unless algorithms.blank? || Array(algorithms).include?(alg) raise UnexpectedAlgorithm.new('Unexpected enc header') unless encryption_methods.blank? || Array(encryption_methods).include?(enc) self.private_key_or_secret = with_jwk_support private_key_or_secret cipher.decrypt self.content_encryption_key = decrypt_content_encryption_key self.mac_key, self.encryption_key = derive_encryption_and_mac_keys cipher.key = encryption_key cipher.iv = iv # NOTE: 'iv' has to be set after 'key' for GCM if gcm? # https://github.com/ruby/openssl/issues/63 raise DecryptionFailed.new('Invalid authentication tag') if authentication_tag.length < 16 cipher.auth_tag = authentication_tag cipher.auth_data = auth_data end 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| Base64.urlsafe_encode64 segment.to_s, padding: false end.join('.') end def as_json(options = {}) case options[:syntax] when :general { protected: Base64.urlsafe_encode64(header.to_json, padding: false), recipients: [{ encrypted_key: Base64.urlsafe_encode64(jwe_encrypted_key, padding: false) }], iv: Base64.urlsafe_encode64(iv, padding: false), ciphertext: Base64.urlsafe_encode64(cipher_text, padding: false), tag: Base64.urlsafe_encode64(authentication_tag, padding: false) } else { protected: Base64.urlsafe_encode64(header.to_json, padding: false), encrypted_key: Base64.urlsafe_encode64(jwe_encrypted_key, padding: false), iv: Base64.urlsafe_encode64(iv, padding: false), ciphertext: Base64.urlsafe_encode64(cipher_text, padding: false), tag: Base64.urlsafe_encode64(authentication_tag, padding: false) } end end private # common def gcm? [:A128GCM, :A256GCM].include? encryption_method&.to_sym end def cbc? [:'A128CBC-HS256', :'A256CBC-HS512'].include? encryption_method&.to_sym end def dir? :dir == alg&.to_sym end def cipher raise "#{cipher_name} isn't supported" unless OpenSSL::Cipher.ciphers.include?(cipher_name) @cipher ||= OpenSSL::Cipher.new cipher_name end def cipher_name case encryption_method&.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&.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 case when gcm? [:wont_be_used, content_encryption_key] when cbc? content_encryption_key.unpack( "a#{content_encryption_key.length / 2}" * 2 ) end end # encryption def jwe_encrypted_key @jwe_encrypted_key ||= case alg&.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, :A256KW AESKeyWrap.wrap content_encryption_key, public_key_or_secret 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_content_encryption_key case when dir? public_key_or_secret when gcm? cipher.random_key when cbc? SecureRandom.random_bytes sha_size / 8 end 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 fake_content_encryption_key = generate_content_encryption_key # NOTE: do this always not to make timing difference case alg&.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, :A256KW AESKeyWrap.unwrap jwe_encrypted_key, private_key_or_secret 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 rescue OpenSSL::PKey::PKeyError fake_content_encryption_key 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, algorithms = nil, encryption_methods = nil, _allow_blank_payload = false) 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('.', NUM_OF_SEGMENTS).collect do |segment| begin Base64.urlsafe_decode64 segment rescue ArgumentError raise DecryptionFailed end end jwe.auth_data = input.split('.').first jwe.header = JSON.parse(_header_json_).with_indifferent_access unless private_key_or_secret == :skip_decryption jwe.decrypt! private_key_or_secret, algorithms, encryption_methods end jwe end def decode_json_serialized(input, private_key_or_secret, algorithms = nil, encryption_methods = nil, _allow_blank_payload = false) 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, algorithms, encryption_methods end end end end json-jwt-1.13.0/lib/json/jwk.rb000066400000000000000000000064171366504062400162240ustar00rootroot00000000000000module 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 calculate_default_kid if self[:kid].blank? 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 Base64.urlsafe_encode64 digest.digest(normalize.to_json), padding: false 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 def rsa? self[:kty]&.to_sym == :RSA end def ec? self[:kty]&.to_sym == :EC end def oct? self[:kty]&.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 private def calculate_default_kid self[:kid] = thumbprint rescue # ignore end def to_rsa_key e, n, d, p, q, dp, dq, qi = [:e, :n, :d, :p, :q, :dp, :dq, :qi].collect do |key| if self[key] OpenSSL::BN.new Base64.urlsafe_decode64(self[key]), 2 end end key = OpenSSL::PKey::RSA.new if key.respond_to? :set_key key.set_key n, e, d key.set_factors p, q if p && q key.set_crt_params dp, dq, qi if dp && dq && qi else key.e = e key.n = n key.d = d if d key.p = p if p key.q = q if q key.dmp1 = dp if dp key.dmq1 = dq if dq key.iqmp = qi if qi end key end def to_ec_key curve_name = case self[:crv]&.to_sym when :'P-256' 'prime256v1' when :'P-384' 'secp384r1' when :'P-521' 'secp521r1' when :secp256k1 'secp256k1' else raise UnknownAlgorithm.new('Unknown EC Curve') end x, y, d = [:x, :y, :d].collect do |key| if self[key] Base64.urlsafe_decode64(self[key]) end end key = OpenSSL::PKey::EC.new curve_name key.private_key = OpenSSL::BN.new(d, 2) if d key.public_key = OpenSSL::PKey::EC::Point.new( OpenSSL::PKey::EC::Group.new(curve_name), OpenSSL::BN.new(['04' + x.unpack('H*').first + y.unpack('H*').first].pack('H*'), 2) ) key end end end json-jwt-1.13.0/lib/json/jwk/000077500000000000000000000000001366504062400156675ustar00rootroot00000000000000json-jwt-1.13.0/lib/json/jwk/jwkizable.rb000066400000000000000000000043451366504062400202040ustar00rootroot00000000000000module JSON class JWK module JWKizable module RSA def to_jwk(ex_params = {}) params = { kty: :RSA, e: Base64.urlsafe_encode64(e.to_s(2), padding: false), n: Base64.urlsafe_encode64(n.to_s(2), padding: false) }.merge ex_params if private? params.merge!( d: Base64.urlsafe_encode64(d.to_s(2), padding: false), p: Base64.urlsafe_encode64(p.to_s(2), padding: false), q: Base64.urlsafe_encode64(q.to_s(2), padding: false), dp: Base64.urlsafe_encode64(dmp1.to_s(2), padding: false), dq: Base64.urlsafe_encode64(dmq1.to_s(2), padding: false), qi: Base64.urlsafe_encode64(iqmp.to_s(2), padding: false), ) end JWK.new params end end module EC def to_jwk(ex_params = {}) params = { kty: :EC, crv: curve_name, x: Base64.urlsafe_encode64([coordinates[:x]].pack('H*'), padding: false), y: Base64.urlsafe_encode64([coordinates[:y]].pack('H*'), padding: false) }.merge ex_params params[:d] = Base64.urlsafe_encode64([coordinates[:d]].pack('H*'), padding: false) 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' when 'secp256k1' :secp256k1 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 hex_x = hex[2, data_len / 2] hex_y = hex[2 + data_len / 2, data_len / 2] @coordinates = { x: hex_x, y: hex_y } @coordinates[:d] = private_key.to_s(16) if private_key? end @coordinates end end end end end OpenSSL::PKey::RSA.send :include, JSON::JWK::JWKizable::RSA OpenSSL::PKey::EC.send :include, JSON::JWK::JWKizable::EC json-jwt-1.13.0/lib/json/jwk/set.rb000066400000000000000000000011621366504062400170070ustar00rootroot00000000000000module JSON class JWK class Set < Array class KidNotFound < JWT::Exception; end def initialize(*jwks) jwks = if jwks.first.is_a?(Hash) && (keys = jwks.first.with_indifferent_access[:keys]) keys else jwks end jwks = Array(jwks).flatten.collect do |jwk| JWK.new jwk end replace jwks end def content_type 'application/jwk-set+json' end def as_json(options = {}) # NOTE: Array.new wrapper is requied to avoid CircularReferenceError {keys: Array.new(self)} end end end endjson-jwt-1.13.0/lib/json/jws.rb000066400000000000000000000154121366504062400162270ustar00rootroot00000000000000module JSON class JWS < JWT class InvalidFormat < JWT::InvalidFormat; end class VerificationFailed < JWT::VerificationFailed; end class UnexpectedAlgorithm < JWT::UnexpectedAlgorithm; end NUM_OF_SEGMENTS = 3 attr_writer :signature_base_string def initialize(jwt) update jwt end def sign!(private_key_or_secret) self.alg = autodetected_algorithm_from(private_key_or_secret) if alg == :autodetect self.signature = sign signature_base_string, private_key_or_secret self end def verify!(public_key_or_secret, algorithms = nil) if alg&.to_sym == :none raise UnexpectedAlgorithm if public_key_or_secret signature == '' or raise VerificationFailed elsif algorithms.blank? || Array(algorithms).include?(alg&.to_sym) public_key_or_secret && valid?(public_key_or_secret) or raise VerificationFailed else raise UnexpectedAlgorithm.new('Unexpected alg header') end end def update(hash_or_jwt) super if hash_or_jwt.is_a? JSON::JWT self.header.update hash_or_jwt.header self.signature = hash_or_jwt.signature self.blank_payload = hash_or_jwt.blank_payload end self end private def digest OpenSSL::Digest.new "SHA#{alg.to_s[2, 3]}" end def hmac? [:HS256, :HS384, :HS512].include? alg&.to_sym end def rsa? [:RS256, :RS384, :RS512].include? alg&.to_sym end def rsa_pss? [:PS256, :PS384, :PS512].include? alg&.to_sym end def ecdsa? [:ES256, :ES384, :ES512, :ES256K].include? alg&.to_sym end def autodetected_algorithm_from(private_key_or_secret) private_key_or_secret = with_jwk_support private_key_or_secret case private_key_or_secret when String :HS256 when OpenSSL::PKey::RSA :RS256 when OpenSSL::PKey::EC case private_key_or_secret.group.curve_name when 'prime256v1' :ES256 when 'secp384r1' :ES384 when 'secp521r1' :ES512 when 'secp256k1' :ES256K else raise UnknownAlgorithm.new('Unknown EC Curve') end else raise UnexpectedAlgorithm.new('Signature algorithm auto-detection failed') end end def signature_base_string @signature_base_string ||= [ header.to_json, self.to_json ].collect do |segment| Base64.urlsafe_encode64 segment, padding: false end.join('.') end def sign(signature_base_string, private_key_or_secret) private_key_or_secret = with_jwk_support private_key_or_secret case when hmac? secret = private_key_or_secret OpenSSL::HMAC.digest digest, secret, signature_base_string when rsa? private_key = private_key_or_secret private_key.sign digest, signature_base_string when rsa_pss? private_key = private_key_or_secret private_key.sign_pss digest, signature_base_string, salt_length: :digest, mgf1_hash: digest when ecdsa? private_key = private_key_or_secret verify_ecdsa_group! private_key asn1_to_raw( private_key.sign(digest, signature_base_string), private_key ) else raise UnexpectedAlgorithm.new('Unknown Signature Algorithm') end end def valid?(public_key_or_secret) public_key_or_secret = with_jwk_support public_key_or_secret case when hmac? secure_compare sign(signature_base_string, public_key_or_secret), signature when rsa? public_key = public_key_or_secret public_key.verify digest, signature, signature_base_string when rsa_pss? public_key = public_key_or_secret public_key.verify_pss digest, signature, signature_base_string, salt_length: :digest, mgf1_hash: digest when ecdsa? public_key = public_key_or_secret verify_ecdsa_group! public_key public_key.verify digest, raw_to_asn1(signature, public_key), signature_base_string else raise UnexpectedAlgorithm.new('Unknown Signature Algorithm') end rescue TypeError => e raise UnexpectedAlgorithm.new(e.message) end def verify_ecdsa_group!(key) group_name = case digest.digest_length * 8 when 256 case key.group.curve_name when 'secp256k1' :secp256k1 else :prime256v1 end when 384 :secp384r1 when 512 :secp521r1 end key.group = OpenSSL::PKey::EC::Group.new group_name.to_s key.check_key end def raw_to_asn1(signature, public_key) byte_size = (public_key.group.degree + 7) / 8 r = signature[0..(byte_size - 1)] s = signature[byte_size..-1] OpenSSL::ASN1::Sequence.new([r, s].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der end def asn1_to_raw(signature, private_key) byte_size = (private_key.group.degree + 7) / 8 OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join end class << self def decode_compact_serialized(input, public_key_or_secret, algorithms = nil, allow_blank_payload = false) unless input.count('.') + 1 == NUM_OF_SEGMENTS raise InvalidFormat.new("Invalid JWS Format. JWS should include #{NUM_OF_SEGMENTS} segments.") end header, claims, signature = input.split('.', NUM_OF_SEGMENTS).collect do |segment| Base64.urlsafe_decode64 segment.to_s end header = JSON.parse(header).with_indifferent_access if allow_blank_payload && claims == '' claims = nil else claims = JSON.parse(claims).with_indifferent_access end jws = new claims jws.header = header jws.signature = signature jws.signature_base_string = input.split('.')[0, NUM_OF_SEGMENTS - 1].join('.') jws.verify! public_key_or_secret, algorithms unless public_key_or_secret == :skip_verification jws end def decode_json_serialized(input, public_key_or_secret, algorithms = nil, allow_blank_payload = false) input = input.with_indifferent_access header, payload, signature = if input[:signatures].present? [ input[:signatures].first[:protected], input[:payload], input[:signatures].first[:signature] ].collect do |segment| segment end else [:protected, :payload, :signature].collect do |key| input[key] end end compact_serialized = [header, payload, signature].join('.') decode_compact_serialized compact_serialized, public_key_or_secret, algorithms, allow_blank_payload end end end end json-jwt-1.13.0/lib/json/jwt.rb000066400000000000000000000076261366504062400162400ustar00rootroot00000000000000require 'openssl' require 'base64' require 'active_support' require 'active_support/core_ext' require 'json/jose' module JSON class JWT < ActiveSupport::HashWithIndifferentAccess attr_accessor :blank_payload attr_accessor :signature class Exception < StandardError; end class InvalidFormat < Exception; end class VerificationFailed < Exception; end class UnexpectedAlgorithm < VerificationFailed; end include JOSE def initialize(claims = {}) @content_type = 'application/jwt' self.typ = :JWT self.alg = :none unless claims.nil? [:exp, :nbf, :iat].each do |key| claims[key] = claims[key].to_i if claims[key] end end update claims end def sign(private_key_or_secret, algorithm = :autodetect) jws = JWS.new self jws.kid ||= private_key_or_secret[:kid] if private_key_or_secret.is_a? JSON::JWK jws.alg = algorithm jws.sign! private_key_or_secret end def encrypt(public_key_or_secret, algorithm = :RSA1_5, encryption_method = :'A128CBC-HS256') jwe = JWE.new self jwe.kid ||= public_key_or_secret[:kid] if public_key_or_secret.is_a? JSON::JWK jwe.alg = algorithm jwe.enc = encryption_method jwe.encrypt! public_key_or_secret end def to_s [ header.to_json, self.to_json, signature ].collect do |segment| Base64.urlsafe_encode64 segment.to_s, padding: false end.join('.') end def as_json(options = {}) case options[:syntax] when :general { payload: Base64.urlsafe_encode64(self.to_json, padding: false), signatures: [{ protected: Base64.urlsafe_encode64(header.to_json, padding: false), signature: Base64.urlsafe_encode64(signature.to_s, padding: false) }] } when :flattened { protected: Base64.urlsafe_encode64(header.to_json, padding: false), payload: Base64.urlsafe_encode64(self.to_json, padding: false), signature: Base64.urlsafe_encode64(signature.to_s, padding: false) } else super end end def to_json *args if @blank_payload && args.empty? '' else super end end def update claims if claims.nil? @blank_payload = true else super end end def pretty_generate [ JSON.pretty_generate(header), JSON.pretty_generate(self) ] end class << self def decode_compact_serialized(jwt_string, key_or_secret, algorithms = nil, encryption_methods = nil, allow_blank_payload = false) case jwt_string.count('.') + 1 when JWS::NUM_OF_SEGMENTS JWS.decode_compact_serialized jwt_string, key_or_secret, algorithms, allow_blank_payload when JWE::NUM_OF_SEGMENTS JWE.decode_compact_serialized jwt_string, key_or_secret, algorithms, encryption_methods else raise InvalidFormat.new("Invalid JWT Format. JWT should include #{JWS::NUM_OF_SEGMENTS} or #{JWE::NUM_OF_SEGMENTS} segments.") end end def decode_json_serialized(input, key_or_secret, algorithms = nil, encryption_methods = nil, allow_blank_payload = false) input = input.with_indifferent_access if (input[:signatures] || input[:signature]).present? JWS.decode_json_serialized input, key_or_secret, algorithms, allow_blank_payload elsif input[:ciphertext].present? JWE.decode_json_serialized input, key_or_secret, algorithms, encryption_methods else raise InvalidFormat.new("Unexpected JOSE JSON Serialization Format.") end end def pretty_generate(jwt_string) decode(jwt_string, :skip_verification).pretty_generate end end end end require 'json/jws' require 'json/jwe' require 'json/jwk' require 'json/jwk/jwkizable' require 'json/jwk/set' json-jwt-1.13.0/spec/000077500000000000000000000000001366504062400143075ustar00rootroot00000000000000json-jwt-1.13.0/spec/fixtures/000077500000000000000000000000001366504062400161605ustar00rootroot00000000000000json-jwt-1.13.0/spec/fixtures/ecdsa/000077500000000000000000000000001366504062400172375ustar00rootroot00000000000000json-jwt-1.13.0/spec/fixtures/ecdsa/256/000077500000000000000000000000001366504062400175535ustar00rootroot00000000000000json-jwt-1.13.0/spec/fixtures/ecdsa/256/private_key.pem000066400000000000000000000003421366504062400225770ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHcCAQEEIHo5LvIgMVpOlEKjjZiE5n+xYtTxLm4Eumx7FRMgICyDoAoGCCqGSM49 AwEHoUQDQgAEsaPyrO4Lh9kh2FxrF9y1QVmZznWnRRJwpr12UHqzrVYwzPhb3POq WsmGqv4nKum+WdogjJlAToN+uA+TEwDDUw== -----END EC PRIVATE KEY-----json-jwt-1.13.0/spec/fixtures/ecdsa/256/public_key.pem000066400000000000000000000002611366504062400224030ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsaPyrO4Lh9kh2FxrF9y1QVmZznWn RRJwpr12UHqzrVYwzPhb3POqWsmGqv4nKum+WdogjJlAToN+uA+TEwDDUw== -----END PUBLIC KEY-----json-jwt-1.13.0/spec/fixtures/ecdsa/256/secp256k1/000077500000000000000000000000001366504062400211765ustar00rootroot00000000000000json-jwt-1.13.0/spec/fixtures/ecdsa/256/secp256k1/private_key.pem000066400000000000000000000003361366504062400242250ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHQCAQEEINMWuYrnHN2TF03tNQYYssscDLNnDFoEdL9cuR7Ld7vfoAcGBSuBBAAK oUQDQgAEYMHAtmtrOmrZMPdy550VQ9xNgfO+1jvh2cc++uf4LITkVZ9B8vrpLsKg RARj3hLH1WyllCupbEl0sKQn8EwMDA== -----END EC PRIVATE KEY-----json-jwt-1.13.0/spec/fixtures/ecdsa/256/secp256k1/public_key.pem000066400000000000000000000002551366504062400240310ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEYMHAtmtrOmrZMPdy550VQ9xNgfO+1jvh 2cc++uf4LITkVZ9B8vrpLsKgRARj3hLH1WyllCupbEl0sKQn8EwMDA== -----END PUBLIC KEY-----json-jwt-1.13.0/spec/fixtures/ecdsa/384/000077500000000000000000000000001366504062400175555ustar00rootroot00000000000000json-jwt-1.13.0/spec/fixtures/ecdsa/384/private_key.pem000066400000000000000000000004401366504062400226000ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIGkAgEBBDB1NRLzYeQa7oRUwWrnQFZOBVqzlyJ9n654/PFjCLJh/A/uGWeECoM2 1hXEvp80pqGgBwYFK4EEACKhZANiAASmXMCnIWcrurOGDlechlsWPaFmgfZV2Xj5 EWbsOew0wb23Kqul+rZHKN8oAFtwVG2LEHN9+GTd9xuZ6KkYuS9AE0LN42bpAveE 5RMfogUHM4vRjsewZOik1NOykuOWK9s= -----END EC PRIVATE KEY----- json-jwt-1.13.0/spec/fixtures/ecdsa/384/public_key.pem000066400000000000000000000003261366504062400224070ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEplzApyFnK7qzhg5XnIZbFj2hZoH2Vdl4 +RFm7DnsNMG9tyqrpfq2RyjfKABbcFRtixBzffhk3fcbmeipGLkvQBNCzeNm6QL3 hOUTH6IFBzOL0Y7HsGTopNTTspLjlivb -----END PUBLIC KEY-----json-jwt-1.13.0/spec/fixtures/ecdsa/512/000077500000000000000000000000001366504062400175465ustar00rootroot00000000000000json-jwt-1.13.0/spec/fixtures/ecdsa/512/private_key.pem000066400000000000000000000005541366504062400225770ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIBBpwKqvGEZGpE3wX1fDzJjrrM4uXr16WKsijjqjRP8tHdnvr5p2fO zrPVyDVbiQDulOhSh9aouunuwmbudKjWvZagBwYFK4EEACOhgYkDgYYABAHDAg/m tGuq5xPU7wtJjqhfwxl0YOWN4k2+HhzcE5tpA+oro8fTP3/HfxRh69DoaasxJ+K2 D2GaLhrGyDxIC9Kv/wFC2BHfJfm1fwSNvPWns4Ui2dUQxdpbYAzxMvWO2LamGuHC XKYss1QzKV1sAaenI4Ok1yDZKFa1V2YTeNOIobuCNg== -----END EC PRIVATE KEY-----json-jwt-1.13.0/spec/fixtures/ecdsa/512/public_key.pem000066400000000000000000000004131366504062400223750ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBwwIP5rRrqucT1O8LSY6oX8MZdGDl jeJNvh4c3BObaQPqK6PH0z9/x38UYevQ6GmrMSfitg9hmi4axsg8SAvSr/8BQtgR 3yX5tX8Ejbz1p7OFItnVEMXaW2AM8TL1jti2phrhwlymLLNUMyldbAGnpyODpNcg 2ShWtVdmE3jTiKG7gjY= -----END PUBLIC KEY-----json-jwt-1.13.0/spec/fixtures/rsa/000077500000000000000000000000001366504062400167455ustar00rootroot00000000000000json-jwt-1.13.0/spec/fixtures/rsa/private_key.der000066400000000000000000000023021366504062400217600ustar00rootroot0000000000000000  *H 0ͅүz@9_ăDyi%3K?m IT'jyW0>[y ]+O MF?36℞KElG2A3Ƀi{)۽Ni<DM F5 ˚%$ z[rOhsV]')yZ!8TfH'!-@fSLփ.'o|H%H@Cܳ1Lǫ6a?yjJ蕊_:=N=,}sK|PaoҿwI{jB;>ogG}4-Ayzs8c%腙dž6TmSqLӚ*J.}]cg,5& 4?8n 5AOɣjrݒsDw!H q!7L эx4r4\`4mױvwRq.Mg= Um_ĸfMվ3*W8x%)u8cʳT0tƓBje￁(% T׌عU-B mKm>O=Oz^L5޷OKҝILwen;EMc7lq t7ݬ(/M{EP8`J>қ  1]&ǯ1Lyz뭸MRz .?6sSޠG?6|fO_e0kU});qv &pu21 V3ӨRAhw8n=%U" V8%B>\ Gcg wC%]!.Rnjۿ"vu<@ہ7/TK1pCj_~y'rfB*DhRfOPOYǭ_Y4sXW3omڈGԍ ƱUh*Q;( 0C45C$p(蛫JP%j/Ҝ~:U|*Aޫo(fĆSV,K20)Â*?3/;U0 q\ {'\8i5UlNS~ĺGSکc+lW?+5&ڤ?=~jmn7 { x: 'saPyrO4Lh9kh2FxrF9y1QVmZznWnRRJwpr12UHqzrVY', y: 'MMz4W9zzqlrJhqr-JyrpvlnaIIyZQE6DfrgPkxMAw1M' }, 384 => { x: 'plzApyFnK7qzhg5XnIZbFj2hZoH2Vdl4-RFm7DnsNMG9tyqrpfq2RyjfKABbcFRt', y: 'ixBzffhk3fcbmeipGLkvQBNCzeNm6QL3hOUTH6IFBzOL0Y7HsGTopNTTspLjlivb' }, 512 => { x: 'AcMCD-a0a6rnE9TvC0mOqF_DGXRg5Y3iTb4eHNwTm2kD6iujx9M_f8d_FGHr0OhpqzEn4rYPYZouGsbIPEgL0q__', y: 'AULYEd8l-bV_BI289aezhSLZ1RDF2ltgDPEy9Y7YtqYa4cJcpiyzVDMpXWwBp6cjg6TXINkoVrVXZhN404ihu4I2' } } end [256, 384, 512].each do |digest_length| describe "EC#{digest_length}" do let(:jwk) { JSON::JWK.new public_key(:ecdsa, digest_length: digest_length) } it { jwk.keys.collect(&:to_sym).should include :kty, :crv, :x, :y } its(:kty) { jwk[:kty].should == :EC } its(:x) { jwk[:x].should == expected_coordinates[digest_length][:x] } its(:y) { jwk[:y].should == expected_coordinates[digest_length][:y] } end end describe 'unknown curve' do it do key = OpenSSL::PKey::EC.new('secp112r2').generate_key expect do JSON::JWK.new key end.to raise_error JSON::JWK::UnknownAlgorithm, 'Unknown EC Curve' end end describe '#thumbprint' do context 'using default hash function' do subject { jwk.thumbprint } it { should == '-egRpLjyZCqxBh4OOfd8JSvXwayHmNFAUNkbi8exfhc' } end context 'using SHA512 hash function' do subject { jwk.thumbprint :SHA512 } it { should == 'B_yXDZJ9doudaVCj5q5vqxshvVtW2IFnz_ypvRt5O60gemkDAhO78L6YMyTWH0ZRm15cO2_laTSaNO9yZQFsvQ' } end end describe '#to_key' do it { jwk.to_key.should be_instance_of OpenSSL::PKey::EC } end end context 'when shared secret given' do let(:jwk) { JSON::JWK.new 'secret' } its(:kty) { jwk[:kty].should == :oct } its(:x) { jwk[:k].should == 'secret' } describe '#thumbprint' do context 'using default hash function' do subject { jwk.thumbprint } it { should == 'XZPWsTEZFIerowAF9GHzBtq5CkAOcVvIBnkMu0IIQH0' } end context 'using SHA512 hash function' do subject { jwk.thumbprint :SHA512 } it { should == 'rK7EtcEe9Xr0kryR9lNnyOTRe7Vb_BglbTBtbcVG2LzvL26_PFaMCwOtiUiXWfCK-wV8vcxjmvbcvV4ZxDE0FQ' } end end describe '#to_key' do it { jwk.to_key.should be_instance_of String } end end describe 'unknown key type' do it do key = OpenSSL::PKey::DSA.generate 256 expect do JSON::JWK.new key end.to raise_error JSON::JWK::UnknownAlgorithm, 'Unknown Key Type' end end end json-jwt-1.13.0/spec/json/jws_spec.rb000066400000000000000000000373671366504062400174420ustar00rootroot00000000000000require 'spec_helper' describe JSON::JWS do let(:alg) { :none } let(:jwt) do _jwt_ = JSON::JWT.new claims _jwt_.alg = alg _jwt_ end let(:jwt_blank) do _jwt_ = JSON::JWT.new nil _jwt_.alg = alg _jwt_ end let(:jws) { JSON::JWS.new jwt } let(:jws_blank) { JSON::JWS.new jwt_blank } let(:signed) { jws.sign! private_key_or_secret } let(:signed_blank) { jws_blank.sign! private_key_or_secret } let(:decoded) { JSON::JWT.decode signed.to_s, public_key_or_secret } let(:decoded_blank) { JSON::JWT.decode signed_blank.to_s, public_key_or_secret, nil, nil, true } let(:claims) do { iss: 'joe', exp: 1300819380, :'http://example.com/is_root' => true } end let(:expected_signature) do { :HS256 => 'DyuTgO2Ggb5nrhkkhI-RjVYIBe3o8oL4ijkAn94YPxQ', :HS384 => 'a5-7rr61TG8Snv9xxJ7l064ky-SCq1Mswe9t8HEorvoc_nnfIeUy9WQCLMIli34R', :HS512 => 'ce-GlHDaNwaHfmAFRGp3QPPKvrpruTug2hC1bf6yNlbuvkMwJw2jFZgq_4wmIPetRdiBy7XFq7rrtmw1Im7tmQ', :RS256 => 'E5VELqAdla2Bx1axc9KFxO0EiCr0Mw6HPYX070qGQ8zA_XmyxGPUZLyyWU_6Cn399W-oYBWO2ynLlr8pqqjP3jXevyCeYeGRVN0HzLYiBebEugNnc3hevr7WV2UzfksWRA-Ux2bDv2sz9p_LGbL33wWNxGDvIlpDyZUul_a48nCipS0riBjkTLTSE8dfBxQTXEF5GEUUu99ot6aBLzUhc25nHXSXogXF6MHK-hAcE7f4v-vJ0lbPbHLVGUopIoxoqe4XjoBpzE5UvhrVl5LYbdjbyJhu5ZIA8GLsgwtUFh3dfdIechORoR3k5NSFSv8157bAEa8t4iwgWD2MSNSQnw', :RS384 => 'lT5JbytGKgG9QrwkJuxgw7UjmN9tjkEQW9pVGR2XnKEdC0_wLNIzAmT-jTwyMDGBLUkWO7opDOP6Xy6_DOTg58k9PwVkyQzrLnmxJMEng2Q-aMqcitRSIvUk3DPy8kemp8yUPls9NzWmByM2GoUVHbDsR0r-tZN-g_9QYev32mvMhjMr30JI5S2xiRjc9m2GAaXMOQmNTovJgV4bgCp4UjruCrA0BD1JJwDqKYoR_YYr_ALcVjD_LUgy80udJvbi8MAYJVUf0QYtQDrX2wnT_-eiiWjD5XafLuXEQVDRh-v2MKAwdvtXMq5cZ08Zjl2SyHxJ3OqhEeWPvYGltxZh_A', :RS512 => 'EHeGM2Mo3ghhUfSB99AlREehrbC6OPE-nYL_rwf88ysTnJ8L1QQ0UuCrXq4SpRutGLK_bYTK3ZALvFRPoOgK_g0QWmqv6qjQRU_QTxoq8y8APP-IgKKDuIiGH6daBV2rAPLDReqYNKsKjmTvZJo2c0a0e_WZkkj_ZwpgjTG3v0gW9lbDAzLJDz18eqtR4ZO7JTu_fyNrUrNk-w2_wpxSsn9sygIMp0lKE0_pt0b01fz3gjTDjlltU0cKSalUp4geaBDH7QRcexrolIctdQFbNKTXQxoigxD3NLNkKGH7f6A8KZdcOm8AnEjullcZs8_OWGnW43p1qrxoBRSivb9pqQ' } end let(:expected_signature_blank_payload) do { :HS256 => 'iRFMM3GknVfzRTxlVQT87jfIw32Ik3lUYNGePPk5wnM', :HS384 => 'rxyzr3I2RWRBgQaewQt3yjdp3BqkrFh-iHcet318OYHWhXvyzAE0npf0l0xi5DOV', :HS512 => 'VDHOrPYrwycjaKbwccObXi6dmw4fVFqiFsNFQjqYHQAkxJGxqhfVLc1_WfKMa6C7vGSGroabaVdK7nn08XPdSQ', :RS256 => 'WthQjouPVbErM7McwSY4slJjHaWqmFg1qKdmTDvttkiyAEcTjVViJkNHH9Mp573h13cXtLob1xh3UJYh5_-hSA4Y24zdyck3jp3fsOusflp1cMmhWXZ2nETKeWCEJDKRAnWynHqkwes7tgWmS0gVeuljeNkuovJlHmNRcoMR9Z3ZuiHfc2WFh-iFbM5Zne1y-_SSgAZwOD20P0Ysn28DtJTlXcm74ENqhLEJnvHS-872d6surb23kHMns43GtT5bm-aJoMLct0nO1GBapQAiKUknTsw24IfOkX4vJNQzIWVSzx3zOxXjcVHlH92af6NknIlPCfRparLC9YEK2NkJYg', :RS384 => 'Jy6XNLNAyujRHYoCOtFqu7z0imHZMiwkwBr73ok_DDSDxQSA9ryt_q_tX0u8knpAIRcTJuNA0-s5DkGbpIj9coKgZ5JBvE_n9ijvNubImf8_vCDDitJemzUtnJypb9GbP4A3nWDAZC0KONVqlxpy92-9xrG5sFEzaYCFYZYnXv8kmmQEIVI1GXw4_Fx8HxRu5cae9WWTgaKQOFG54S303C0H966C1o6d9o3HQH7x8GEl632qBw4LzONWr_QpCN-UFgmJHO7yBwaP-RWnLDW3hYlb4IybRIvMQQicjkjNaNwLTmwo31orVxO53GcSjyhU2y_R843nQcNjTT_lD1QRvg', :RS512 => 'ws2HZ6wvh8GMrFKiIHXDogyx8HFpa4wvrLxfZaMfCoMPf0SZ4V3tiEZRWfrxyvwpsdBj2Mgm5lt3IYAHhlI2hqWvuikDq6tuViloaAIm2xwTU060bF0GL1tQJ-h20wUukJ6fsWet8M9DNg7hcElYQMawHhk4L91YUtY2hKT_uWgPih_pn0Hq5Ve0at4CwAyXXTwCYSEH23PMsUdDfE5tfCyvL2bNQ71Ld_MvQS1NLS7hydzEtfxLK-UkDQVclFmEM3JXrPG7YSRodtKlwJ-ESDx6CaJXXDAgitSF32dslcIkmOXRJqjNmF15i_aVg0ExiU92WTpCrdwzWTt4Aphqlw', } end shared_examples_for :jwt_with_alg do it { should == jwt } its(:header) { should == jwt.header } end context 'before sign' do subject { jws } it_behaves_like :jwt_with_alg its(:signature) { should be_nil } end describe '#content_type' do it do jws.content_type.should == 'application/jose' end end describe 'decode' do let(:alg) { :RS256 } let(:private_key_or_secret) { private_key } let(:public_key_or_secret) { public_key } describe 'blank payload not allowed' do it 'should raise format error' do expect do JSON::JWT.decode signed_blank.to_s, public_key_or_secret end.to raise_error JSON::JWT::InvalidFormat end end describe 'blank payload allowed' do it 'should not raise an error' do expect do JSON::JWT.decode signed_blank.to_s, public_key_or_secret, nil, nil, true end.to_not raise_error end end end describe '#sign!' do shared_examples_for :generate_expected_signature do it do Base64.urlsafe_encode64(signed.signature, padding: false).should == expected_signature[alg] end context 'with blank payload' do it do Base64.urlsafe_encode64(signed_blank.signature, padding: false).should == expected_signature_blank_payload[alg] end end end subject { signed } [:HS256, :HS384, :HS512].each do |algorithm| describe algorithm do let(:alg) { algorithm } context 'when String key given' do let(:private_key_or_secret) { shared_secret } it_behaves_like :jwt_with_alg it_behaves_like :generate_expected_signature end context 'when JSON::JWK key given' do let(:private_key_or_secret) { JSON::JWK.new shared_secret } it_behaves_like :jwt_with_alg it_behaves_like :generate_expected_signature end end end [:RS256, :RS384, :RS512].each do |algorithm| describe algorithm do let(:alg) { algorithm } context 'when OpenSSL::PKey::RSA key given' do let(:private_key_or_secret) { private_key } it_behaves_like :jwt_with_alg it_behaves_like :generate_expected_signature end context 'when JSON::JWK key given' do let(:private_key_or_secret) { JSON::JWK.new private_key } it_behaves_like :jwt_with_alg it_behaves_like :generate_expected_signature end end end [:ES256, :ES384, :ES512, :ES256K].each do |algorithm| describe algorithm do let(:alg) { algorithm } shared_examples_for :self_verifiable do it 'should be self-verifiable' do expect do JSON::JWT.decode( JSON::JWT.new(claims).sign( private_key_or_secret, algorithm ).to_s, public_key_or_secret ) end.not_to raise_error end end context 'when OpenSSL::PKey::EC key given' do let(:private_key_or_secret) { private_key :ecdsa, digest_length: algorithm.to_s[2,3].to_i } let(:public_key_or_secret) { public_key :ecdsa, digest_length: algorithm.to_s[2,3].to_i } it_behaves_like :jwt_with_alg it_behaves_like :self_verifiable end context 'when JSON::JWK key given' do let(:private_key_or_secret) { JSON::JWK.new(private_key :ecdsa, digest_length: algorithm.to_s[2,3].to_i) } let(:public_key_or_secret) { JSON::JWK.new(public_key :ecdsa, digest_length: algorithm.to_s[2,3].to_i) } it_behaves_like :jwt_with_alg it_behaves_like :self_verifiable end end end context 'when JSON::JWK::Set key given' do let(:alg) { :HS256 } let(:kid) { 'kid' } let(:jwks) do jwk = JSON::JWK.new shared_secret, kid: kid JSON::JWK::Set.new jwk, JSON::JWK.new('another') end let(:signed) { jws.sign!(jwks) } context 'when jwk is found by given kid' do before { jws.kid = kid } it { should == jws.sign!('secret') } end context 'otherwise' do it do expect do subject end.to raise_error JSON::JWK::Set::KidNotFound end end end describe 'unknown algorithm' do let(:alg) { :unknown } it do expect do jws.sign! 'key' end.to raise_error JSON::JWS::UnexpectedAlgorithm end end end describe '#verify!' do shared_examples_for :success_signature_verification do it do expect { decoded }.not_to raise_error decoded.should be_a JSON::JWT end describe 'header' do let(:header) { decoded.header } it 'should be parsed successfully' do header[:typ].should == 'JWT' header[:alg].should == alg.to_s end end describe 'claims' do it 'should be parsed successfully' do decoded[:iss].should == 'joe' decoded[:exp].should == 1300819380 decoded[:'http://example.com/is_root'] == true end end context 'with blank payload' do it do expect { decoded_blank }.not_to raise_error decoded_blank.should be_a JSON::JWT end describe 'header' do let(:header) { decoded_blank.header } it 'should be parsed successfully' do header[:typ].should == 'JWT' header[:alg].should == alg.to_s end end describe 'claims' do it 'should be parsed successfully' do p decoded_blank.blank_payload decoded_blank.blank_payload.should == true decoded_blank[:iss].should == nil decoded_blank[:exp].should == nil decoded[:'http://example.com/is_root'] == nil end end end end subject { decoded } [:HS256, :HS384, :HS512].each do |algorithm| describe algorithm do let(:alg) { algorithm } let(:private_key_or_secret) { shared_secret } context 'when String key given' do let(:public_key_or_secret) { shared_secret } it_behaves_like :success_signature_verification end context 'when JSON::JWK key given' do let(:public_key_or_secret) { JSON::JWK.new shared_secret } it_behaves_like :success_signature_verification end end end [:RS256, :RS384, :RS512].each do |algorithm| describe algorithm do let(:alg) { algorithm } let(:private_key_or_secret) { private_key } context 'when OpenSSL::PKey::RSA key given' do let(:public_key_or_secret) { public_key } it_behaves_like :success_signature_verification end context 'when JSON::JWK key given' do let(:public_key_or_secret) { JSON::JWK.new public_key } it_behaves_like :success_signature_verification end end end [:ES256, :ES384, :ES512, :ES256K].each do |algorithm| describe algorithm do let(:alg) { algorithm } let(:private_key_or_secret) { private_key :ecdsa, digest_length: algorithm.to_s[2,3].to_i } context 'when OpenSSL::PKey::EC key given' do let(:public_key_or_secret) { public_key :ecdsa, digest_length: algorithm.to_s[2,3].to_i } it_behaves_like :success_signature_verification end context 'when JSON::JWK key given' do let(:public_key_or_secret) { JSON::JWK.new public_key(:ecdsa, digest_length: algorithm.to_s[2,3].to_i) } it_behaves_like :success_signature_verification end end end context 'when JSON::JWK::Set key given' do subject { JSON::JWT.decode signed.to_s, jwks } let(:alg) { :HS256 } let(:kid) { 'kid' } let(:jwks) do jwk = JSON::JWK.new shared_secret, kid: kid JSON::JWK::Set.new jwk, JSON::JWK.new('another') end let(:signed) { jws.sign!(jwks) } context 'when jwk is found by given kid' do before { jws.kid = kid } it { should == signed } end context 'otherwise' do it do expect do subject end.to raise_error JSON::JWK::Set::KidNotFound end end end describe 'unknown algorithm' do let(:alg) { :unknown } it do expect do jws.verify! 'key' end.to raise_error JSON::JWS::UnexpectedAlgorithm end end end describe '#to_json' do let(:alg) { :RS256 } let(:private_key_or_secret) { private_key } context 'as default' do it 'should JSONize payload' do jws.to_json.should == claims.to_json end end context 'with blank payload' do it 'should JSONize payload' do puts ("jws_blank: #{jws_blank.to_json.inspect}") jws_blank.to_json.should == '' end end context 'when syntax option given' do context 'when general' do it 'should return General JWS JSON Serialization' do signed.to_json(syntax: :general).should == { payload: Base64.urlsafe_encode64(claims.to_json, padding: false), signatures: [{ protected: Base64.urlsafe_encode64(signed.header.to_json, padding: false), signature: Base64.urlsafe_encode64(signed.signature, padding: false) }] }.to_json end context 'with blank payload' do it 'should return General JWS JSON Serialization' do signed_blank.to_json(syntax: :general).should == { payload: '', signatures: [{ protected: Base64.urlsafe_encode64(signed_blank.header.to_json, padding: false), signature: Base64.urlsafe_encode64(signed_blank.signature, padding: false) }] }.to_json end end context 'when not signed yet' do it 'should not fail' do jws.to_json(syntax: :general).should == { payload: Base64.urlsafe_encode64(claims.to_json, padding: false), signatures: [{ protected: Base64.urlsafe_encode64(jws.header.to_json, padding: false), signature: Base64.urlsafe_encode64('', padding: false) }] }.to_json end context 'with blank payload' do it 'should not fail' do jws_blank.to_json(syntax: :general).should == { payload: '', signatures: [{ protected: Base64.urlsafe_encode64(jws_blank.header.to_json, padding: false), signature: Base64.urlsafe_encode64('', padding: false) }] }.to_json end end end end context 'when flattened' do it 'should return Flattened JWS JSON Serialization' do signed.to_json(syntax: :flattened).should == { protected: Base64.urlsafe_encode64(signed.header.to_json, padding: false), payload: Base64.urlsafe_encode64(claims.to_json, padding: false), signature: Base64.urlsafe_encode64(signed.signature, padding: false) }.to_json end context 'with blank payload' do it 'should return Flattened JWS JSON Serialization' do signed_blank.to_json(syntax: :flattened).should == { protected: Base64.urlsafe_encode64(signed_blank.header.to_json, padding: false), payload: '', signature: Base64.urlsafe_encode64(signed_blank.signature, padding: false) }.to_json end end context 'when not signed yet' do it 'should not fail' do jws.to_json(syntax: :flattened).should == { protected: Base64.urlsafe_encode64(jws.header.to_json, padding: false), payload: Base64.urlsafe_encode64(claims.to_json, padding: false), signature: Base64.urlsafe_encode64('', padding: false) }.to_json end context 'with blank payload' do it 'should not fail' do jws_blank.to_json(syntax: :flattened).should == { protected: Base64.urlsafe_encode64(jws_blank.header.to_json, padding: false), payload: '', signature: Base64.urlsafe_encode64('', padding: false) }.to_json end end end end end end end json-jwt-1.13.0/spec/json/jwt_spec.rb000066400000000000000000000364521366504062400174350ustar00rootroot00000000000000require 'spec_helper' describe JSON::JWT do let(:jwt) { JSON::JWT.new claims } let(:jws) do jwt.alg = :HS256 jws = JSON::JWS.new jwt jws.signature = 'signature' jws end let(:claims) do { iss: 'joe', exp: 1300819380, 'http://example.com/is_root' => true }.with_indifferent_access end let(:no_signed) do 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.' end context 'when not signed nor encrypted' do it do jwt.to_s.should == no_signed end end describe '#content_type' do it do jwt.content_type.should == 'application/jwt' end end describe '#sign' do [:HS256, :HS384, :HS512].each do |algorithm| context algorithm do it do jwt.sign(shared_secret, algorithm).should be_a JSON::JWS end end end [:RS256, :RS384, :RS512].each do |algorithm| context algorithm do it do jwt.sign(private_key, algorithm).should be_a JSON::JWS end end end context 'when no algirithm specified' do subject { jwt.sign(key) } context 'when key is String' do let(:key) { shared_secret } its(:alg) { should == :HS256 } end context 'when key is RSA key' do let(:key) { private_key } its(:alg) { should == :RS256 } end context 'when key is EC key' do context 'when prime256v1' do let(:key) { private_key(:ecdsa) } its(:alg) { should == :ES256 } end context 'when secp384r1' do let(:key) { private_key(:ecdsa, digest_length: 384) } its(:alg) { should == :ES384 } end context 'when secp521r1' do let(:key) { private_key(:ecdsa, digest_length: 512) } its(:alg) { should == :ES512 } end context 'when secp256k1' do let(:key) { private_key(:ecdsa, curve_name: :secp256k1) } its(:alg) { should == :ES256K } end end context 'when key is JWK with kty=okt' do let(:key) { JSON::JWK.new shared_secret } its(:alg) { should == :HS256 } end context 'when key is JWK with kty=RSA' do let(:key) { JSON::JWK.new private_key } its(:alg) { should == :RS256 } end context 'when key is JWK with kty=EC' do context 'when prime256v1' do let(:key) { JSON::JWK.new private_key(:ecdsa) } its(:alg) { should == :ES256 } end context 'when secp384r1' do let(:key) { JSON::JWK.new private_key(:ecdsa, digest_length: 384) } its(:alg) { should == :ES384 } end context 'when secp521r1' do let(:key) { JSON::JWK.new private_key(:ecdsa, digest_length: 512) } its(:alg) { should == :ES512 } end context 'when secp256k1' do let(:key) { JSON::JWK.new private_key(:ecdsa, curve_name: :secp256k1) } its(:alg) { should == :ES256K } end end end context 'when non-JWK key is given' do let(:key) { private_key } it 'should not set kid header automatically' do jws = jwt.sign(key, :RS256) jws.kid.should be_blank end end context 'when JWK is given' do let(:key) { JSON::JWK.new private_key } it 'should set kid header automatically' do jws = jwt.sign(key, :RS256) jwt.kid.should be_blank jws.kid.should == key[:kid] end end describe 'object copy behaviour' do before do @jwt = JSON::JWT.new(obj: {foo: :bar}) @jws = @jwt.sign('secret') end context 'when original JWT is modified' do before do @jwt.header[:x] = :x @jwt[:obj][:x] = :x end describe 'copied JWS' do it 'should be affected as shallow copy, but not as a simple reference' do @jws.header.should_not include :x @jws[:obj].should include :x end end end context 'when copied JWS is modified' do before do @jws.header[:x] = :x @jws[:obj][:x] = :x end describe 'original JWT' do it 'should be affected as shallow copy, but not as a simple reference' do @jwt.header.should_not include :x @jwt[:obj].should include :x end end end end end describe '#encrypt' do let(:shared_key) { SecureRandom.hex 16 } # default shared key is too short it 'should encryptable without signing' do jwt.encrypt(public_key).should be_a JSON::JWE end it 'should encryptable after signed' do jwt.sign(shared_key).encrypt(public_key).should be_a JSON::JWE end it 'should accept optional algorithm' do jwt.encrypt(shared_key, :dir).should be_a JSON::JWE end it 'should accept optional algorithm and encryption method' do jwt.encrypt(SecureRandom.hex(32), :dir, :'A256CBC-HS512').should be_a JSON::JWE end context 'when non-JWK key is given' do let(:key) { shared_key } it 'should not set kid header automatically' do jwe = jwt.encrypt(key, :dir) jwe.kid.should be_blank end end context 'when JWK is given' do let(:key) { JSON::JWK.new shared_key } it 'should set kid header automatically' do jwe = jwt.encrypt(key, :dir) jwt.kid.should be_blank jwe.kid.should == key[:kid] end end end describe '.decode' do context 'when not signed nor encrypted' do context 'no signature given' do it do JSON::JWT.decode(no_signed).should == jwt end end end context 'when signed' do context 'when no secret/key given' do it 'should do verification' do expect do JSON::JWT.decode jws.to_s end.to raise_error JSON::JWT::VerificationFailed end end context 'when secret/key given' do it 'should do verification' do expect do JSON::JWT.decode jws.to_s, 'secret' end.to raise_error JSON::JWT::VerificationFailed end end context 'when alg header malformed' do context 'from alg=HS256' do context 'to alg=none' do let(:malformed_jwt_string) do header, payload, signature = jws.to_s.split('.') malformed_header = {alg: :none}.to_json [ Base64.urlsafe_encode64(malformed_header, padding: false), payload, '' ].join('.') end it do expect do JSON::JWT.decode malformed_jwt_string, 'secret' end.to raise_error JSON::JWT::VerificationFailed end end end context 'from alg=RS256' do let(:jws) do jwt.sign private_key, :RS256 end context 'to alg=none' do let(:malformed_jwt_string) do header, payload, signature = jws.to_s.split('.') malformed_header = {alg: :none}.to_json [ Base64.urlsafe_encode64(malformed_header, padding: false), payload, '' ].join('.') end it do expect do JSON::JWT.decode malformed_jwt_string, public_key end.to raise_error JSON::JWT::UnexpectedAlgorithm end end context 'to alg=HS256' do let(:malformed_jwt_string) do header, payload, signature = jws.to_s.split('.') malformed_header = {alg: :HS256}.to_json malformed_signature = OpenSSL::HMAC.digest( OpenSSL::Digest.new('SHA256'), public_key.to_s, [Base64.urlsafe_encode64(malformed_header, padding: false), payload].join('.') ) [ Base64.urlsafe_encode64(malformed_header, padding: false), payload, Base64.urlsafe_encode64(malformed_signature, padding: false) ].join('.') end it do expect do JSON::JWT.decode malformed_jwt_string, public_key end.to raise_error JSON::JWS::UnexpectedAlgorithm end end end context 'from alg=PS512' do let(:jws) do jwt.sign private_key, :PS512 end context 'to alg=PS256' do let(:malformed_jwt_string) do header, payload, signature = jws.to_s.split('.') malformed_header = {alg: :PS256}.to_json digest = OpenSSL::Digest.new('SHA256') malformed_signature = private_key.sign_pss( digest, [Base64.urlsafe_encode64(malformed_header, padding: false), payload].join('.'), salt_length: :digest, mgf1_hash: digest ) [ Base64.urlsafe_encode64(malformed_header, padding: false), payload, Base64.urlsafe_encode64(malformed_signature, padding: false) ].join('.') end context 'when verification algorithm is specified' do it do expect do JSON::JWT.decode malformed_jwt_string, public_key, :PS512 end.to raise_error JSON::JWS::UnexpectedAlgorithm, 'Unexpected alg header' end end context 'otherwise' do it do expect do JSON::JWT.decode malformed_jwt_string, public_key end.not_to raise_error end end end context 'to alg=RS516' do let(:malformed_jwt_string) do header, payload, signature = jws.to_s.split('.') malformed_header = {alg: :RS512}.to_json malformed_signature = private_key.sign( OpenSSL::Digest.new('SHA512'), [Base64.urlsafe_encode64(malformed_header, padding: false), payload].join('.') ) [ Base64.urlsafe_encode64(malformed_header, padding: false), payload, Base64.urlsafe_encode64(malformed_signature, padding: false) ].join('.') end context 'when verification algorithm is specified' do it do expect do JSON::JWT.decode malformed_jwt_string, public_key, :PS512 end.to raise_error JSON::JWS::UnexpectedAlgorithm, 'Unexpected alg header' end end context 'otherwise' do it do expect do JSON::JWT.decode malformed_jwt_string, public_key end.not_to raise_error end end end end end context 'when :skip_verification given as secret/key' do it 'should skip verification' do expect do jwt = JSON::JWT.decode jws.to_s, :skip_verification jwt.header.should == {'alg' => 'HS256', 'typ' => 'JWT'} end.not_to raise_error end end context 'when JSON Serialization given' do let(:signed) { JSON::JWT.new(claims).sign('secret') } shared_examples_for :json_serialization_parser do context 'when proper secret given' do it { JSON::JWT.decode(serialized, 'secret').should == signed } end context 'when verification skipped' do it { JSON::JWT.decode(serialized, :skip_verification).should == signed } end context 'when wrong secret given' do it do expect do JSON::JWT.decode serialized, 'wrong' end.to raise_error JSON::JWT::VerificationFailed end end end context 'when general' do let(:serialized) do { payload: Base64.urlsafe_encode64(claims.to_json, padding: false), signatures: [{ protected: Base64.urlsafe_encode64(signed.header.to_json, padding: false), signature: Base64.urlsafe_encode64(signed.signature, padding: false) }] } end it_behaves_like :json_serialization_parser end context 'when flattened' do let(:serialized) do { protected: Base64.urlsafe_encode64(signed.header.to_json, padding: false), payload: Base64.urlsafe_encode64(claims.to_json, padding: false), signature: Base64.urlsafe_encode64(signed.signature, padding: false) } end it_behaves_like :json_serialization_parser end end end context 'when encrypted' do let(:input) { jwt.encrypt(public_key).to_s } let(:shared_key) { SecureRandom.hex 16 } # default shared key is too short it 'should decryptable' do JSON::JWT.decode(input, private_key).should be_instance_of JSON::JWE end context 'when :skip_decryption given as secret/key' do it 'should skip verification' do expect do jwe = JSON::JWT.decode input, :skip_decryption jwe.should be_instance_of JSON::JWE jwe.header.should == {'alg' => 'RSA1_5', 'enc' => 'A128CBC-HS256'} end.not_to raise_error end end context 'when alg & enc is specified' do context 'when expected' do it do expect do JSON::JWT.decode(input, private_key, 'RSA1_5', 'A128CBC-HS256') end.not_to raise_error end end context 'when alg is unexpected' do it do expect do JSON::JWT.decode(input, private_key, 'dir', 'A128CBC-HS256') end.to raise_error JSON::JWE::UnexpectedAlgorithm, 'Unexpected alg header' end end context 'when enc is unexpected' do it do expect do JSON::JWT.decode(input, private_key, 'RSA1_5', 'A128GCM') end.to raise_error JSON::JWE::UnexpectedAlgorithm, 'Unexpected enc header' end end end end context 'when JSON parse failed' do it do expect do JSON::JWT.decode('header.payload.signature') end.to raise_error JSON::JWT::InvalidFormat end end context 'when unexpected format' do context 'when too few dots' do it do expect do JSON::JWT.decode 'header' end.to raise_error JSON::JWT::InvalidFormat end end context 'when too many dots' do it do expect do JSON::JWT.decode 'header.payload.signature.too.many.dots' end.to raise_error JSON::JWT::InvalidFormat end end end end describe '.pretty_generate' do subject { JSON::JWT.pretty_generate jws.to_s } its(:size) { should == 2 } its(:first) do should == <<~HEADER.chop { "typ": "JWT", "alg": "HS256" } HEADER end its(:last) do should == <<~HEADER.chop { "iss": "joe", "exp": 1300819380, "http://example.com/is_root": true } HEADER end end end json-jwt-1.13.0/spec/spec_helper.rb000066400000000000000000000004641366504062400171310ustar00rootroot00000000000000require 'simplecov' SimpleCov.start do add_filter 'spec' end require 'rspec' require 'rspec/its' require 'json/jwt' RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] end end require 'helpers/sign_key_fixture_helper' require 'helpers/nimbus_spec_helper'