lib/global_session/session/v3.rb in global_session-3.2.10 vs lib/global_session/session/v3.rb in global_session-3.3.0

- old
+ new

@@ -20,11 +20,11 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # Standard library dependencies require 'set' -# Cryptographical Hash +# SignedHash, which encapsulates the crypto bit of global sessions require 'right_support/crypto' module GlobalSession::Session # Global session V3 uses JSON serialization, no compression, and a detached signature that is # excluded from the JSON structure for efficiency reasons. @@ -45,10 +45,13 @@ # 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. class V3 < Abstract + # Pattern that matches strings that are probably a V3 session cookie. + HEADER = /^WzM/ + STRING_ENCODING = !!(RUBY_VERSION !~ /1.8/) # Utility method to decode a cookie; good for console debugging. This performs no # validation or security check of any sort. # @@ -108,37 +111,10 @@ result << "\x00" result << signature result end - # Delete a key from the global session attributes. If the key exists, - # mark the global session dirty - # - # @param [String] the key to delete - # @return [Object] the value of the key deleted, or nil if not found - def delete(key) - key = key.to_s #take care of symbol-style keys - raise GlobalSession::InvalidSession unless valid? - - if @schema_signed.include?(key) - authority_check - - # 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 - # 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. # # @return [String] a B64cookie-encoded JSON-serialized global session @@ -166,13 +142,13 @@ authority_check authority = @directory.local_authority_name hash['a'] = authority signed_hash = RightSupport::Crypto::SignedHash.new( hash, - :envelope=>true, - :encoding=>GlobalSession::Encoding::JSON, - :private_key=>@directory.private_key) + @directory.private_key, + envelope: true, + encoding: GlobalSession::Encoding::JSON) @signature = signed_hash.sign(@expired_at) end hash['dx'] = @insecure hash['a'] = authority @@ -181,91 +157,10 @@ json = GlobalSession::Encoding::JSON.dump(array) bin = self.class.join_body(json, @signature) return GlobalSession::Encoding::Base64Cookie.dump(bin) end - # Determine whether any state has changed since the session was loaded. - # - # @return [Boolean] true if something has changed - def dirty? - !!(super || @dirty_secure || @dirty_insecure) - end - - # Return the keys that are currently present in the global session. - # - # === Return - # keys(Array):: List of keys contained in the global session - def keys - @signed.keys + @insecure.keys - end - - # Return the values that are currently present in the global session. - # - # === Return - # values(Array):: List of values contained in the global session - def values - @signed.values + @insecure.values - end - - # 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 - def each_pair(&block) # :yields: |key, value| - @signed.each_pair(&block) - @insecure.each_pair(&block) - end - - # Lookup a value by its key. - # - # === Parameters - # key(String):: the key - # - # === Return - # value(Object):: The value associated with +key+, or nil if +key+ is not present - def [](key) - key = key.to_s #take care of symbol-style keys - @signed[key] || @insecure[key] - end - - # 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 - def []=(key, value) - key = key.to_s #take care of symbol-style keys - raise GlobalSession::InvalidSession unless valid? - - if @schema_signed.include?(key) - authority_check - @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 - # 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. # @@ -275,20 +170,10 @@ @signature ? digest(@signature) : nil end private - # This is called by #clone and is used to augment the shallow clone behavior - # - # @return [Object] this global session object which doesn't reference the - # the hashes from the original object - def initialize_copy(source) - super - @signed = ::RightSupport::Data::HashTools.deep_clone2(@signed) - @insecure = ::RightSupport::Data::HashTools.deep_clone2(@insecure) - end - def load_from_cookie(cookie) # :nodoc: hash = nil begin array, signature = self.class.decode_cookie(cookie) @@ -309,24 +194,24 @@ #Check trust in signing authority if @directory.trusted_authority?(authority) signed_hash = RightSupport::Crypto::SignedHash.new( hash, + @directory.authorities[authority], :envelope=>true, - :encoding=>GlobalSession::Encoding::JSON, - :public_key=>@directory.authorities[authority]) + :encoding=>GlobalSession::Encoding::JSON) begin signed_hash.verify!(signature, expired_at) rescue RightSupport::Crypto::ExpiredSignature raise GlobalSession::ExpiredSession, "Session expired at #{expired_at}" rescue RightSupport::Crypto::InvalidSignature => e - raise SecurityError, "Global session signature verification failed: " + e.message + raise GlobalSession::InvalidSignature, "Global session signature verification failed: " + e.message end else - raise SecurityError, "Global sessions signed by #{authority.inspect} are not trusted" + raise GlobalSession::InvalidSignature, "Global sessions signed by #{authority.inspect} are not trusted" end #Check expiration unless expired_at > Time.now.utc raise GlobalSession::ExpiredSession, "Session expired at #{expired_at}" @@ -347,26 +232,15 @@ @signature = signature @cookie = cookie end def create_from_scratch # :nodoc: - authority_check - @signed = {} @insecure = {} @created_at = Time.now.utc @authority = @directory.local_authority_name @id = RightSupport::Data::UUID.generate renew! - end - - def create_invalid # :nodoc: - @id = nil - @created_at = Time.now.utc - @expired_at = created_at - @signed = {} - @insecure = {} - @authority = nil end # Transform a V1-style attribute hash to an Array with fixed placement for # each element. The V3 scheme is serialized as an array to save space. #