Class: GlobalSession::Session::V3
- Defined in:
- lib/global_session/session/v3.rb
Overview
Global session V3 uses JSON serialization, no compression, and a detached signature that is excluded from the JSON structure for efficiency reasons.
The binary structure of a V3 session looks like this:
<utf8_json><0x00><binary_signature>
Its JSON structure is an Array with the following format:
[<version_integer>,
<uuid_string>,
<>,
<>,
<>,
{<signed_data_hash>},
{<unsigned_data_hash>}]
The design goal of V3 is to ensure broad compatibility across various programming languages and cryptographic libraries, and to create a serialization format that can be reused for future versions. To this end, it sacrifices space efficiency by switching back to JSON encoding (instead of msgpack), and uses the undocumented OpenSSL::PKey#sign and #verify operations which rely on the PKCS7-compliant OpenSSL EVP API.
Constant Summary collapse
- STRING_ENCODING =
!!(RUBY_VERSION !~ /1.8/)
Instance Attribute Summary
Attributes inherited from Abstract
#authority, #created_at, #directory, #expired_at, #id, #insecure, #signed
Class Method Summary collapse
-
.decode_cookie(cookie) ⇒ Object
Utility method to decode a cookie; good for console debugging.
-
.join_body(json, signature) ⇒ String
Join a UTF-8 JSON document and an ASCII-8bit binary string.
-
.split_body(input) ⇒ Array
Split an ASCII-8bit input string into two constituent parts: a UTF-8 JSON document and an ASCII-8bit binary string.
Instance Method Summary collapse
-
#[](key) ⇒ Object
Lookup a value by its key.
-
#[]=(key, value) ⇒ Object
Set a value in the global session hash.
-
#delete(key) ⇒ Object
Delete a key from the global session attributes.
-
#dirty? ⇒ Boolean
Determine whether any state has changed since the session was loaded.
-
#each_pair(&block) ⇒ Object
Iterate over each key/value pair.
-
#keys ⇒ Object
Return the keys that are currently present in the global session.
-
#signature_digest ⇒ Object
Return the SHA1 hash of the most recently-computed RSA signature of this session.
-
#to_s ⇒ String
Serialize the session to a form suitable for use with HTTP cookies.
-
#values ⇒ Object
Return the values that are currently present in the global session.
Methods inherited from Abstract
#has_key?, #initialize, #inspect, #invalidate!, #new_record?, #renew!, #supports_key?, #to_h, #valid?
Constructor Details
This class inherits a constructor from GlobalSession::Session::Abstract
Class Method Details
.decode_cookie(cookie) ⇒ Object
Utility method to decode a cookie; good for console debugging. This performs no validation or security check of any sort.
Parameters
- cookie(String)
-
well-formed global session cookie
54 55 56 57 58 |
# File 'lib/global_session/session/v3.rb', line 54 def self.() bin = GlobalSession::Encoding::Base64Cookie.load() json, sig = split_body(bin) return GlobalSession::Encoding::JSON.load(json), sig end |
.join_body(json, signature) ⇒ String
Join a UTF-8 JSON document and an ASCII-8bit binary string.
This is an implementation helper for GlobalSession serialization and not useful for the public at large. It’s left public as an aid for those who want to hack sessions.
96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/global_session/session/v3.rb', line 96 def self.join_body(json, signature) result = "" if STRING_ENCODING result.force_encoding(Encoding::ASCII_8BIT) json.force_encoding(Encoding::ASCII_8BIT) signature.force_encoding(Encoding::ASCII_8BIT) end result << json result << "\x00" result << signature result end |
.split_body(input) ⇒ Array
Split an ASCII-8bit input string into two constituent parts: a UTF-8 JSON document and an ASCII-8bit binary string. A null (0x00) separator character is presumed to separate the two parts of the input string.
This is an implementation helper for GlobalSession serialization and not useful for the public at large. It’s left public as an aid for those who want to hack sessions.
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/global_session/session/v3.rb', line 70 def self.split_body(input) input.force_encoding(Encoding::ASCII_8BIT) if STRING_ENCODING null_at = input.index("\x00") if null_at json = input[0...null_at] sig = input[null_at+1..-1] if STRING_ENCODING json.force_encoding(Encoding::UTF_8) sig.force_encoding(Encoding::ASCII_8BIT) end return json, sig else raise ArgumentError, "Malformed input string does not contain 0x00 byte" end end |
Instance Method Details
#[](key) ⇒ Object
Lookup a value by its key.
Parameters
- key(String)
-
the key
Return
- value(Object)
-
The value associated with
key, or nil ifkeyis not present
225 226 227 228 |
# File 'lib/global_session/session/v3.rb', line 225 def [](key) key = key.to_s #take care of symbol-style keys @signed[key] || @insecure[key] end |
#[]=(key, value) ⇒ Object
Set a value in the global session hash. If the supplied key is denoted as secure by the global session schema, causes a new signature to be computed when the session is next serialized.
Parameters
- key(String)
-
The key to set
- value(Object)
-
The value to set
Return
- value(Object)
-
Always returns the value that was set
Raise
- InvalidSession
-
if the session has been invalidated (and therefore can’t be written to)
- ArgumentError
-
if the configuration doesn’t define the specified key as part of the global session
- NoAuthority
-
if the specified key is secure and the local node is not an authority
- UnserializableType
-
if the specified value can’t be serialized as JSON
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/global_session/session/v3.rb', line 246 def []=(key, value) key = key.to_s #take care of symbol-style keys raise GlobalSession::InvalidSession unless valid? if @schema_signed.include?(key) @signed[key] = value @dirty_secure = true elsif @schema_insecure.include?(key) @insecure[key] = value @dirty_insecure = true else raise ArgumentError, "Attribute '#{key}' is not specified in global session configuration" end return value end |
#delete(key) ⇒ Object
Delete a key from the global session attributes. If the key exists, mark the global session dirty
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/global_session/session/v3.rb', line 115 def delete(key) key = key.to_s #take care of symbol-style keys raise GlobalSession::InvalidSession unless valid? if @schema_signed.include?(key) # Only mark dirty if the key actually exists @dirty_secure = true if @signed.keys.include? key value = @signed.delete(key) elsif @schema_insecure.include?(key) # Only mark dirty if the key actually exists @dirty_insecure = true if @insecure.keys.include? key value = @insecure.delete(key) else raise ArgumentError, "Attribute '#{key}' is not specified in global session configuration" end return value end |
#dirty? ⇒ Boolean
Determine whether any state has changed since the session was loaded.
186 187 188 |
# File 'lib/global_session/session/v3.rb', line 186 def dirty? !!(super || @dirty_secure || @dirty_insecure) end |
#each_pair(&block) ⇒ Object
Iterate over each key/value pair
Block
An iterator which will be called with each key/value pair
Return
Returns the value of the last expression evaluated by the block
213 214 215 216 |
# File 'lib/global_session/session/v3.rb', line 213 def each_pair(&block) # :yields: |key, value| @signed.each_pair(&block) @insecure.each_pair(&block) end |
#keys ⇒ Object
Return the keys that are currently present in the global session.
Return
- keys(Array)
-
List of keys contained in the global session
194 195 196 |
# File 'lib/global_session/session/v3.rb', line 194 def keys @signed.keys + @insecure.keys end |
#signature_digest ⇒ Object
Return the SHA1 hash of the most recently-computed RSA signature of this session. This isn’t really intended for the end user; it exists so the Web framework integration code can optimize request speed by caching the most recently verified signature in the local session and avoid re-verifying it on every request.
Return
- digest(String)
-
SHA1 hex-digest of most-recently-computed signature
271 272 273 |
# File 'lib/global_session/session/v3.rb', line 271 def signature_digest @signature ? digest(@signature) : nil end |
#to_s ⇒ String
Serialize the session to a form suitable for use with HTTP cookies. If any secure attributes have changed since the session was instantiated, compute a fresh RSA signature.
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/global_session/session/v3.rb', line 143 def to_s if && !dirty? #use cached cookie if nothing has changed return end unless serializable?(@signed) && serializable?(@insecure) raise GlobalSession::UnserializableType, "Attributes hash contains non-String keys, cannot be cleanly marshalled" end hash = {'v' => 3, 'id' => @id, 'a' => , 'tc' => @created_at.to_i, 'te' => @expired_at.to_i, 'ds' => @signed} if @signature && !dirty? #use cached signature unless we've changed secure state = else = @directory. hash['a'] = signed_hash = RightSupport::Crypto::SignedHash.new( hash, :envelope=>true, :encoding=>GlobalSession::Encoding::JSON, :private_key=>@directory.private_key) @signature = signed_hash.sign(@expired_at) end hash['dx'] = @insecure hash['a'] = array = attribute_hash_to_array(hash) json = GlobalSession::Encoding::JSON.dump(array) bin = self.class.join_body(json, @signature) return GlobalSession::Encoding::Base64Cookie.dump(bin) end |
#values ⇒ Object
Return the values that are currently present in the global session.
Return
- values(Array)
-
List of values contained in the global session
202 203 204 |
# File 'lib/global_session/session/v3.rb', line 202 def values @signed.values + @insecure.values end |