From b826d4e95031eb0762e24dc5522f1b38932ba532 Mon Sep 17 00:00:00 2001 From: Greg Beech Date: Sun, 5 Oct 2014 16:53:32 +0100 Subject: [PATCH] Clean up JSON and util methods --- CHANGELOG.md | 9 ++++++++ lib/sandal.rb | 18 ++++++++-------- lib/sandal/enc.rb | 2 +- lib/sandal/enc/acbc_hs.rb | 4 ++-- lib/sandal/enc/agcm.rb | 4 ++-- lib/sandal/json.rb | 43 -------------------------------------- lib/sandal/sig/hs.rb | 2 +- lib/sandal/util.rb | 6 +++--- spec/sandal/sig/es_spec.rb | 8 +++---- spec/sandal/util_spec.rb | 32 ++++++++++++++-------------- 10 files changed, 47 insertions(+), 81 deletions(-) delete mode 100644 lib/sandal/json.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index b907ecd..b72fee7 100644 --- a/CHANGELOG.md +++ b/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: diff --git a/lib/sandal.rb b/lib/sandal.rb index 11b51f2..2760642 100644 --- a/lib/sandal.rb +++ b/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" @@ -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. @@ -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 @@ -213,8 +213,8 @@ 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." @@ -222,7 +222,7 @@ def self.decode_token_parts(parts) # 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 diff --git a/lib/sandal/enc.rb b/lib/sandal/enc.rb index 9b8c24e..0a3ec11 100644 --- a/lib/sandal/enc.rb +++ b/lib/sandal/enc.rb @@ -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." diff --git a/lib/sandal/enc/acbc_hs.rb b/lib/sandal/enc/acbc_hs.rb index 6c35ada..b873aaf 100644 --- a/lib/sandal/enc/acbc_hs.rb +++ b/lib/sandal/enc/acbc_hs.rb @@ -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 diff --git a/lib/sandal/enc/agcm.rb b/lib/sandal/enc/agcm.rb index 5834a20..a206012 100644 --- a/lib/sandal/enc/agcm.rb +++ b/lib/sandal/enc/agcm.rb @@ -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 diff --git a/lib/sandal/json.rb b/lib/sandal/json.rb deleted file mode 100644 index 72a1aa8..0000000 --- a/lib/sandal/json.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Sandal - # Contains JSON encode and decode functionality. - module Json - if !defined?(MultiJson) - require 'json' - - # Decode a JSON string into Ruby. This version delegates to the included JSON engine. - # - # @param encoded [String] The JSON string representation of the object. - # @return The decoded Ruby object. - def self.load(encoded) - JSON.parse(encoded) - end - - # Encodes a Ruby object as JSON. This version delegates to the included JSON engine. - # - # @param raw The Ruby object to be encoded - # @return [String] The JSON string representation of the object. - def self.dump(raw) - JSON.generate(raw) - end - - else - require 'multi_json' - - # Decode a JSON string into Ruby. This version delegates to MultiJson. - # - # @param encoded [String] The JSON string representation of the object. - # @return The decoded Ruby object. - def self.load(encoded) - MultiJson.load(encoded) - end - - # Encodes a Ruby object as JSON. This version delegates to MultiJson. - # - # @param raw The Ruby object to be encoded - # @return [String] The JSON string representation of the object. - def self.dump(raw) - MultiJson.dump(raw) - end - end - end -end \ No newline at end of file diff --git a/lib/sandal/sig/hs.rb b/lib/sandal/sig/hs.rb index 07dd750..950aac7 100644 --- a/lib/sandal/sig/hs.rb +++ b/lib/sandal/sig/hs.rb @@ -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 diff --git a/lib/sandal/util.rb b/lib/sandal/util.rb index 5bfd414..02d1f6d 100644 --- a/lib/sandal/util.rb +++ b/lib/sandal/util.rb @@ -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 @@ -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 @@ -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 diff --git a/spec/sandal/sig/es_spec.rb b/spec/sandal/sig/es_spec.rb index 948cf80..9d69863 100644 --- a/spec/sandal/sig/es_spec.rb +++ b/spec/sandal/sig/es_spec.rb @@ -109,7 +109,7 @@ 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 @@ -117,7 +117,7 @@ def make_point(group, x, y) 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 @@ -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) @@ -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) diff --git a/spec/sandal/util_spec.rb b/spec/sandal/util_spec.rb index 39e0d0c..fc188cd 100644 --- a/spec/sandal/util_spec.rb +++ b/spec/sandal/util_spec.rb @@ -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'