Skip to content
This repository has been archived by the owner on Jan 1, 2024. It is now read-only.

Commit

Permalink
Clean up JSON and util methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Greg Beech committed Oct 5, 2014
1 parent ff96525 commit b826d4e
Show file tree
Hide file tree
Showing 10 changed files with 47 additions and 81 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,12 @@
# Change Log

## 0.7.0 (5 October 2014)

Breaking changes:

- Removed the `jwt_` prefix from methods on the `Sandal::Util` module.
- Removed `JSON` and just use the built-in `JSON` module.

## 0.6.0 (28 February 2014)

Breaking changes:
Expand Down
18 changes: 9 additions & 9 deletions lib/sandal.rb
@@ -1,9 +1,9 @@
require "json"
require "openssl"
require "zlib"
require "sandal/version"
require "sandal/claims"
require "sandal/enc"
require "sandal/json"
require "sandal/sig"
require "sandal/util"

Expand Down Expand Up @@ -112,13 +112,13 @@ def self.encode_token(payload, signer, header_fields = nil)
header = {}
header["alg"] = signer.name
header = header_fields.merge(header) if header_fields
header = Sandal::Json.dump(header)
header = JSON.generate(header)

payload = Sandal::Json.dump(payload) unless payload.is_a?(String)
payload = JSON.generate(payload) unless payload.is_a?(String)

sec_input = [header, payload].map { |p| Sandal::Util.jwt_base64_encode(p) }.join(".")
sec_input = [header, payload].map { |p| Sandal::Util.base64_encode(p) }.join(".")
signature = signer.sign(sec_input)
[sec_input, Sandal::Util.jwt_base64_encode(signature)].join(".")
[sec_input, Sandal::Util.base64_encode(signature)].join(".")
end

# Creates an encrypted JSON Web Token.
Expand All @@ -140,7 +140,7 @@ def self.encrypt_token(payload, encrypter, header_fields = nil)
payload = Zlib::Deflate.deflate(payload, Zlib::BEST_COMPRESSION)
end

encrypter.encrypt(Sandal::Json.dump(header), payload)
encrypter.encrypt(JSON.generate(header), payload)
end

# Decodes and validates a signed and/or encrypted JSON Web Token, recursing into any nested tokens, and returns the
Expand Down Expand Up @@ -213,16 +213,16 @@ def self.validate_signature(parts, signature, validator)

# Decodes the parts of a token.
def self.decode_token_parts(parts)
parts = parts.map { |part| Sandal::Util.jwt_base64_decode(part) }
parts[0] = Sandal::Json.load(parts[0])
parts = parts.map { |part| Sandal::Util.base64_decode(part) }
parts[0] = JSON.parse(parts[0])
parts
rescue
raise InvalidTokenError, "Invalid token encoding."
end

# Parses the content of a token and validates the claims if is JSON claims.
def self.parse_and_validate(payload, options)
claims = Sandal::Json.load(payload) rescue nil
claims = JSON.parse(payload) rescue nil
if claims
claims.extend(Sandal::Claims).validate_claims(options)
else
Expand Down
2 changes: 1 addition & 1 deletion lib/sandal/enc.rb
Expand Up @@ -12,7 +12,7 @@ module Enc
def self.token_parts(token)
parts = token.is_a?(Array) ? token : token.split(".")
raise ArgumentError unless parts.length == 5
decoded_parts = parts.map { |part| Sandal::Util.jwt_base64_decode(part) }
decoded_parts = parts.map { |part| Sandal::Util.base64_decode(part) }
return parts, decoded_parts
rescue ArgumentError
raise Sandal::InvalidTokenError, "Invalid token encoding."
Expand Down
4 changes: 2 additions & 2 deletions lib/sandal/enc/acbc_hs.rb
Expand Up @@ -43,13 +43,13 @@ def encrypt(header, payload)
cipher.iv = iv = SecureRandom.random_bytes(16)
ciphertext = cipher.update(payload) + cipher.final

