jwt-1.0.0/0000755000004100000410000000000012340661777012361 5ustar www-datawww-datajwt-1.0.0/Rakefile0000644000004100000410000000067212340661777014033 0ustar www-datawww-datarequire 'rubygems' require 'rake' require 'echoe' Echoe.new('jwt', '1.0.0') do |p| p.description = "JSON Web Token implementation in Ruby" p.url = "http://github.com/progrium/ruby-jwt" p.author = "Jeff Lindsay" p.email = "progrium@gmail.com" p.ignore_pattern = ["tmp/*"] p.development_dependencies = ["echoe >=4.6.3"] p.licenses = "MIT" end task :test do sh "rspec spec/jwt_spec.rb" end jwt-1.0.0/spec/0000755000004100000410000000000012340661777013313 5ustar www-datawww-datajwt-1.0.0/spec/helper.rb0000644000004100000410000000013712340661777015120 0ustar www-datawww-datarequire 'rspec' require "#{File.dirname(__FILE__)}/../lib/jwt.rb" RSpec.configure do |c| end jwt-1.0.0/spec/jwt_spec.rb0000644000004100000410000001550712340661777015466 0ustar www-datawww-datarequire 'helper' describe JWT do before do @payload = {"foo" => "bar"} end it "encodes and decodes JWTs" do secret = "secret" jwt = JWT.encode(@payload, secret) decoded_payload = JWT.decode(jwt, secret) expect(decoded_payload).to include(@payload) end it "encodes and decodes JWTs for RSA signatures" do private_key = OpenSSL::PKey::RSA.generate(512) jwt = JWT.encode(@payload, private_key, "RS256") decoded_payload = JWT.decode(jwt, private_key.public_key) expect(decoded_payload).to include(@payload) end it "encodes and decodes JWTs with custom header fields" do private_key = OpenSSL::PKey::RSA.generate(512) jwt = JWT.encode(@payload, private_key, "RS256", {"kid" => 'default'}) decoded_payload = JWT.decode(jwt) do |header| expect(header["kid"]).to eq('default') private_key.public_key end expect(decoded_payload).to include(@payload) end it "decodes valid JWTs" do example_payload = {"hello" => "world"} example_secret = 'secret' example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8' decoded_payload = JWT.decode(example_jwt, example_secret) expect(decoded_payload).to include(example_payload) end it "raises exception when the token is invalid" do example_secret = 'secret' # Same as above exmaple with some random bytes replaced example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHiMomlwIjogIkJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8' expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError) end it "raises exception with wrong hmac key" do right_secret = 'foo' bad_secret = 'bar' jwt_message = JWT.encode(@payload, right_secret, "HS256") expect { JWT.decode(jwt_message, bad_secret) }.to raise_error(JWT::DecodeError) end it "raises exception with wrong rsa key" do right_private_key = OpenSSL::PKey::RSA.generate(512) bad_private_key = OpenSSL::PKey::RSA.generate(512) jwt = JWT.encode(@payload, right_private_key, "RS256") expect { JWT.decode(jwt, bad_private_key.public_key) }.to raise_error(JWT::DecodeError) end it "raises exception with invalid signature" do example_secret = 'secret' example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.' expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError) end it "raises exception with nonexistent header" do expect { JWT.decode("..stuff") }.to raise_error(JWT::DecodeError) end it "raises exception with nonexistent payload" do expect { JWT.decode("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9..stuff") }.to raise_error(JWT::DecodeError) end it "raises exception with nil jwt" do expect { JWT.decode(nil) }.to raise_error(JWT::DecodeError) end it "allows decoding without key" do right_secret = 'foo' bad_secret = 'bar' jwt = JWT.encode(@payload, right_secret) decoded_payload = JWT.decode(jwt, bad_secret, false) expect(decoded_payload).to include(@payload) end it "checks the key when verify is truthy" do right_secret = 'foo' bad_secret = 'bar' jwt = JWT.encode(@payload, right_secret) verify = "yes" =~ /^y/i expect { JWT.decode(jwt, bad_secret, verify) }.to raise_error(JWT::DecodeError) end it "raises exception on unsupported crypto algorithm" do expect { JWT.encode(@payload, "secret", 'HS1024') }.to raise_error(NotImplementedError) end it "encodes and decodes plaintext JWTs" do jwt = JWT.encode(@payload, nil, nil) expect(jwt.split('.').length).to eq(2) decoded_payload = JWT.decode(jwt, nil, nil) expect(decoded_payload).to include(@payload) end it "requires a signature segment when verify is truthy" do jwt = JWT.encode(@payload, nil, nil) expect(jwt.split('.').length).to eq(2) expect { JWT.decode(jwt, nil, true) }.to raise_error(JWT::DecodeError) end it "does not use == to compare digests" do secret = "secret" jwt = JWT.encode(@payload, secret) crypto_segment = jwt.split(".").last signature = JWT.base64url_decode(crypto_segment) expect(signature).not_to receive('==') expect(JWT).to receive(:base64url_decode).with(crypto_segment).once.and_return(signature) expect(JWT).to receive(:base64url_decode).at_least(:once).and_call_original JWT.decode(jwt, secret) end describe "secure comparison" do it "returns true if strings are equal" do expect(JWT.secure_compare("Foo", "Foo")).to be_true end it "returns false if either input is nil or empty" do [nil, ""].each do |bad| expect(JWT.secure_compare(bad, "Foo")).to be_false expect(JWT.secure_compare("Foo", bad)).to be_false end end it "retuns false if the strings are different" do expect(JWT.secure_compare("Foo", "Bar")).to be_false end end # no method should leave OpenSSL.errors populated after do expect(OpenSSL.errors).to be_empty end it "raise exception on invalid signature" do pubkey = OpenSSL::PKey::RSA.new(<<-PUBKEY) -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCaY7425h964bjaoLeUm SlZ8sK7VtVk9zHbGmZh2ygGYwfuUf2bmMye2Ofv99yDE/rd4loVIAcu7RVvDRgHq 3/CZTnIrSvHsiJQsHBNa3d+F1ihPfzURzf1M5k7CFReBj2SBXhDXd57oRfBQj12w CVhhwP6kGTAWuoppbIIIBfNF2lE/Nvm7lVVYQqL9xOrP/AQ4xRbpQlB8Ll9sO9Or SvbWhCDa/LMOWxHdmrcJi6XoSg1vnOyCoKbyAoauTt/XqdkHbkDdQ6HFbJieu9il LDZZNliPhfENuKeC2MCGVXTEu8Cqhy1w6e4axavLlXoYf4laJIZ/e7au8SqDbY0B xwIDAQAB -----END PUBLIC KEY----- PUBKEY jwt = ( 'eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY' + 'XVkIjoiMTA2MDM1Nzg5MTY4OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSI' + 'sImNpZCI6IjEwNjAzNTc4OTE2ODguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb' + '20iLCJpZCI6IjExNjQ1MjgyNDMwOTg1Njc4MjE2MyIsInRva2VuX2hhc2giOiJ' + '0Z2hEOUo4bjhWME4ydmN3NmVNaWpnIiwiaWF0IjoxMzIwNjcwOTc4LCJleHAiO' + 'jEzMjA2NzQ4Nzh9.D8x_wirkxDElqKdJBcsIws3Ogesk38okz6MN7zqC7nEAA7' + 'wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl' + '6hFycH8wj7Su6JqqyEbIVLxE7q7DkAZGaMPkxbTHs1EhSd5_oaKQ6O4xO3ZnnT4' ) expect { JWT.decode(jwt, pubkey, true) }.to raise_error(JWT::DecodeError) end describe "urlsafe base64 encoding" do it "replaces + and / with - and _" do allow(Base64).to receive(:encode64) { "string+with/non+url-safe/characters_" } expect(JWT.base64url_encode("foo")).to eq("string-with_non-url-safe_characters_") end end describe 'decoded_segments' do it "allows access to the decoded header and payload" do secret = "secret" jwt = JWT.encode(@payload, secret) decoded_segments = JWT.decoded_segments(jwt) expect(decoded_segments.size).to eq(4) expect(decoded_segments[0]).to eq({"typ" => "JWT", "alg" => "HS256"}) expect(decoded_segments[1]).to eq(@payload) end end end jwt-1.0.0/lib/0000755000004100000410000000000012340661777013127 5ustar www-datawww-datajwt-1.0.0/lib/jwt.rb0000644000004100000410000001057512340661777014270 0ustar www-datawww-data# # JSON Web Token implementation # # Should be up to date with the latest spec: # http://self-issued.info/docs/draft-jones-json-web-token-06.html require "base64" require "openssl" require "jwt/json" module JWT class DecodeError < StandardError; end extend JWT::Json module_function def sign(algorithm, msg, key) if ["HS256", "HS384", "HS512"].include?(algorithm) sign_hmac(algorithm, msg, key) elsif ["RS256", "RS384", "RS512"].include?(algorithm) sign_rsa(algorithm, msg, key) else raise NotImplementedError.new("Unsupported signing method") end end def sign_rsa(algorithm, msg, private_key) private_key.sign(OpenSSL::Digest.new(algorithm.sub("RS", "sha")), msg) end def verify_rsa(algorithm, public_key, signing_input, signature) public_key.verify(OpenSSL::Digest.new(algorithm.sub("RS", "sha")), signature, signing_input) end def sign_hmac(algorithm, msg, key) OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub("HS", "sha")), key, msg) end def base64url_decode(str) str += "=" * (4 - str.length.modulo(4)) Base64.decode64(str.tr("-_", "+/")) end def base64url_encode(str) Base64.encode64(str).tr("+/", "-_").gsub(/[\n=]/, "") end def encoded_header(algorithm="HS256", header_fields={}) header = {"typ" => "JWT", "alg" => algorithm}.merge(header_fields) base64url_encode(encode_json(header)) end def encoded_payload(payload) base64url_encode(encode_json(payload)) end def encoded_signature(signing_input, key, algorithm) if algorithm == "none" "" else signature = sign(algorithm, signing_input, key) base64url_encode(signature) end end def encode(payload, key, algorithm="HS256", header_fields={}) algorithm ||= "none" segments = [] segments << encoded_header(algorithm, header_fields) segments << encoded_payload(payload) segments << encoded_signature(segments.join("."), key, algorithm) segments.join(".") end def raw_segments(jwt, verify=true) segments = jwt.split(".") required_num_segments = verify ? [3] : [2,3] raise JWT::DecodeError.new("Not enough or too many segments") unless required_num_segments.include? segments.length segments end def decode_header_and_payload(header_segment, payload_segment) header = decode_json(base64url_decode(header_segment)) payload = decode_json(base64url_decode(payload_segment)) [header, payload] end def decoded_segments(jwt, verify=true) header_segment, payload_segment, crypto_segment = raw_segments(jwt, verify) header, payload = decode_header_and_payload(header_segment, payload_segment) signature = base64url_decode(crypto_segment.to_s) if verify signing_input = [header_segment, payload_segment].join(".") [header, payload, signature, signing_input] end def decode(jwt, key=nil, verify=true, &keyfinder) raise JWT::DecodeError.new("Nil JSON web token") unless jwt header, payload, signature, signing_input = decoded_segments(jwt, verify) raise JWT::DecodeError.new("Not enough or too many segments") unless header && payload if verify algo, key = signature_algorithm_and_key(header, key, &keyfinder) verify_signature(algo, key, signing_input, signature) end return payload,header end def signature_algorithm_and_key(header, key, &keyfinder) if keyfinder key = keyfinder.call(header) end [header['alg'], key] end def verify_signature(algo, key, signing_input, signature) begin if ["HS256", "HS384", "HS512"].include?(algo) raise JWT::DecodeError.new("Signature verification failed") unless secure_compare(signature, sign_hmac(algo, signing_input, key)) elsif ["RS256", "RS384", "RS512"].include?(algo) raise JWT::DecodeError.new("Signature verification failed") unless verify_rsa(algo, key, signing_input, signature) else raise JWT::DecodeError.new("Algorithm not supported") end rescue OpenSSL::PKey::PKeyError raise JWT::DecodeError.new("Signature verification failed") ensure OpenSSL.errors.clear end end # From devise # constant-time comparison algorithm to prevent timing attacks def secure_compare(a, b) return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize l = a.unpack "C#{a.bytesize}" res = 0 b.each_byte { |byte| res |= byte ^ l.shift } res == 0 end end jwt-1.0.0/lib/jwt/0000755000004100000410000000000012340661777013733 5ustar www-datawww-datajwt-1.0.0/lib/jwt/json.rb0000644000004100000410000000115712340661777015235 0ustar www-datawww-datamodule JWT module Json if RUBY_VERSION >= "1.9" && !defined?(MultiJson) require 'json' def decode_json(encoded) JSON.parse(encoded) rescue JSON::ParserError raise JWT::DecodeError.new("Invalid segment encoding") end def encode_json(raw) JSON.generate(raw) end else require "multi_json" def decode_json(encoded) MultiJson.decode(encoded) rescue MultiJson::LoadError raise JWT::DecodeError.new("Invalid segment encoding") end def encode_json(raw) MultiJson.encode(raw) end end end endjwt-1.0.0/metadata.yml0000644000004100000410000000301512340661777014663 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: jwt version: !ruby/object:Gem::Version version: 1.0.0 prerelease: platform: ruby authors: - Jeff Lindsay autorequire: bindir: bin cert_chain: [] date: 2014-05-07 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: echoe requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 4.6.3 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 4.6.3 description: JSON Web Token implementation in Ruby email: progrium@gmail.com executables: [] extensions: [] extra_rdoc_files: - lib/jwt.rb - lib/jwt/json.rb files: - Rakefile - lib/jwt.rb - lib/jwt/json.rb - spec/helper.rb - spec/jwt_spec.rb - Manifest - jwt.gemspec homepage: http://github.com/progrium/ruby-jwt licenses: - MIT post_install_message: rdoc_options: - --line-numbers - --title - Jwt - --main - README.md require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '1.2' requirements: [] rubyforge_project: jwt rubygems_version: 1.8.23 signing_key: specification_version: 3 summary: JSON Web Token implementation in Ruby test_files: [] jwt-1.0.0/Manifest0000644000004100000410000000011512340661777014047 0ustar www-datawww-dataRakefile lib/jwt.rb lib/jwt/json.rb spec/helper.rb spec/jwt_spec.rb Manifest jwt-1.0.0/jwt.gemspec0000644000004100000410000000222312340661777014531 0ustar www-datawww-data# -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = "jwt" s.version = "1.0.0" s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version= s.authors = ["Jeff Lindsay"] s.date = "2014-05-07" s.description = "JSON Web Token implementation in Ruby" s.email = "progrium@gmail.com" s.extra_rdoc_files = ["lib/jwt.rb", "lib/jwt/json.rb"] s.files = ["Rakefile", "lib/jwt.rb", "lib/jwt/json.rb", "spec/helper.rb", "spec/jwt_spec.rb", "Manifest", "jwt.gemspec"] s.homepage = "http://github.com/progrium/ruby-jwt" s.licenses = ["MIT"] s.rdoc_options = ["--line-numbers", "--title", "Jwt", "--main", "README.md"] s.require_paths = ["lib"] s.rubyforge_project = "jwt" s.rubygems_version = "1.8.23" s.summary = "JSON Web Token implementation in Ruby" if s.respond_to? :specification_version then s.specification_version = 3 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, [">= 4.6.3"]) else s.add_dependency(%q, [">= 4.6.3"]) end else s.add_dependency(%q, [">= 4.6.3"]) end end