json-jwt-1.7.2/0000755000004100000410000000000013123325332013321 5ustar www-datawww-datajson-jwt-1.7.2/Rakefile0000644000004100000410000000061713123325332014772 0ustar www-datawww-datarequire '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.7.2/Gemfile0000644000004100000410000000004513123325332014613 0ustar www-datawww-datasource "http://rubygems.org" gemspecjson-jwt-1.7.2/.rspec0000644000004100000410000000003713123325332014436 0ustar www-datawww-data--color --format=documentation json-jwt-1.7.2/spec/0000755000004100000410000000000013123325332014253 5ustar www-datawww-datajson-jwt-1.7.2/spec/spec_helper.rb0000644000004100000410000000062513123325332017074 0ustar www-datawww-datarequire '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 def gcm_supported? RUBY_VERSION >= '2.0.0' && OpenSSL::OPENSSL_VERSION >= 'OpenSSL 1.0.1' end require 'helpers/sign_key_fixture_helper' require 'helpers/nimbus_spec_helper' json-jwt-1.7.2/spec/fixtures/0000755000004100000410000000000013123325332016124 5ustar www-datawww-datajson-jwt-1.7.2/spec/fixtures/ecdsa/0000755000004100000410000000000013123325332017203 5ustar www-datawww-datajson-jwt-1.7.2/spec/fixtures/ecdsa/512/0000755000004100000410000000000013123325332017512 5ustar www-datawww-datajson-jwt-1.7.2/spec/fixtures/ecdsa/512/public_key.pem0000644000004100000410000000041313123325332022341 0ustar www-datawww-data-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBwwIP5rRrqucT1O8LSY6oX8MZdGDl jeJNvh4c3BObaQPqK6PH0z9/x38UYevQ6GmrMSfitg9hmi4axsg8SAvSr/8BQtgR 3yX5tX8Ejbz1p7OFItnVEMXaW2AM8TL1jti2phrhwlymLLNUMyldbAGnpyODpNcg 2ShWtVdmE3jTiKG7gjY= -----END PUBLIC KEY-----json-jwt-1.7.2/spec/fixtures/ecdsa/512/private_key.pem0000644000004100000410000000055413123325332022543 0ustar www-datawww-data-----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIBBpwKqvGEZGpE3wX1fDzJjrrM4uXr16WKsijjqjRP8tHdnvr5p2fO zrPVyDVbiQDulOhSh9aouunuwmbudKjWvZagBwYFK4EEACOhgYkDgYYABAHDAg/m tGuq5xPU7wtJjqhfwxl0YOWN4k2+HhzcE5tpA+oro8fTP3/HfxRh69DoaasxJ+K2 D2GaLhrGyDxIC9Kv/wFC2BHfJfm1fwSNvPWns4Ui2dUQxdpbYAzxMvWO2LamGuHC XKYss1QzKV1sAaenI4Ok1yDZKFa1V2YTeNOIobuCNg== -----END EC PRIVATE KEY-----json-jwt-1.7.2/spec/fixtures/ecdsa/384/0000755000004100000410000000000013123325332017521 5ustar www-datawww-datajson-jwt-1.7.2/spec/fixtures/ecdsa/384/public_key.pem0000644000004100000410000000032613123325332022353 0ustar www-datawww-data-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEplzApyFnK7qzhg5XnIZbFj2hZoH2Vdl4 +RFm7DnsNMG9tyqrpfq2RyjfKABbcFRtixBzffhk3fcbmeipGLkvQBNCzeNm6QL3 hOUTH6IFBzOL0Y7HsGTopNTTspLjlivb -----END PUBLIC KEY-----json-jwt-1.7.2/spec/fixtures/ecdsa/384/private_key.pem0000644000004100000410000000044013123325332022544 0ustar www-datawww-data-----BEGIN EC PRIVATE KEY----- MIGkAgEBBDB1NRLzYeQa7oRUwWrnQFZOBVqzlyJ9n654/PFjCLJh/A/uGWeECoM2 1hXEvp80pqGgBwYFK4EEACKhZANiAASmXMCnIWcrurOGDlechlsWPaFmgfZV2Xj5 EWbsOew0wb23Kqul+rZHKN8oAFtwVG2LEHN9+GTd9xuZ6KkYuS9AE0LN42bpAveE 5RMfogUHM4vRjsewZOik1NOykuOWK9s= -----END EC PRIVATE KEY----- json-jwt-1.7.2/spec/fixtures/ecdsa/256/0000755000004100000410000000000013123325332017517 5ustar www-datawww-datajson-jwt-1.7.2/spec/fixtures/ecdsa/256/public_key.pem0000644000004100000410000000026113123325332022347 0ustar www-datawww-data-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsaPyrO4Lh9kh2FxrF9y1QVmZznWn RRJwpr12UHqzrVYwzPhb3POqWsmGqv4nKum+WdogjJlAToN+uA+TEwDDUw== -----END PUBLIC KEY-----json-jwt-1.7.2/spec/fixtures/ecdsa/256/private_key.pem0000644000004100000410000000034213123325332022543 0ustar www-datawww-data-----BEGIN EC PRIVATE KEY----- MHcCAQEEIHo5LvIgMVpOlEKjjZiE5n+xYtTxLm4Eumx7FRMgICyDoAoGCCqGSM49 AwEHoUQDQgAEsaPyrO4Lh9kh2FxrF9y1QVmZznWnRRJwpr12UHqzrVYwzPhb3POq WsmGqv4nKum+WdogjJlAToN+uA+TEwDDUw== -----END EC PRIVATE KEY-----json-jwt-1.7.2/spec/fixtures/rsa/0000755000004100000410000000000013123325332016711 5ustar www-datawww-datajson-jwt-1.7.2/spec/fixtures/rsa/public_key.pem0000644000004100000410000000065113123325332021544 0ustar www-datawww-data-----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.7.2/spec/fixtures/rsa/private_key.pem0000644000004100000410000000334513123325332021743 0ustar www-datawww-data-----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.7.2/spec/fixtures/rsa/private_key.der0000644000004100000410000000230213123325332021724 0ustar www-datawww-data00  *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.7.2/spec/json/jws_spec.rb0000644000004100000410000002467213123325332017401 0ustar www-datawww-datarequire 'spec_helper' describe JSON::JWS do let(:alg) { :none } let(:jwt) do _jwt_ = JSON::JWT.new claims _jwt_.alg = alg _jwt_ end let(:jws) { JSON::JWS.new jwt } let(:signed) { jws.sign! private_key_or_secret } let(:decoded) { JSON::JWT.decode signed.to_s, public_key_or_secret } 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 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 '#sign!' do shared_examples_for :generate_expected_signature do it do UrlSafeBase64.encode64(signed.signature).should == expected_signature[alg] 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].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 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].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 'when syntax option given' do context 'when general' do it 'should return General JWS JSON Serialization' do signed.to_json(syntax: :general).should == { payload: UrlSafeBase64.encode64(claims.to_json), signatures: [{ protected: UrlSafeBase64.encode64(signed.header.to_json), signature: UrlSafeBase64.encode64(signed.signature) }] }.to_json end context 'when not signed yet' do it 'should not fail' do jws.to_json(syntax: :general).should == { payload: UrlSafeBase64.encode64(claims.to_json), signatures: [{ protected: UrlSafeBase64.encode64(jws.header.to_json), signature: UrlSafeBase64.encode64('') }] }.to_json end end end context 'when flattened' do it 'should return Flattened JWS JSON Serialization' do signed.to_json(syntax: :flattened).should == { protected: UrlSafeBase64.encode64(signed.header.to_json), payload: UrlSafeBase64.encode64(claims.to_json), signature: UrlSafeBase64.encode64(signed.signature) }.to_json end context 'when not signed yet' do it 'should not fail' do jws.to_json(syntax: :flattened).should == { protected: UrlSafeBase64.encode64(jws.header.to_json), payload: UrlSafeBase64.encode64(claims.to_json), signature: UrlSafeBase64.encode64('') }.to_json end end end end end end json-jwt-1.7.2/spec/json/jwt_spec.rb0000644000004100000410000002337013123325332017374 0ustar www-datawww-datarequire '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 'otherwise' do let(:key) { private_key } its(:alg) { should == :RS256 } 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 [ UrlSafeBase64.encode64(malformed_header), payload, '' ].join('.') end it 'should do verification' 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 [ UrlSafeBase64.encode64(malformed_header), payload, '' ].join('.') end it 'should fail verification' 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, [malformed_header, payload].join('.') ) [ UrlSafeBase64.encode64(malformed_header), payload, UrlSafeBase64.encode64(malformed_signature) ].join('.') end it 'should fail verification' do expect do JSON::JWT.decode malformed_jwt_string, public_key end.to raise_error JSON::JWS::UnexpectedAlgorithm 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: UrlSafeBase64.encode64(claims.to_json), signatures: [{ protected: UrlSafeBase64.encode64(signed.header.to_json), signature: UrlSafeBase64.encode64(signed.signature) }] } end it_behaves_like :json_serialization_parser end context 'when flattened' do let(:serialized) do { protected: UrlSafeBase64.encode64(signed.header.to_json), payload: UrlSafeBase64.encode64(claims.to_json), signature: UrlSafeBase64.encode64(signed.signature) } 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 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.something.wrong' end.to raise_error JSON::JWT::InvalidFormat end end end end end json-jwt-1.7.2/spec/json/jwk/0000755000004100000410000000000013123325332016017 5ustar www-datawww-datajson-jwt-1.7.2/spec/json/jwk/jwkizable_spec.rb0000644000004100000410000000256413123325332021347 0ustar www-datawww-datarequire '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 } it_behaves_like :jwkizable_as_public end describe :private_key do let(:key) { private_key :ecdsa } it_behaves_like :jwkizable_as_private end end end endjson-jwt-1.7.2/spec/json/jwk/set_spec.rb0000644000004100000410000000305713123325332020156 0ustar www-datawww-datarequire '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.7.2/.travis.yml0000644000004100000410000000037713123325332015441 0ustar www-datawww-databefore_install: - gem install bundler - git submodule update --init --recursive rvm: - 2.2.2 # NOTE: 2.2.1 or lower aren't supported by activesupport 5.0, CI isn't needed for such legacy versions. - 2.2.6 - 2.3.3 - 2.4.1 jdk: - oraclejdk8 json-jwt-1.7.2/lib/0000755000004100000410000000000013123325332014067 5ustar www-datawww-datajson-jwt-1.7.2/lib/json/0000755000004100000410000000000013123325332015040 5ustar www-datawww-datajson-jwt-1.7.2/lib/json/jws.rb0000644000004100000410000001223513123325332016173 0ustar www-datawww-datamodule JSON class JWS < JWT class InvalidFormat < JWT::InvalidFormat; end class VerificationFailed < JWT::VerificationFailed; end class UnexpectedAlgorithm < JWT::UnexpectedAlgorithm; end NUM_OF_SEGMENTS = 3 attr_accessor :signature_base_string def initialize(jwt) update jwt end def sign!(private_key_or_secret) self.signature = sign signature_base_string, private_key_or_secret self end def verify!(public_key_or_secret) if alg.try(:to_sym) == :none raise UnexpectedAlgorithm if public_key_or_secret signature == '' or raise VerificationFailed else public_key_or_secret && valid?(public_key_or_secret) or raise VerificationFailed 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 end self end private def digest OpenSSL::Digest.new "SHA#{algorithm.to_s[2, 3]}" end def hmac? [:HS256, :HS384, :HS512].include? algorithm.try(:to_sym) end def rsa? [:RS256, :RS384, :RS512].include? algorithm.try(:to_sym) end def ecdsa? [:ES256, :ES384, :ES512].include? algorithm.try(:to_sym) end def signature_base_string @signature_base_string ||= [ header.to_json, self.to_json ].collect do |segment| UrlSafeBase64.encode64 segment 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 ecdsa? private_key = private_key_or_secret verify_ecdsa_group! private_key asn1_to_raw( private_key.dsa_sign_asn1(digest.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 ecdsa? public_key = public_key_or_secret verify_ecdsa_group! public_key public_key.dsa_verify_asn1( digest.digest(signature_base_string), raw_to_asn1(signature, public_key) ) 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 :prime256v1 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) 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('.', JWS::NUM_OF_SEGMENTS).collect do |segment| UrlSafeBase64.decode64 segment.to_s end header, claims = [header, claims].collect do |json| MultiJson.load(json).with_indifferent_access end jws = new claims jws.header = header jws.signature = signature jws.signature_base_string = input.split('.')[0, JWS::NUM_OF_SEGMENTS - 1].join('.') jws.verify! public_key_or_secret unless public_key_or_secret == :skip_verification jws end def decode_json_serialized(input, public_key_or_secret) 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 end end end end json-jwt-1.7.2/lib/json/jose.rb0000644000004100000410000000253413123325332016331 0ustar www-datawww-datarequire 'securecompare' module JSON module JOSE extend ActiveSupport::Concern included do extend ClassMethods include SecureCompare register_header_keys :alg, :jku, :jwk, :x5u, :x5t, :x5c, :kid, :typ, :cty, :crit alias_method :algorithm, :alg attr_accessor :header def header @header ||= {} end def content_type @content_type ||= 'application/jose' end end def with_jwk_support(key) case key when JSON::JWK key.to_key when JSON::JWK::Set key.detect do |jwk| jwk[:kid] && jwk[:kid] == kid end.try(:to_key) or raise JWK::Set::KidNotFound else key end end module ClassMethods def register_header_keys(*keys) keys.each do |header_key| define_method header_key do self.header[header_key] end define_method "#{header_key}=" do |value| self.header[header_key] = value end end end def decode(input, key_or_secret = nil) if input.is_a? Hash decode_json_serialized input, key_or_secret else decode_compact_serialized input, key_or_secret end rescue MultiJson::DecodeError raise JWT::InvalidFormat.new("Invalid JSON Format") end end end endjson-jwt-1.7.2/lib/json/jwk.rb0000644000004100000410000000632113123325332016162 0ustar www-datawww-datamodule 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 UrlSafeBase64.encode64 digest.digest(normalize.to_json) end def to_key case when rsa? to_rsa_key when ec? to_ec_key when oct? self[:k] else raise UnknownAlgorithm.new('Unknown Key Type') end end private def rsa? self[:kty].try(:to_sym) == :RSA end def ec? self[:kty].try(:to_sym) == :EC end def oct? self[:kty].try(:to_sym) == :oct end def calculate_default_kid self[:kid] = thumbprint rescue # ignore end def normalize case when rsa? { e: self[:e], kty: self[:kty], n: self[:n] } when ec? { crv: self[:crv], kty: self[:kty], x: self[:x], y: self[:y] } when oct? { k: self[:k], kty: self[:kty] } else raise UnknownAlgorithm.new('Unknown Key Type') end end def to_rsa_key e, n, d, p, q, dp, dq, qi = [:e, :n, :d, :p, :q, :dp, :dq, :qi].collect do |key| if self[key] OpenSSL::BN.new UrlSafeBase64.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].try(:to_sym) when :'P-256' 'prime256v1' when :'P-384' 'secp384r1' when :'P-521' 'secp521r1' else raise UnknownAlgorithm.new('Unknown EC Curve') end x, y, d = [:x, :y, :d].collect do |key| if self[key] OpenSSL::BN.new UrlSafeBase64.decode64(self[key]), 2 end end key = OpenSSL::PKey::EC.new curve_name key.private_key = d if d key.public_key = OpenSSL::PKey::EC::Point.new( OpenSSL::PKey::EC::Group.new(curve_name), OpenSSL::BN.new(['04' + x.to_s(16) + y.to_s(16)].pack('H*'), 2) ) key end end end json-jwt-1.7.2/lib/json/jwt.rb0000644000004100000410000000646313123325332016202 0ustar www-datawww-datarequire 'openssl' require 'url_safe_base64' require 'multi_json' require 'active_support' require 'active_support/core_ext' require 'json/jose' module JSON class JWT < ActiveSupport::HashWithIndifferentAccess 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 [:exp, :nbf, :iat].each do |key| claims[key] = claims[key].to_i if claims[key] end update claims end def sign(private_key_or_secret, algorithm = :autodetect) if algorithm == :autodetect # NOTE: # I'd like to make :RS256 default. # However, by histrical reasons, :HS256 was default. # This code is needed to keep legacy behavior. algorithm = private_key_or_secret.is_a?(String) ? :HS256 : :RS256 end 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| UrlSafeBase64.encode64 segment.to_s end.join('.') end def as_json(options = {}) case options[:syntax] when :general { payload: UrlSafeBase64.encode64(self.to_json), signatures: [{ protected: UrlSafeBase64.encode64(header.to_json), signature: UrlSafeBase64.encode64(signature.to_s) }] } when :flattened { protected: UrlSafeBase64.encode64(header.to_json), payload: UrlSafeBase64.encode64(self.to_json), signature: UrlSafeBase64.encode64(signature.to_s) } else super end end class << self def decode_compact_serialized(jwt_string, key_or_secret) case jwt_string.count('.') + 1 when JWS::NUM_OF_SEGMENTS JWS.decode_compact_serialized jwt_string, key_or_secret when JWE::NUM_OF_SEGMENTS JWE.decode_compact_serialized jwt_string, key_or_secret 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) input = input.with_indifferent_access if (input[:signatures] || input[:signature]).present? JWS.decode_json_serialized input, key_or_secret elsif input[:ciphertext].present? JWE.decode_json_serialized input, key_or_secret else raise InvalidFormat.new("Unexpected JOSE JSON Serialization Format.") end end end end end require 'json/jws' require 'json/jwe' require 'json/jwk' require 'json/jwk/jwkizable' require 'json/jwk/set'json-jwt-1.7.2/lib/json/jwe.rb0000644000004100000410000002114013123325332016150 0ustar www-datawww-datarequire 'securerandom' require 'bindata' module JSON class JWE class InvalidFormat < JWT::InvalidFormat; end class DecryptionFailed < JWT::VerificationFailed; end class UnexpectedAlgorithm < JWT::UnexpectedAlgorithm; end NUM_OF_SEGMENTS = 5 include JOSE attr_accessor( :public_key_or_secret, :private_key_or_secret, :plain_text, :cipher_text, :authentication_tag, :iv, :auth_data, :content_encryption_key, :jwe_encrypted_key, :encryption_key, :mac_key ) register_header_keys :enc, :epk, :zip, :apu, :apv alias_method :encryption_method, :enc def initialize(input = nil) self.plain_text = input.to_s end def encrypt!(public_key_or_secret) self.public_key_or_secret = with_jwk_support public_key_or_secret cipher.encrypt 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 = UrlSafeBase64.encode64 header.to_json cipher.auth_data = auth_data if gcm? self.cipher_text = cipher.update(plain_text) + cipher.final self end def decrypt!(private_key_or_secret) self.private_key_or_secret = with_jwk_support private_key_or_secret cipher.decrypt 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? 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| UrlSafeBase64.encode64 segment.to_s end.join('.') end def as_json(options = {}) case options[:syntax] when :general { protected: UrlSafeBase64.encode64(header.to_json), recipients: [{ encrypted_key: UrlSafeBase64.encode64(jwe_encrypted_key) }], iv: UrlSafeBase64.encode64(iv), ciphertext: UrlSafeBase64.encode64(cipher_text), tag: UrlSafeBase64.encode64(authentication_tag) } else { protected: UrlSafeBase64.encode64(header.to_json), encrypted_key: UrlSafeBase64.encode64(jwe_encrypted_key), iv: UrlSafeBase64.encode64(iv), ciphertext: UrlSafeBase64.encode64(cipher_text), tag: UrlSafeBase64.encode64(authentication_tag) } end end private # common def gcm_supported? RUBY_VERSION >= '2.0.0' && OpenSSL::OPENSSL_VERSION >= 'OpenSSL 1.0.1' end def gcm? [:A128GCM, :A256GCM].include? encryption_method.try(:to_sym) end def cbc? [:'A128CBC-HS256', :'A256CBC-HS512'].include? encryption_method.try(:to_sym) end def dir? :dir == algorithm.try(:to_sym) end def cipher @cipher ||= if gcm? && !gcm_supported? raise UnexpectedAlgorithm.new('AEC GCM requires Ruby 2.0+ and OpenSSL 1.0.1c+') else OpenSSL::Cipher.new cipher_name end end def cipher_name case encryption_method.try(:to_sym) when :A128GCM 'aes-128-gcm' when :A256GCM 'aes-256-gcm' when :'A128CBC-HS256' 'aes-128-cbc' when :'A256CBC-HS512' 'aes-256-cbc' else raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm') end end def sha_size case encryption_method.try(:to_sym) when :'A128CBC-HS256' 256 when :'A256CBC-HS512' 512 else raise UnexpectedAlgorithm.new('Unknown Hash Size') end end def sha_digest OpenSSL::Digest.new "SHA#{sha_size}" end def derive_encryption_and_mac_keys 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 algorithm.try(:to_sym) when :RSA1_5 public_key_or_secret.public_encrypt content_encryption_key when :'RSA-OAEP' public_key_or_secret.public_encrypt content_encryption_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING when :A128KW raise NotImplementedError.new('A128KW not supported yet') when :A256KW raise NotImplementedError.new('A256KW not supported yet') when :dir '' when :'ECDH-ES' raise NotImplementedError.new('ECDH-ES not supported yet') when :'ECDH-ES+A128KW' raise NotImplementedError.new('ECDH-ES+A128KW not supported yet') when :'ECDH-ES+A256KW' raise NotImplementedError.new('ECDH-ES+A256KW not supported yet') else raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm') end end def generate_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 algorithm.try(:to_sym) when :RSA1_5 private_key_or_secret.private_decrypt jwe_encrypted_key when :'RSA-OAEP' private_key_or_secret.private_decrypt jwe_encrypted_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING when :A128KW raise NotImplementedError.new('A128KW not supported yet') when :A256KW raise NotImplementedError.new('A256KW not supported yet') when :dir private_key_or_secret when :'ECDH-ES' raise NotImplementedError.new('ECDH-ES not supported yet') when :'ECDH-ES+A128KW' raise NotImplementedError.new('ECDH-ES+A128KW not supported yet') when :'ECDH-ES+A256KW' raise NotImplementedError.new('ECDH-ES+A256KW not supported yet') else raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm') end 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) unless input.count('.') + 1 == NUM_OF_SEGMENTS raise InvalidFormat.new("Invalid JWE Format. JWE should include #{NUM_OF_SEGMENTS} segments.") end jwe = new _header_json_, jwe.jwe_encrypted_key, jwe.iv, jwe.cipher_text, jwe.authentication_tag = input.split('.').collect do |segment| UrlSafeBase64.decode64 segment end jwe.auth_data = input.split('.').first jwe.header = MultiJson.load(_header_json_).with_indifferent_access jwe.decrypt! private_key_or_secret unless private_key_or_secret == :skip_decryption jwe end def decode_json_serialized(input, private_key_or_secret) input = input.with_indifferent_access jwe_encrypted_key = if input[:recipients].present? input[:recipients].first[:encrypted_key] else input[:encrypted_key] end compact_serialized = [ input[:protected], jwe_encrypted_key, input[:iv], input[:ciphertext], input[:tag] ].join('.') decode_compact_serialized compact_serialized, private_key_or_secret end end end endjson-jwt-1.7.2/lib/json/jwk/0000755000004100000410000000000013123325332015633 5ustar www-datawww-datajson-jwt-1.7.2/lib/json/jwk/set.rb0000644000004100000410000000116213123325332016753 0ustar www-datawww-datamodule 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.7.2/lib/json/jwk/jwkizable.rb0000644000004100000410000000404213123325332020142 0ustar www-datawww-datamodule JSON class JWK module JWKizable module RSA def to_jwk(ex_params = {}) params = { kty: :RSA, e: UrlSafeBase64.encode64(e.to_s(2)), n: UrlSafeBase64.encode64(n.to_s(2)) }.merge ex_params if private? params.merge!( d: UrlSafeBase64.encode64(d.to_s(2)), p: UrlSafeBase64.encode64(p.to_s(2)), q: UrlSafeBase64.encode64(q.to_s(2)), dp: UrlSafeBase64.encode64(dmp1.to_s(2)), dq: UrlSafeBase64.encode64(dmq1.to_s(2)), qi: UrlSafeBase64.encode64(iqmp.to_s(2)), ) end JWK.new params end end module EC def to_jwk(ex_params = {}) params = { kty: :EC, crv: curve_name, x: UrlSafeBase64.encode64(coordinates[:x].to_s(2)), y: UrlSafeBase64.encode64(coordinates[:y].to_s(2)) }.merge ex_params params[:d] = UrlSafeBase64.encode64(coordinates[:d].to_s(2)) if private_key? JWK.new params end private def curve_name case group.curve_name when 'prime256v1' :'P-256' when 'secp384r1' :'P-384' when 'secp521r1' :'P-521' else raise UnknownAlgorithm.new('Unknown EC Curve') end end def coordinates unless @coordinates hex = public_key.to_bn.to_s(16) data_len = hex.length - 2 hex_x = hex[2, data_len / 2] hex_y = hex[2 + data_len / 2, data_len / 2] @coordinates = { x: OpenSSL::BN.new([hex_x].pack('H*'), 2), y: OpenSSL::BN.new([hex_y].pack('H*'), 2) } @coordinates[:d] = private_key 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.7.2/.gitignore0000644000004100000410000000024713123325332015314 0ustar www-datawww-data## MAC OS .DS_Store ## TEXTMATE *.tmproj tmtags ## EMACS *~ \#* .\#* ## VIM *.swp ## PROJECT::GENERAL coverage* rdoc pkg Gemfile.lock ## PROJECT::SPECIFIC .class json-jwt-1.7.2/LICENSE0000644000004100000410000000203613123325332014327 0ustar www-datawww-dataCopyright (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.7.2/VERSION0000644000004100000410000000000613123325332014365 0ustar www-datawww-data1.7.2 json-jwt-1.7.2/json-jwt.gemspec0000644000004100000410000000217713123325332016450 0ustar www-datawww-dataGem::Specification.new do |gem| gem.name = "json-jwt" gem.version = File.read("VERSION") gem.authors = ["nov matake"] gem.email = ["nov@matake.jp"] gem.homepage = "https://github.com/nov/json-jwt" gem.summary = %q{JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and JSON Web Key) in Ruby} gem.description = %q{JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and JSON Web Key) in Ruby} gem.license = 'MIT' gem.files = `git ls-files`.split("\n") gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } gem.require_paths = ["lib"] gem.add_runtime_dependency "multi_json", ">= 1.3" gem.add_runtime_dependency "url_safe_base64" gem.add_runtime_dependency "activesupport" gem.add_runtime_dependency "bindata" gem.add_runtime_dependency "securecompare" gem.add_development_dependency "rake", ">= 0.8" gem.add_development_dependency "simplecov" gem.add_development_dependency "rspec" gem.add_development_dependency 'rspec-its' endjson-jwt-1.7.2/README.md0000644000004100000410000000322513123325332014602 0ustar www-datawww-data# 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.7.2/.gitmodules0000644000004100000410000000020013123325332015466 0ustar www-datawww-data[submodule "spec/helpers/json-jwt-nimbus"] path = spec/helpers/json-jwt-nimbus url = git://github.com/nov/json-jwt-nimbus.git