pax_global_header00006660000000000000000000000064142622452720014520gustar00rootroot0000000000000052 comment=2876fa5961bfb6b6160bac13ed7e4ed742123923 json-jwt-1.14.0/000077500000000000000000000000001426224527200133565ustar00rootroot00000000000000json-jwt-1.14.0/.github/000077500000000000000000000000001426224527200147165ustar00rootroot00000000000000json-jwt-1.14.0/.github/FUNDING.yml000066400000000000000000000000731426224527200165330ustar00rootroot00000000000000# These are supported funding model platforms github: nov json-jwt-1.14.0/.gitignore000066400000000000000000000002471426224527200153510ustar00rootroot00000000000000## MAC OS .DS_Store ## TEXTMATE *.tmproj tmtags ## EMACS *~ \#* .\#* ## VIM *.swp ## PROJECT::GENERAL coverage* rdoc pkg Gemfile.lock ## PROJECT::SPECIFIC .class json-jwt-1.14.0/.gitmodules000066400000000000000000000002021426224527200155250ustar00rootroot00000000000000[submodule "spec/helpers/json-jwt-nimbus"] path = spec/helpers/json-jwt-nimbus url = https://github.com/nov/json-jwt-nimbus.git json-jwt-1.14.0/.rspec000066400000000000000000000000371426224527200144730ustar00rootroot00000000000000--color --format=documentation json-jwt-1.14.0/.travis.yml000066400000000000000000000002261426224527200154670ustar00rootroot00000000000000before_install: - gem install bundler - git submodule update --init --recursive rvm: - 2.5.8 - 2.6.6 - 2.7.2 - 3.0.2 jdk: - openjdk11 json-jwt-1.14.0/Gemfile000066400000000000000000000000451426224527200146500ustar00rootroot00000000000000source "http://rubygems.org" gemspecjson-jwt-1.14.0/LICENSE000066400000000000000000000020361426224527200143640ustar00rootroot00000000000000Copyright (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.14.0/README.md000066400000000000000000000032251426224527200146370ustar00rootroot00000000000000# 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.14.0/Rakefile000066400000000000000000000006171426224527200150270ustar00rootroot00000000000000require '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.14.0/VERSION000066400000000000000000000000061426224527200144220ustar00rootroot000000000000001.14.0json-jwt-1.14.0/json-jwt.gemspec000066400000000000000000000020641426224527200165000ustar00rootroot00000000000000Gem::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.14.0/lib/000077500000000000000000000000001426224527200141245ustar00rootroot00000000000000json-jwt-1.14.0/lib/json/000077500000000000000000000000001426224527200150755ustar00rootroot00000000000000json-jwt-1.14.0/lib/json/jose.rb000066400000000000000000000036701426224527200163700ustar00rootroot00000000000000require '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.14.0/lib/json/jwe.rb000066400000000000000000000224161426224527200162140ustar00rootroot00000000000000require '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.14.0/lib/json/jwk.rb000066400000000000000000000101611426224527200162140ustar00rootroot00000000000000module 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 # Public key data_sequence = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(n), OpenSSL::ASN1::Integer(e), ]) if d && p && q && dp && dq && qi data_sequence = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Integer(n), OpenSSL::ASN1::Integer(e), OpenSSL::ASN1::Integer(d), OpenSSL::ASN1::Integer(p), OpenSSL::ASN1::Integer(q), OpenSSL::ASN1::Integer(dp), OpenSSL::ASN1::Integer(dq), OpenSSL::ASN1::Integer(qi), ]) end asn1 = OpenSSL::ASN1::Sequence(data_sequence) OpenSSL::PKey::RSA.new(asn1.to_der) 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 point = 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) ) # Public key data_sequence = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::ObjectId("id-ecPublicKey"), OpenSSL::ASN1::ObjectId(curve_name) ]), OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed)) ]) if d # Private key data_sequence = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(1), OpenSSL::ASN1::OctetString(OpenSSL::BN.new(d, 2).to_s(2)), OpenSSL::ASN1::ObjectId(curve_name, 0, :EXPLICIT), OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT) ]) end OpenSSL::PKey::EC.new(data_sequence.to_der) end end end json-jwt-1.14.0/lib/json/jwk/000077500000000000000000000000001426224527200156705ustar00rootroot00000000000000json-jwt-1.14.0/lib/json/jwk/jwkizable.rb000066400000000000000000000043451426224527200202050ustar00rootroot00000000000000module 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.14.0/lib/json/jwk/set.rb000066400000000000000000000011621426224527200170100ustar00rootroot00000000000000module 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.14.0/lib/json/jws.rb000066400000000000000000000154111426224527200162270ustar00rootroot00000000000000module 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 newkey = OpenSSL::PKey::EC.generate(group_name.to_s) newkey.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.14.0/lib/json/jwt.rb000066400000000000000000000076261426224527200162410ustar00rootroot00000000000000require '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.14.0/spec/000077500000000000000000000000001426224527200143105ustar00rootroot00000000000000json-jwt-1.14.0/spec/fixtures/000077500000000000000000000000001426224527200161615ustar00rootroot00000000000000json-jwt-1.14.0/spec/fixtures/ecdsa/000077500000000000000000000000001426224527200172405ustar00rootroot00000000000000json-jwt-1.14.0/spec/fixtures/ecdsa/256/000077500000000000000000000000001426224527200175545ustar00rootroot00000000000000json-jwt-1.14.0/spec/fixtures/ecdsa/256/prime256v1/000077500000000000000000000000001426224527200213745ustar00rootroot00000000000000json-jwt-1.14.0/spec/fixtures/ecdsa/256/prime256v1/private_key.pem000066400000000000000000000003421426224527200244200ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHcCAQEEIHo5LvIgMVpOlEKjjZiE5n+xYtTxLm4Eumx7FRMgICyDoAoGCCqGSM49 AwEHoUQDQgAEsaPyrO4Lh9kh2FxrF9y1QVmZznWnRRJwpr12UHqzrVYwzPhb3POq WsmGqv4nKum+WdogjJlAToN+uA+TEwDDUw== -----END EC PRIVATE KEY-----json-jwt-1.14.0/spec/fixtures/ecdsa/256/prime256v1/public_key.pem000066400000000000000000000002611426224527200242240ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsaPyrO4Lh9kh2FxrF9y1QVmZznWn RRJwpr12UHqzrVYwzPhb3POqWsmGqv4nKum+WdogjJlAToN+uA+TEwDDUw== -----END PUBLIC KEY-----json-jwt-1.14.0/spec/fixtures/ecdsa/256/secp256k1/000077500000000000000000000000001426224527200211775ustar00rootroot00000000000000json-jwt-1.14.0/spec/fixtures/ecdsa/256/secp256k1/private_key.pem000066400000000000000000000003361426224527200242260ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHQCAQEEINMWuYrnHN2TF03tNQYYssscDLNnDFoEdL9cuR7Ld7vfoAcGBSuBBAAK oUQDQgAEYMHAtmtrOmrZMPdy550VQ9xNgfO+1jvh2cc++uf4LITkVZ9B8vrpLsKg RARj3hLH1WyllCupbEl0sKQn8EwMDA== -----END EC PRIVATE KEY-----json-jwt-1.14.0/spec/fixtures/ecdsa/256/secp256k1/public_key.pem000066400000000000000000000002551426224527200240320ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEYMHAtmtrOmrZMPdy550VQ9xNgfO+1jvh 2cc++uf4LITkVZ9B8vrpLsKgRARj3hLH1WyllCupbEl0sKQn8EwMDA== -----END PUBLIC KEY-----json-jwt-1.14.0/spec/fixtures/ecdsa/384/000077500000000000000000000000001426224527200175565ustar00rootroot00000000000000json-jwt-1.14.0/spec/fixtures/ecdsa/384/private_key.pem000066400000000000000000000004401426224527200226010ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIGkAgEBBDB1NRLzYeQa7oRUwWrnQFZOBVqzlyJ9n654/PFjCLJh/A/uGWeECoM2 1hXEvp80pqGgBwYFK4EEACKhZANiAASmXMCnIWcrurOGDlechlsWPaFmgfZV2Xj5 EWbsOew0wb23Kqul+rZHKN8oAFtwVG2LEHN9+GTd9xuZ6KkYuS9AE0LN42bpAveE 5RMfogUHM4vRjsewZOik1NOykuOWK9s= -----END EC PRIVATE KEY----- json-jwt-1.14.0/spec/fixtures/ecdsa/384/public_key.pem000066400000000000000000000003261426224527200224100ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEplzApyFnK7qzhg5XnIZbFj2hZoH2Vdl4 +RFm7DnsNMG9tyqrpfq2RyjfKABbcFRtixBzffhk3fcbmeipGLkvQBNCzeNm6QL3 hOUTH6IFBzOL0Y7HsGTopNTTspLjlivb -----END PUBLIC KEY-----json-jwt-1.14.0/spec/fixtures/ecdsa/512/000077500000000000000000000000001426224527200175475ustar00rootroot00000000000000json-jwt-1.14.0/spec/fixtures/ecdsa/512/private_key.pem000066400000000000000000000005541426224527200226000ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIBBpwKqvGEZGpE3wX1fDzJjrrM4uXr16WKsijjqjRP8tHdnvr5p2fO zrPVyDVbiQDulOhSh9aouunuwmbudKjWvZagBwYFK4EEACOhgYkDgYYABAHDAg/m tGuq5xPU7wtJjqhfwxl0YOWN4k2+HhzcE5tpA+oro8fTP3/HfxRh69DoaasxJ+K2 D2GaLhrGyDxIC9Kv/wFC2BHfJfm1fwSNvPWns4Ui2dUQxdpbYAzxMvWO2LamGuHC XKYss1QzKV1sAaenI4Ok1yDZKFa1V2YTeNOIobuCNg== -----END EC PRIVATE KEY-----json-jwt-1.14.0/spec/fixtures/ecdsa/512/public_key.pem000066400000000000000000000004131426224527200223760ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBwwIP5rRrqucT1O8LSY6oX8MZdGDl jeJNvh4c3BObaQPqK6PH0z9/x38UYevQ6GmrMSfitg9hmi4axsg8SAvSr/8BQtgR 3yX5tX8Ejbz1p7OFItnVEMXaW2AM8TL1jti2phrhwlymLLNUMyldbAGnpyODpNcg 2ShWtVdmE3jTiKG7gjY= -----END PUBLIC KEY-----json-jwt-1.14.0/spec/fixtures/rsa/000077500000000000000000000000001426224527200167465ustar00rootroot00000000000000json-jwt-1.14.0/spec/fixtures/rsa/private_key.der000066400000000000000000000023021426224527200217610ustar00rootroot000000000000000‚¾0  *†H†÷ ‚¨0‚¤‚ÇÛÍ…ËÒ¯ìz@Žê9_èăDyiÞ%‡3KÍ?m IêT’'ÍjyÌW0‹>[ìƒøyå ]+¸Oí ã¿MúF?36℞KElG2ÛA3ªÉƒi{Œ·)íÛ½Þô¯ÅNšŽi¨ÍÈ<±°DÑM ²FÀи¥ƒ¥­Ä5 Ëš%Ù$ÇÖ ‘z[rOh¼sV]'¸â–)¦‡–žyZ!8‹TfîH'!-Å@fèSLÖƒ.ÒÈ'¡´ÈÒo|Hž%H@ó øCܳÁ1LÇ«¦6a?ú£›¡ yjJ蕊_:üô²ÿ=ÃN¿¬=,}sí•‚K|PŽõaøòoöÒ¿€w‰IÖ›{jB;>ogG}4¦-AÍyzs8c°÷·ÏðÆ%æè…™¦Ç†ó6TãámSqLÓš*J.Ã}]cg,å5& ²4ô?ˆ£8ÁÏn ³Ö5AO£þ ºÉ£jõr±øçí¦Ý’sD¬w!Hå qÅù’!7àÔLÞ ·Ñx4rö 4\`4Ûm×±óv¹wRq.¸õÄÛ´MgÅ=Ö ÛU©¾§m£_ĸšf‹Úæ½M¿ò®Õ¾Õùö”Ä3*âW8x%½™´…)®uðÍ8àcʳTà0t€Æ“šBùéj¿e­òï¿(%Ô² T׌عª‘U«-B’˜é èmKm>ÌO=•O¤ñ¼zõŒòÀù^L5Þ·ù¾ÀOKÒILwe¯ŠnŠ»»¿;E€ÐM‰¼äíÝc™7ƒçlÙq› »ãÝþt7êݬ(ü/øMß{EP8ô˜`¦€J˜>Ò› à 1]Æ&ǯ›¥1Lš¤yèzºÍë­¸MRzóÓÿ ý.?¸†ä6sàÁÚÈSÞ G?ó6ì»ï£|fÐO_e­×0¹kU});˜Öqv¸ ý–­&öšp÷u¹é‰2‹®1 §V3§Ó¨ÿÖR¾A“hw8n=ÿ£›‘%UáÞØ" çVÚÇ8%B>\ ïGcg µw°õCÌ%]¢¹¶à!Þ.ÈÎñ«R¨Íönjۿ"¡v°ª¾™”íuÎ<š@ÛÞò´¼7Ä/©ËÒT»’K1ÃïpëCjâ_~žøy'—ùèräfB€§±þ*DhÅRÊfOPOªŽæïYÇ­û—_õYøÞ4Ÿ•¯sóXWÌ3oæmÚˆ¿GÔ„ ƱÇUh*¡Q;å(ÿ ­ÜÌ0C4ê¼5òCùÅöòÇ$p(Ï蛫ðJPøÉö%jó/‰ ˜õŸÒœ‚È~ìÌ:™U„|*ÓAÞ«²ïŸo(úöÿf·’ĆàóÛSV,¨ÎK€2É0)«ƒÃ‚*þ?à3ƒ/;¼U’êë0 qİæÞ\ÅÓÝ {¢ø'\ÎË8iµÍ5ÅUìíð£lµNS~©ÁĺûŠëG•ÀSÚ©¢c+åúÑü×lW?‚¶‘Û+†Ö5&Ú¤?ç=®~­j«¹mn7§<‚Lûÿ¯£json-jwt-1.14.0/spec/fixtures/rsa/private_key.pem000066400000000000000000000033451426224527200220000ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,A6B5D10FFA23D7BC66CE4FB46B754E8C gvYFJmzetWSwpf1Ut6USm4IReFLA6bpp5nVUfU2Xa9dJmrCu0cTupTkdzNmGp14/ mzeT/a5WplK1hWV41Y7bLm57hOpaoxbEx2h3IW9cxuqgAp422AbafRu6hEvjUONO qUEm63I4d7/FKo0he4IVVx0UHaX5eEq8pdY1AZR9+fqlxWvjpgZlB0anT3AMpfVL CY4sGc/Es14M9A4zUpOkp+09PbV3WNG8A4G4IJA3wx6tYBxrd2866Te2ccP6/BL8 HA2uoqeD+70ziybvd//CtGAD1xoTDvOCwzXXT5A4fCuec3apFp8O6WyJ63ao99R4 E0r6AvD4iOkLMC8K2YUSc1zFLJAkhlfhBb8qFbXOwOomazWf2Z8rDtkP1VCfB9gC W6oYRBOyVUkbaBSBOOPBJtxjuTL/cJBvFhn6r0zdC67g0CD4cabMPd1VY92lUVbl JvE9CouDB5O39jwfpgfxo3kGev2UzwRze9U2uw6EdYIUB/ixyC8/5BoN8lKZXdTn 7vQBmcBsC3boMqQiq2c/dVGW7yrt/Y4q8wq9aGFwdBAY1pYhRlOZ0MwFmlAJKOzo Y3i2OILtPM0sdfqIeAvrwicQULGAzLGEhecDOQ1r0GwZRy5/Sl3ILxKMW8ngdaK4 iNDM56u6F9dwqqArrZDZgkfwnKakXU2ZbltSOWTCleCfYrc1D75Yw3FHKcWozB/l uyMTIqzBXzO6OiNDUv7zAE1xzVaV+VeBH/5KGx18dAuRP3TnpYltGSUVDyGk9FX7 m7nhvkqOcFJMHOGdTjt2Ff9Ibn3rgCEFI0CjCwcJPo2ym1Zox8GfP0/nXk3p/oWV ebBTvS+yc9HaSm2Al77GDXEwY7NDcTef+3kEtQHpesS+xmC8mFtMEKV8vifXEhpt iNp6gtsfDMtFmrFXCADTaUD1WBmhJQtYUsGsoQ75c5zzUHU93BOswBe2KODCA3Ie X3XKvHTjGw3wjWJvLcnL3EgcHDL1x7ehIA5UgvHFixmkx5D91uIdq4YDMt6xERle JgeLKF9g1qCl9wmxqT4T1J9h9LGMQQEUhth0uTFr6bEpzgg6hFTrPDaI8dOMlcgu ctOH2wFZgRMfrykGIs53rwF4pL+8iy+pakaRRAVTaZIX2CL2dThy0283jTVu9dNn f/Zfu5mNlvsRcXlZMgTy0UMBpXeLMjaWyd+JeAnAeZxO3/ID/Ppg6vcc0Wn8XyB5 YUOIQe+Rc0jJnB1zqjww2cB6H5Ke3GjktQqrulHFfEYbn7lj8WLVgDLmellFNOxX k9H2sta5SN/t3fN3oBO0A6JyotQOiBE54yHVpgoc+PBlcrpoOxSZx67lprD+WnlV Ynnf6qIrR9NxgYN00Elzj9KP9OT1ufFrMQ0BnW4EWdFLCScGLsZVlmBKPrtHwK3N FSy3JFMJcpymgNas5+bqIyZGqAZREHH3AWhc2TVl1Kt11g6PZWm3dSafT6SlqgyP Z7OcBEnWr+ZhDeJfnAXrn5siah9eXuT0KtQWlqpSn76dExlfz16Da/3xBtO4ceyz Lk4gzZ1QjP1ZvjarWOIEtkT7eiWaCQHYNVbvFRu5wo98o/KwO3xaPTDN9LCZKGhR -----END RSA PRIVATE KEY-----json-jwt-1.14.0/spec/fixtures/rsa/public_key.pem000066400000000000000000000006511426224527200216010ustar00rootroot00000000000000-----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEAx9vNhcvSrxjsegZAAo4OEuoZOV/oxINEeWneJYczS80/bQ1J6lSS J81qecxXAzCLPlvsFoP4eeUNXSt/G7hP7SAM479N+kY/MzbihJ5LRY9sRzLbQTMe qsmDAmmQe4y3Ke3bvd70r8VOmo5pqM3IPLGwBkTRTQmyRsDQArilg6WtxDUgy5ol 2STHFA8E1iCReh9bck8ZaLxzVhYRXZ0nuOKWGRMppocPlp55HVohOItUZh7uSCch LcVAZuhTTNaDLtLIJ6G0yNJvfEieJUhA8wGBoPhD3LMQwQMxTMerpjZhP/qjm6Gg eWpKf+iVil86/PSy/z0Vw06/rD0sfXPtlQIDAQAB -----END RSA PUBLIC KEY-----json-jwt-1.14.0/spec/helpers/000077500000000000000000000000001426224527200157525ustar00rootroot00000000000000json-jwt-1.14.0/spec/helpers/json-jwt-nimbus/000077500000000000000000000000001426224527200210205ustar00rootroot00000000000000json-jwt-1.14.0/spec/helpers/nimbus_spec_helper.rb000066400000000000000000000005451426224527200221510ustar00rootroot00000000000000module NimbusSpecHelper module_function def setup nimbus_path = File.expand_path( File.join( File.dirname(__FILE__), 'json-jwt-nimbus', 'nimbus_jwe.rb' ) ) if File.exist? nimbus_path require nimbus_path end end def nimbus_available? defined? NimbusJWE end end NimbusSpecHelper.setupjson-jwt-1.14.0/spec/helpers/sign_key_fixture_helper.rb000066400000000000000000000024741426224527200232230ustar00rootroot00000000000000module SignKeyFixtureHelper def shared_secret 'shared-secret' end def pem_file(file_name) File.new pem_file_path(file_name) end def pem_file_path(file_name) File.join( File.dirname(__FILE__), "../fixtures/#{file_name}.pem" ) end def der_file_path(file_name) File.join( File.dirname(__FILE__), "../fixtures/#{file_name}.der" ) end def private_key(algorithm = :rsa, digest_length: 256, curve_name: nil) case algorithm when :rsa OpenSSL::PKey::RSA.new( pem_file("#{algorithm}/private_key"), 'pass-phrase' ) when :ecdsa OpenSSL::PKey::EC.new( pem_file( File.join([ algorithm, digest_length, curve_name, 'private_key', ].compact.collect(&:to_s)) ) ) end end def public_key(algorithm = :rsa, digest_length: 256, curve_name: nil) case algorithm when :rsa OpenSSL::PKey::RSA.new( pem_file("#{algorithm}/public_key") ) when :ecdsa OpenSSL::PKey::EC.new( pem_file( File.join([ algorithm, digest_length, curve_name, 'public_key', ].compact.collect(&:to_s)) ) ) end end end include SignKeyFixtureHelperjson-jwt-1.14.0/spec/interop/000077500000000000000000000000001426224527200157705ustar00rootroot00000000000000json-jwt-1.14.0/spec/interop/with_jsrsasign_spec.rb000066400000000000000000000032741426224527200223730ustar00rootroot00000000000000require 'spec_helper' describe 'interop' do describe 'with jsrsasign' do context 'JWS' do let(:public_key) do pem = <<~PEM -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoBUyo8CQAFPeYPvv78ylh5MwFZjT CLQeb042TjiMJxG+9DLFmRSMlBQ9T/RsLLc+PmpB1+7yPAR+oR5gZn3kJQ== -----END PUBLIC KEY----- PEM OpenSSL::PKey::EC.new pem end let(:private_key) do pem = <<~PEM -----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEbVzfPnZPxfAyxqE ZV05laAoJAl+/6Xt2O4mOB611sOhRANCAASgFTKjwJAAU95g++/vzKWHkzAVmNMI tB5vTjZOOIwnEb70MsWZFIyUFD1P9Gwstz4+akHX7vI8BH6hHmBmfeQl -----END PRIVATE KEY----- PEM OpenSSL::PKey::EC.new pem end let(:jws_string) do 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2p3dC1pZHAuZXhhbXBsZS5jb20iLCJzdWIiOiJtYWlsdG86bWlrZUBleGFtcGxlLmNvbSIsIm5iZiI6MTQzNTA2MjUyMywiZXhwIjoxNDM1MDY2MTIzLCJpYXQiOjE0MzUwNjI1MjMsImp0aSI6ImlkMTIzNDU2IiwidHlwIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZWdpc3RlciJ9.HFmKrExGIFm5SwzTq_ayG80ELUIKnrR9psedV_6ZsuHl5ZLZ-1nV35o0yjKkN7qPQipQMK90xMvDYpi7e2XU9Q' end let(:payload) do { iss: 'https://jwt-idp.example.com', sub: 'mailto:mike@example.com', nbf: 1435062523, exp: 1435066123, iat: 1435062523, jti: 'id123456', typ: 'https://example.com/register' } end describe 'verify' do it 'should succeed' do expect do JSON::JWT.decode(jws_string, public_key, :ES256) end.not_to raise_error end end end end end json-jwt-1.14.0/spec/interop/with_nimbus_jose_spec.rb000066400000000000000000000057311426224527200227050ustar00rootroot00000000000000require 'spec_helper' describe 'interop' do describe 'with Nimbus JOSE' do if NimbusSpecHelper.nimbus_available? context 'JWE' do let(:shared_key) { SecureRandom.hex 16 } # default shared key is too short let(:private_key_path) { der_file_path 'rsa/private_key' } describe 'encrypt!' do shared_examples_for :gcm_encryption do context 'when enc=A128GCM' do before { jwe.enc = :A128GCM } it 'should decryptable by Nimbus JOSE JWT' do jwe.encrypt! key NimbusJWE.decrypt(jwe, private_key_path).should == plain_text end end context 'when enc=A256GCM' do before { jwe.enc = :A256GCM } it 'should decryptable by Nimbus JOSE JWT' do jwe.encrypt! key NimbusJWE.decrypt(jwe, private_key_path).should == plain_text end end end shared_examples_for :cbc_encryption do context 'when enc=A128CBC-HS256' do before { jwe.enc = :'A128CBC-HS256' } it 'should decryptable by Nimbus JOSE JWT' do jwe.encrypt! key NimbusJWE.decrypt(jwe, private_key_path).should == plain_text end end context 'when enc=A256CBC-HS512' do before { jwe.enc = :'A256CBC-HS512' } it 'should decryptable by Nimbus JOSE JWT' do jwe.encrypt! key NimbusJWE.decrypt(jwe, private_key_path).should == plain_text end end end context 'when plaintext given' do let(:plain_text) { 'Hello World' } let(:jwe) { JSON::JWE.new plain_text } context 'when alg=RSA1_5' do let(:key) { public_key } before { jwe.alg = :'RSA1_5' } it_behaves_like :gcm_encryption it_behaves_like :cbc_encryption end context 'when alg=RSA-OAEP' do let(:key) { public_key } before { jwe.alg = :'RSA-OAEP' } it_behaves_like :gcm_encryption it_behaves_like :cbc_encryption end end context 'when jwt given' do let(:plain_text) { jwt.to_s } let(:jwt) { JSON::JWT.new(foo: :bar) } let(:jwe) { JSON::JWE.new jwt } context 'when alg=RSA-OAEP' do let(:key) { public_key } before { jwe.alg = :'RSA1_5' } it_behaves_like :gcm_encryption it_behaves_like :cbc_encryption end context 'when alg=RSA-OAEP' do let(:key) { public_key } before { jwe.alg = :'RSA-OAEP' } it_behaves_like :gcm_encryption it_behaves_like :cbc_encryption end end end end else skip 'Nimbus JOSE unavailable' end end end json-jwt-1.14.0/spec/interop/with_rfc_example_spec.rb000066400000000000000000000013261426224527200226510ustar00rootroot00000000000000require 'spec_helper' describe 'interop' do describe 'with RFC Example' do describe 'JWK Thubmprint' do subject do JSON::JWK.new( kty: :RSA, n: '0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw', e: 'AQAB', alg: :RSA256, kid: '2011-04-29' ) end its(:thumbprint) { should == 'NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs' } end end end json-jwt-1.14.0/spec/json/000077500000000000000000000000001426224527200152615ustar00rootroot00000000000000json-jwt-1.14.0/spec/json/jwe_spec.rb000066400000000000000000000162321426224527200174110ustar00rootroot00000000000000require 'spec_helper' describe JSON::JWE do let(:private_key_path) { der_file_path 'rsa/private_key' } describe '#content_type' do let(:jwe) { JSON::JWE.new 'hello' } it do jwe.content_type.should == 'application/jose' end end describe 'encrypt!' do shared_examples_for :unexpected_algorithm_for_encryption do it do expect do jwe.encrypt!(key).to_s # NOTE: encrypt! won't raise, but to_s does. might need to fix. end.to raise_error JSON::JWE::UnexpectedAlgorithm end end shared_examples_for :unsupported_algorithm_for_encryption do it do expect do jwe.encrypt!(key).to_s # NOTE: encrypt! won't raise, but to_s does. might need to fix. end.to raise_error NotImplementedError end end context 'when plaintext given' do let(:plain_text) { 'Hello World' } let(:jwe) { JSON::JWE.new plain_text } context 'when alg=dir' do it :TODO end context 'when alg=A128KW' do it :TODO end context 'when alg=A256KW' do it :TODO end context 'when unknonw/unsupported algorithm given' do let(:key) { public_key } let(:alg) { :RSA1_5 } let(:enc) { :'A128CBC-HS256' } before { jwe.alg, jwe.enc = alg, enc } context 'when alg=unknown' do let(:alg) { :unknown } it_behaves_like :unexpected_algorithm_for_encryption end context 'when enc=unknown' do let(:enc) { :unknown } it_behaves_like :unexpected_algorithm_for_encryption end [:'ECDH-ES', :'ECDH-ES+A128KW', :'ECDH-ES+A256KW'].each do |alg| context "when alg=#{alg}" do let(:alg) { alg } it_behaves_like :unsupported_algorithm_for_encryption end end end end end describe 'decrypt!' do let(:plain_text) { 'Hello World' } let(:jwe_string) do _jwe_ = JSON::JWE.new plain_text _jwe_.alg, _jwe_.enc = alg, enc _jwe_.encrypt! key _jwe_.to_s end let(:jwe) do _jwe_ = JSON::JWE.decode jwe_string, :skip_decryption _jwe_.alg, _jwe_.enc = alg, enc _jwe_ end shared_examples_for :decryptable do it do jwe.decrypt! key jwe.plain_text.should == plain_text end end shared_examples_for :verify_cbc_authentication_tag do let(:jwe_string) do _jwe_ = JSON::JWE.new plain_text _jwe_.alg, _jwe_.enc = alg, enc _jwe_.encrypt! key hdr, extra, iv, cipher_text, _ = _jwe_.to_s.split '.' [hdr, extra, iv, cipher_text, ''].join '.' end it do # fetching those variables outside of exception block to make sure # we intercept exception in decrypt! and not in other place j = jwe k = key expect do j.decrypt! k end.to raise_error JSON::JWE::DecryptionFailed end end shared_examples_for :verify_gcm_authentication_tag do let(:jwe_string) do _jwe_ = JSON::JWE.new plain_text _jwe_.alg, _jwe_.enc = alg, enc _jwe_.encrypt! key header, key, iv, cipher_text, auth_tag = _jwe_.to_s.split('.') truncated_auth_tag = Base64.urlsafe_decode64(auth_tag).slice(0..-2) truncated_auth_tag = Base64.urlsafe_encode64(truncated_auth_tag, padding: false) [header, key, iv, cipher_text, truncated_auth_tag].join('.') end it do expect do jwe.decrypt! key end.to raise_error JSON::JWE::DecryptionFailed end end shared_examples_for :unexpected_algorithm_for_decryption do it do expect do jwe.decrypt! key end.to raise_error JSON::JWE::UnexpectedAlgorithm end end shared_examples_for :unsupported_algorithm_for_decryption do it do expect do jwe.decrypt! key end.to raise_error NotImplementedError end end context 'when alg=RSA1_5' do let(:alg) { :RSA1_5 } let(:key) { private_key } context 'when enc=A128GCM' do let(:enc) { :A128GCM } it_behaves_like :decryptable it_behaves_like :verify_gcm_authentication_tag end context 'when enc=A256GCM' do let(:enc) { :A256GCM } it_behaves_like :decryptable it_behaves_like :verify_gcm_authentication_tag end context 'when enc=A128CBC-HS256' do let(:enc) { :'A128CBC-HS256' } it_behaves_like :decryptable end context 'when enc=A256CBC-HS512' do let(:enc) { :'A256CBC-HS512' } it_behaves_like :decryptable end end context 'when alg=RSA-OAEP' do let(:alg) { :'RSA-OAEP' } let(:key) { private_key } context 'when enc=A128GCM' do let(:enc) { :A128GCM } it_behaves_like :decryptable it_behaves_like :verify_gcm_authentication_tag end context 'when enc=A256GCM' do let(:enc) { :A256GCM } it_behaves_like :decryptable it_behaves_like :verify_gcm_authentication_tag end context 'when enc=A128CBC-HS256' do let(:enc) { :'A128CBC-HS256' } it_behaves_like :decryptable it_behaves_like :verify_cbc_authentication_tag end context 'when enc=A256CBC-HS512' do let(:enc) { :'A256CBC-HS512' } it_behaves_like :decryptable it_behaves_like :verify_cbc_authentication_tag end end context 'when alg=dir' do let(:alg) { :dir } let(:key) { SecureRandom.random_bytes key_size } context 'when enc=A128GCM' do let(:enc) { :A128GCM } let(:key_size) { 16 } it_behaves_like :decryptable it_behaves_like :verify_gcm_authentication_tag end context 'when enc=A256GCM' do let(:enc) { :A256GCM } let(:key_size) { 32 } it_behaves_like :decryptable it_behaves_like :verify_gcm_authentication_tag end context 'when enc=A128CBC-HS256' do let(:enc) { :'A128CBC-HS256' } let(:key_size) { 32 } it_behaves_like :decryptable it_behaves_like :verify_cbc_authentication_tag end context 'when enc=A256CBC-HS512' do let(:enc) { :'A256CBC-HS512' } let(:key_size) { 64 } it_behaves_like :decryptable it_behaves_like :verify_cbc_authentication_tag end end context 'when alg=A128KW' do it :TODO end context 'when alg=A256KW' do it :TODO end context 'when unknonw/unsupported algorithm given' do let(:input) { 'header.key.iv.cipher_text.auth_tag' } let(:key) { public_key } let(:alg) { :RSA1_5 } let(:enc) { :'A128CBC-HS256' } context 'when alg=unknown' do let(:alg) { :unknown } it_behaves_like :unexpected_algorithm_for_decryption end context 'when enc=unknown' do let(:enc) { :unknown } it_behaves_like :unexpected_algorithm_for_decryption end [:'ECDH-ES', :'ECDH-ES+A128KW', :'ECDH-ES+A256KW'].each do |alg| context "when alg=#{alg}" do let(:alg) { alg } it_behaves_like :unsupported_algorithm_for_decryption end end end end end json-jwt-1.14.0/spec/json/jwk/000077500000000000000000000000001426224527200160545ustar00rootroot00000000000000json-jwt-1.14.0/spec/json/jwk/jwkizable_spec.rb000066400000000000000000000026461426224527200214050ustar00rootroot00000000000000require 'spec_helper' describe JSON::JWK::JWKizable do describe '#to_jwk' do subject { key.to_jwk } shared_examples_for :jwkizable_as_public do it { should be_instance_of JSON::JWK } it { should include *public_key_attributes.collect(&:to_s) } it { should_not include *private_key_attributes.collect(&:to_s) } end shared_examples_for :jwkizable_as_private do it { should be_instance_of JSON::JWK } it { should include *public_key_attributes.collect(&:to_s) } it { should include *private_key_attributes.collect(&:to_s) } end describe OpenSSL::PKey::RSA do let(:public_key_attributes) { [:kty, :n, :e] } let(:private_key_attributes) { [:d, :p, :q] } describe :public_key do let(:key) { public_key :rsa } it_behaves_like :jwkizable_as_public end describe :private_key do let(:key) { private_key :rsa } it_behaves_like :jwkizable_as_private end end describe OpenSSL::PKey::EC do let(:public_key_attributes) { [:kty, :crv, :x, :y] } let(:private_key_attributes) { [:d] } describe :public_key do let(:key) { public_key :ecdsa, curve_name: :prime256v1 } it_behaves_like :jwkizable_as_public end describe :private_key do let(:key) { private_key :ecdsa, curve_name: :prime256v1 } it_behaves_like :jwkizable_as_private end end end endjson-jwt-1.14.0/spec/json/jwk/set_spec.rb000066400000000000000000000030571426224527200202130ustar00rootroot00000000000000require 'spec_helper' describe JSON::JWK::Set do let(:jwk) { public_key.to_jwk } let(:set) { JSON::JWK::Set.new jwk } describe '#content_type' do it do set.content_type.should == 'application/jwk-set+json' end end context 'when single JWK given' do subject { JSON::JWK::Set.new jwk } it { should == [jwk] } end context 'when multiple JWKs given' do subject { JSON::JWK::Set.new jwk, jwk } it { should == [jwk, jwk] } end context 'when an Array of JWKs given' do subject { JSON::JWK::Set.new [jwk, jwk] } it { should == [jwk, jwk] } end context 'when JSON::JWK given' do subject { JSON::JWK::Set.new jwk } it 'should keep JSON::JWK' do subject.each do |jwk| jwk.should be_instance_of JSON::JWK end end end context 'when pure Hash given' do subject { JSON::JWK::Set.new jwk.as_json } it 'should convert into JSON::JWK' do subject.each do |jwk| jwk.should be_instance_of JSON::JWK end end end context 'when pure Hash with :keys key given' do subject do JSON::JWK::Set.new( keys: jwk.as_json ) end it 'should convert into JSON::JWK' do subject.each do |jwk| jwk.should be_instance_of JSON::JWK end end end describe '#as_json' do it 'should become proper JWK set format' do json = set.as_json json.should include :keys json[:keys].should == [jwk] end end describe '#to_json' do it do expect { set.to_json }.not_to raise_error end end endjson-jwt-1.14.0/spec/json/jwk_spec.rb000066400000000000000000000133311426224527200174140ustar00rootroot00000000000000require 'spec_helper' describe JSON::JWK do describe '#initialize' do let(:jwk) { JSON::JWK.new key } subject { jwk } shared_examples_for :jwk_with_kid do it { should be_instance_of JSON::JWK } describe 'kid' do subject { jwk[:kid] } it { should == jwk.thumbprint } end end shared_examples_for :jwk_without_kid do it { should be_instance_of JSON::JWK } describe 'kid' do subject { jwk[:kid] } it { should be_blank } end end context 'when no imput' do it do JSON::JWK.new.should be_blank end end context 'with OpenSSL::PKey::RSA' do let(:key) { public_key } it_behaves_like :jwk_with_kid end context 'with OpenSSL::PKey::EC' do let(:key) { public_key :ecdsa, curve_name: :prime256v1 } it_behaves_like :jwk_with_kid end context 'with String' do let(:key) { 'secret' } it_behaves_like :jwk_with_kid end context 'with JSON::JWK' do let(:key) do JSON::JWK.new( k: 'secret', kty: :oct ) end it_behaves_like :jwk_with_kid end context 'with Hash' do let(:key) do { k: 'secret', kty: :oct } end it_behaves_like :jwk_with_kid end context 'with nothing' do let(:jwk) { JSON::JWK.new } it_behaves_like :jwk_without_kid end end describe '#content_type' do let(:jwk) { JSON::JWK.new public_key } it do jwk.content_type.should == 'application/jwk+json' end end context 'when RSA public key given' do let(:jwk) { JSON::JWK.new public_key } it { jwk.keys.collect(&:to_sym).should include :kty, :e, :n } its(:kty) { jwk[:kty].should == :RSA } its(:e) { jwk[:e].should == Base64.urlsafe_encode64(public_key.e.to_s(2), padding: false) } its(:n) { jwk[:n].should == Base64.urlsafe_encode64(public_key.n.to_s(2), padding: false) } context 'when kid/use options given' do let(:jwk) { JSON::JWK.new public_key, kid: '12345', use: :sig } it { jwk.keys.collect(&:to_sym).should include :kid, :use } its(:kid) { jwk[:kid].should == '12345' } its(:use) { jwk[:use].should == :sig } end describe '#thumbprint' do context 'using default hash function' do subject { jwk.thumbprint } it { should == 'nuBTimkcSt_AuEsD8Yv3l8CoGV31bu_3gsRDGN1iVKA' } end context 'using SHA512 hash function' do subject { jwk.thumbprint :SHA512 } it { should == '6v7pXTnQLMiQgvJlPJUdhAUSuGLzgF8C1r3ABAMFet6bc53ea-Pq4ZGbGu3RoAFsNRT1-RhTzDqtqXuLU6NOtw' } end end describe '#to_key' do it { jwk.to_key.should be_instance_of OpenSSL::PKey::RSA } end end context 'when EC public key given' do let(:jwk) { JSON::JWK.new(public_key :ecdsa, curve_name: :prime256v1) } let(:expected_coordinates) do { 256 => { 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(:curve_name) do case digest_length when 256 :prime256v1 else nil end end let(:jwk) { JSON::JWK.new(public_key :ecdsa, curve_name: curve_name, 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.generate('secp112r2') 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 2048 expect do JSON::JWK.new key end.to raise_error JSON::JWK::UnknownAlgorithm, 'Unknown Key Type' end end end json-jwt-1.14.0/spec/json/jws_spec.rb000066400000000000000000000402541426224527200174300ustar00rootroot00000000000000require '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 let(:curve_name) do case alg when :ES256 :prime256v1 when :ES256K :secp256k1 end 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) do private_key :ecdsa, curve_name: curve_name, digest_length: algorithm.to_s[2,3].to_i end let(:public_key_or_secret) do public_key :ecdsa, curve_name: curve_name, digest_length: algorithm.to_s[2,3].to_i end it_behaves_like :jwt_with_alg it_behaves_like :self_verifiable end context 'when JSON::JWK key given' do let(:private_key_or_secret) do JSON::JWK.new( private_key :ecdsa, curve_name: curve_name, digest_length: algorithm.to_s[2,3].to_i ) end let(:public_key_or_secret) do JSON::JWK.new( public_key :ecdsa, curve_name: curve_name, digest_length: algorithm.to_s[2,3].to_i ) end 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 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) do private_key :ecdsa, curve_name: curve_name, digest_length: algorithm.to_s[2,3].to_i end context 'when OpenSSL::PKey::EC key given' do let(:public_key_or_secret) do public_key :ecdsa, curve_name: curve_name, digest_length: algorithm.to_s[2,3].to_i end it_behaves_like :success_signature_verification end context 'when JSON::JWK key given' do let(:public_key_or_secret) do JSON::JWK.new( public_key :ecdsa, curve_name: curve_name, digest_length: algorithm.to_s[2,3].to_i ) end 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 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.14.0/spec/json/jwt_spec.rb000066400000000000000000000365341426224527200174370ustar00rootroot00000000000000require '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, curve_name: :prime256v1) } 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, curve_name: :prime256v1) } 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.14.0/spec/spec_helper.rb000066400000000000000000000004641426224527200171320ustar00rootroot00000000000000require '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'