auth_data = Sandal::Util.jwt_base64_encode(header)
auth_data = Sandal::Util.base64_encode(header)
auth_data_length = [auth_data.length * 8].pack("Q>")
mac_input = [auth_data, iv, ciphertext, auth_data_length].join
mac = OpenSSL::HMAC.digest(@digest, mac_key, mac_input)
auth_tag = mac[0...(mac.length / 2)]

remainder = [encrypted_key, iv, ciphertext, auth_tag].map { |part| Sandal::Util.jwt_base64_encode(part) }
remainder = [encrypted_key, iv, ciphertext, auth_tag].map { |part| Sandal::Util.base64_encode(part) }
[auth_data, *remainder].join(".")
end

Expand Down
4 changes: 2 additions & 2 deletions lib/sandal/enc/agcm.rb
Expand Up @@ -40,12 +40,12 @@ def encrypt(header, payload)
cipher.key = key
cipher.iv = iv = SecureRandom.random_bytes(@@iv_size / 8)

auth_data = Sandal::Util.jwt_base64_encode(header)
auth_data = Sandal::Util.base64_encode(header)
cipher.auth_data = auth_data

ciphertext = cipher.update(payload) + cipher.final
remaining_parts = [encrypted_key, iv, ciphertext, cipher.auth_tag(@@auth_tag_size / 8)]
remaining_parts.map! { |part| Sandal::Util.jwt_base64_encode(part) }
remaining_parts.map! { |part| Sandal::Util.base64_encode(part) }
[auth_data, *remaining_parts].join(".")
end

Expand Down
43 changes: 0 additions & 43 deletions lib/sandal/json.rb

This file was deleted.

2 changes: 1 addition & 1 deletion lib/sandal/sig/hs.rb
Expand Up @@ -35,7 +35,7 @@ def sign(payload)
# @param payload [String] The payload of the token.
# @return [Boolean] true if the signature is correct; otherwise false.
def valid?(signature, payload)
Sandal::Util.jwt_strings_equal?(sign(payload), signature)
Sandal::Util.strings_equal?(sign(payload), signature)
end

end
Expand Down
6 changes: 3 additions & 3 deletions lib/sandal/util.rb
Expand Up @@ -17,7 +17,7 @@ module Util
# @param a [String] The first string.
# @param b [String] The second string.
# @return [Boolean] true if the strings are equal; otherwise false.
def self.jwt_strings_equal?(a, b)
def self.strings_equal?(a, b)
return true if a.object_id == b.object_id
return false if a.nil? || b.nil? || a.length != b.length
a.codepoints.zip(b.codepoints).reduce(0) { |r, (x, y)| r |= x ^ y } == 0
Expand All @@ -27,7 +27,7 @@ def self.jwt_strings_equal?(a, b)
#
# @param s [String] The string to encode.
# @return [String] The encoded base64 string.
def self.jwt_base64_encode(s)
def self.base64_encode(s)
Base64.urlsafe_encode64(s).gsub(/=+$/, "")
end

Expand All @@ -36,7 +36,7 @@ def self.jwt_base64_encode(s)
# @param s [String] The base64 string to decode.
# @return [String] The decoded string.
# @raise [ArgumentError] The base64 string is invalid or contains padding.
def self.jwt_base64_decode(s)
def self.base64_decode(s)
if s.end_with?("=")
raise ArgumentError, "Base64 strings must not contain padding."
end
Expand Down
8 changes: 4 additions & 4 deletions spec/sandal/sig/es_spec.rb
Expand Up @@ -109,15 +109,15 @@ def make_point(group, x, y)
r = make_bn([14, 209, 33, 83, 121, 99, 108, 72, 60, 47, 127, 21, 88, 7, 212, 2, 163, 178, 40, 3, 58, 249, 124, 126, 23, 129, 154, 195, 22, 158, 166, 101])
s = make_bn([197, 10, 7, 211, 140, 60, 112, 229, 216, 241, 45, 175, 8, 74, 84, 128, 166, 101, 144, 197, 242, 147, 80, 154, 143, 63, 127, 138, 131, 163, 84, 213])
signature = Sandal::Sig::ES.encode_jws_signature(r, s, 256)
base64_signature = Sandal::Util.jwt_base64_encode(signature)
base64_signature = Sandal::Util.base64_encode(signature)
expect(base64_signature).to eq("DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q")
end

