Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow access to header and payload without signature verification #32

Merged
merged 1 commit into from
Jan 30, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 64 additions & 36 deletions lib/jwt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

require "base64"
require "openssl"
require "multi_json"
require "jwt/json"

module JWT
class DecodeError < StandardError; end
extend JWT::Json

module_function

Expand Down Expand Up @@ -44,59 +45,86 @@ def base64url_encode(str)
Base64.encode64(str).tr("+/", "-_").gsub(/[\n=]/, "")
end

def encode(payload, key, algorithm="HS256", header_fields={})
algorithm ||= "none"
segments = []
def encoded_header(algorithm="HS256", header_fields={})
header = {"typ" => "JWT", "alg" => algorithm}.merge(header_fields)
segments << base64url_encode(MultiJson.encode(header))
segments << base64url_encode(MultiJson.encode(payload))
signing_input = segments.join(".")
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"
segments << ""
""
else
signature = sign(algorithm, signing_input, key)
segments << base64url_encode(signature)
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 decode(jwt, key=nil, verify=true, &keyfinder)
def raw_segments(jwt, verify=true)
segments = jwt.split(".")
raise JWT::DecodeError.new("Not enough or too many segments") unless [2,3].include? segments.length
header_segment, payload_segment, crypto_segment = segments
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(".")
begin
header = MultiJson.decode(base64url_decode(header_segment))
payload = MultiJson.decode(base64url_decode(payload_segment))
signature = base64url_decode(crypto_segment.to_s) if verify
rescue MultiJson::LoadError
raise JWT::DecodeError.new("Invalid segment encoding")
end
[header, payload, signature, signing_input]
end

def decode(jwt, key=nil, verify=true, &keyfinder)
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 = header["alg"]
algo, key = signature_algorithm_and_key(header, key, &keyfinder)
verify_signature(algo, key, signing_input, signature)
end
payload
end

if keyfinder
key = keyfinder.call(header)
end
def signature_algorithm_and_key(header, key, &keyfinder)
if keyfinder
key = keyfinder.call(header)
end
[header['alg'], key]
end

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
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
payload
end

# From devise
Expand Down
16 changes: 16 additions & 0 deletions lib/jwt/json.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module JWT
module Json

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
21 changes: 19 additions & 2 deletions spec/jwt_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@
expect(decoded_payload).to eq(@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)
Expand All @@ -125,8 +131,8 @@
end
end

it "retuns falise of the strings are different" do
expect(JWT.secure_compare("Foo", "Bar")).to be_false
it "retuns false if the strings are different" do
expect(JWT.secure_compare("Foo", "Bar")).to be_false
end
end

Expand Down Expand Up @@ -166,4 +172,15 @@
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