it "can encode the signature in JWS draft-11 appendix 4" do
r = make_bn([1, 220, 12, 129, 231, 171, 194, 209, 232, 135, 233, 117, 247, 105, 122, 210, 26, 125, 192, 1, 217, 21, 82, 91, 45, 240, 255, 83, 19, 34, 239, 71, 48, 157, 147, 152, 105, 18, 53, 108, 163, 214, 68, 231, 62, 153, 150, 106, 194, 164, 246, 72, 143, 138, 24, 50, 129, 223, 133, 206, 209, 172, 63, 237, 119, 109])
s = make_bn([0, 111, 6, 105, 44, 5, 41, 208, 128, 61, 152, 40, 92, 61, 152, 4, 150, 66, 60, 69, 247, 196, 170, 81, 193, 199, 78, 59, 194, 169, 16, 124, 9, 143, 42, 142, 131, 48, 206, 238, 34, 175, 83, 203, 220, 159, 3, 107, 155, 22, 27, 73, 111, 68, 68, 21, 238, 144, 229, 232, 148, 188, 222, 59, 242, 103])
signature = Sandal::Sig::ES.encode_jws_signature(r, s, 521)
base64_signature = Sandal::Util.jwt_base64_encode(signature)
base64_signature = Sandal::Util.base64_encode(signature)
expect(base64_signature).to eq("AdwMgeerwtHoh-l192l60hp9wAHZFVJbLfD_UxMi70cwnZOYaRI1bKPWROc-mZZqwqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8KpEHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn")
end

Expand All @@ -142,7 +142,7 @@ def make_point(group, x, y)
y = make_bn([199, 241, 68, 205, 27, 189, 155, 126, 135, 44, 223, 237, 185, 238, 185, 244, 179, 105, 93, 110, 169, 11, 36, 173, 138, 70, 35, 40, 133, 136, 229, 173])
d = make_bn([142, 155, 16, 158, 113, 144, 152, 191, 152, 4, 135, 223, 31, 93, 119, 233, 203, 41, 96, 110, 190, 210, 38, 59, 95, 87, 194, 19, 223, 132, 244, 178])
data = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
signature = Sandal::Util.jwt_base64_decode("DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q")
signature = Sandal::Util.base64_decode("DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q")

group = OpenSSL::PKey::EC::Group.new("prime256v1")
public_key = OpenSSL::PKey::EC.new(group)
Expand Down Expand Up @@ -184,7 +184,7 @@ def make_point(group, x, y)
y = make_bn([0, 52, 166, 68, 14, 55, 103, 80, 210, 55, 31, 209, 189, 194, 200, 243, 183, 29, 47, 78, 229, 234, 52, 50, 200, 21, 204, 163, 21, 96, 254, 93, 147, 135, 236, 119, 75, 85, 131, 134, 48, 229, 203, 191, 90, 140, 190, 10, 145, 221, 0, 100, 198, 153, 154, 31, 110, 110, 103, 250, 221, 237, 228, 200, 200, 246])
d = make_bn([1, 142, 105, 111, 176, 52, 80, 88, 129, 221, 17, 11, 72, 62, 184, 125, 50, 206, 73, 95, 227, 107, 55, 69, 237, 242, 216, 202, 228, 240, 242, 83, 159, 70, 21, 160, 233, 142, 171, 82, 179, 192, 197, 234, 196, 206, 7, 81, 133, 168, 231, 187, 71, 222, 172, 29, 29, 231, 123, 204, 246, 97, 53, 230, 61, 130] )
data = "eyJhbGciOiJFUzUxMiJ9.UGF5bG9hZA"
signature = Sandal::Util.jwt_base64_decode("AdwMgeerwtHoh-l192l60hp9wAHZFVJbLfD_UxMi70cwnZOYaRI1bKPWROc-mZZqwqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8KpEHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn")
signature = Sandal::Util.base64_decode("AdwMgeerwtHoh-l192l60hp9wAHZFVJbLfD_UxMi70cwnZOYaRI1bKPWROc-mZZqwqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8KpEHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn")

group = OpenSSL::PKey::EC::Group.new("secp521r1")
public_key = OpenSSL::PKey::EC.new(group)
Expand Down
32 changes: 16 additions & 16 deletions spec/sandal/util_spec.rb
Expand Up @@ -4,62 +4,62 @@

describe Sandal::Util do

context '#jwt_base64_decode' do
context '#base64_decode' do

it 'decodes base64 as per JWT example 6.1' do
encoded = 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ'
val = Sandal::Util.jwt_base64_decode(encoded)
val = Sandal::Util.base64_decode(encoded)
expect(val).to eq(%!{"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}!)
end

it 'raises an ArgumentError if base64 strings contain padding' do
expect { Sandal::Util.jwt_base64_decode('eyJpc3MiOiJq=') }.to raise_error ArgumentError
expect { Sandal::Util.base64_decode('eyJpc3MiOiJq=') }.to raise_error ArgumentError
end

it 'raises an ArgumentError if base64 strings are invalid' do
expect { Sandal::Util.jwt_base64_decode('not valid base64') }.to raise_error ArgumentError
expect { Sandal::Util.base64_decode('not valid base64') }.to raise_error ArgumentError
end

end

context '#jwt_base64_encode' do
context '#base64_encode' do

it 'encodes base64 as per JWT example 6.1' do
src = %!{"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}!
encoded = Sandal::Util.jwt_base64_encode(src)
encoded = Sandal::Util.base64_encode(src)
expect(encoded).to eq('eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ')
end

end

context '#jwt_strings_equal?' do
context '#strings_equal?' do

it 'compares nil strings as equal' do
expect(Sandal::Util.jwt_strings_equal?(nil, nil)).to eq(true)
expect(Sandal::Util.strings_equal?(nil, nil)).to eq(true)
end

it 'compares empty strings as equal' do
expect(Sandal::Util.jwt_strings_equal?('', '')).to eq(true)
expect(Sandal::Util.strings_equal?('', '')).to eq(true)
end

it 'compares nil strings as unequal to empty strings' do
expect(Sandal::Util.jwt_strings_equal?(nil, '')).to eq(false)
expect(Sandal::Util.jwt_strings_equal?('', nil)).to eq(false)
expect(Sandal::Util.strings_equal?(nil, '')).to eq(false)
expect(Sandal::Util.strings_equal?('', nil)).to eq(false)
end

it 'compares equal strings as equal' do
expect(Sandal::Util.jwt_strings_equal?('hello', 'hello')).to eq(true)
expect(Sandal::Util.jwt_strings_equal?('a longer string', 'a longer string')).to eq(true)
expect(Sandal::Util.strings_equal?('hello', 'hello')).to eq(true)
expect(Sandal::Util.strings_equal?('a longer string', 'a longer string')).to eq(true)
end

it 'compares unequal strings as unequal' do
expect(Sandal::Util.jwt_strings_equal?('hello', 'world')).to eq(false)
expect(Sandal::Util.jwt_strings_equal?('a longer string', 'a different longer string')).to eq(false)
expect(Sandal::Util.strings_equal?('hello', 'world')).to eq(false)
expect(Sandal::Util.strings_equal?('a longer string', 'a different longer string')).to eq(false)
end

it 'compares strings without short-circuiting', :timing_dependent do
measure_equals = -> a, b do
Benchmark.realtime { 100.times { Sandal::Util.jwt_strings_equal?(a, b) } }
Benchmark.realtime { 100.times { Sandal::Util.strings_equal?(a, b) } }
end
ref = 'a' * 10000
cmp1 = ('a' * 9999) + 'b'
Expand Down

0 comments on commit b826d4e

Please sign in to comment.