lithium\storage\session\strategy\Hmac

class

This strategy allows you to sign your Session and / or Cookie data with a passphrase to ensure that it has not been tampered with.

Example configuration:

Session::config(['default' => [
   'adapter' => 'Cookie',
   'strategies' => ['Hmac' => ['secret' => 'foobar']]
]]);

This will configure the HMAC strategy to be used for all Session operations with the default named configuration. A hash-based message authentication code (HMAC) will be calculated for all data stored in your cookies, and will be compared to the signature stored in your cookie data. If the two do not match, then your data has been tampered with (or you have modified the data directly without passing through the Session class, which amounts to the same), then a catchable RuntimeException is thrown.

Please note that this strategy is very finnicky, and is so by design. If you attempt to access or modify the stored data in any way other than through the Session class configured with the Hmac strategy with the properly configured secret, then it will probably blow up.

Source

class Hmac extends \lithium\core\Object {

	/**
	 * The HMAC secret.
	 *
	 * @var string HMAC secret string.
	 */
	protected static $_secret = null;

	/**
	 * Constructor.
	 *
	 * @param array $config Configuration array. Will throw an exception if the 'secret'
	 *        configuration key is not set.
	 * @return void
	 */
	public function __construct(array $config = []) {
		if (!isset($config['secret'])) {
			throw new ConfigException("HMAC strategy requires a secret key.");
		}
		static::$_secret = $config['secret'];
	}

	/**
	 * Write strategy method.
	 *
	 * Adds an HMAC signature to the data. Note that this will transform the
	 * passed `$data` to an array, and add a `__signature` key with the HMAC-calculated
	 * value.
	 *
	 * @see lithium\storage\Session
	 * @see lithium\core\Adaptable::config()
	 * @link http://php.net/function.hash-hmac.php PHP Manual: hash_hmac()
	 * @param mixed $data The data to be signed.
	 * @param array $options Options for this method.
	 * @return array Data & signature.
	 */
	public function write($data, array $options = []) {
		$class = $options['class'];

		$futureData = $class::read(null, ['strategies' => false]);
		$futureData = [$options['key'] => $data] + $futureData;
		unset($futureData['__signature']);

		$signature = static::_signature($futureData);
		$class::write('__signature', $signature, ['strategies' => false] + $options);
		return $data;
	}

	/**
	 * Read strategy method.
	 *
	 * Validates the HMAC signature of the stored data. If the signatures match, then the data
	 * is safe and will be passed through as-is.
	 *
	 * If the stored data being read does not contain a `__signature` field, a
	 * `MissingSignatureException` is thrown. When catching this exception, you may choose
	 * to handle it by either writing out a signature (e.g. in cases where you know that no
	 * pre-existing signature may exist), or you can blackhole it as a possible tampering
	 * attempt.
	 *
	 * @throws RuntimeException On possible data tampering.
	 * @throws lithium\storage\session\strategy\MissingSignatureException On missing singature.
	 * @param array $data The data being read.
	 * @param array $options Options for this method.
	 * @return array Validated data.
	 */
	public function read($data, array $options = []) {
		if ($data === null) {
			return $data;
		}
		$class = $options['class'];

		$currentData = $class::read(null, ['strategies' => false]);

		if (!isset($currentData['__signature'])) {
			throw new MissingSignatureException('HMAC signature not found.');
		}
		if (Hash::compare($currentData['__signature'], static::_signature($currentData))) {
			return $data;
		}
		throw new RuntimeException('Possible data tampering: HMAC signature does not match data.');
	}

	/**
	 * Delete strategy method.
	 *
	 * @see lithium\storage\Session
	 * @see lithium\core\Adaptable::config()
	 * @link http://php.net/function.hash-hmac.php PHP Manual: hash_hmac()
	 * @param mixed $data The data to be signed.
	 * @param array $options Options for this method.
	 * @return array Data & signature.
	 */
	public function delete($data, array $options = []) {
		$class = $options['class'];

		$futureData = $class::read(null, ['strategies' => false]);
		unset($futureData[$options['key']]);

		$signature = static::_signature($futureData);
		$class::write('__signature', $signature, ['strategies' => false] + $options);
		return $data;
	}

	/**
	 * Calculate the HMAC signature based on the data and a secret key.
	 *
	 * @param mixed $data
	 * @param null|string $secret Secret key for HMAC signature creation.
	 * @return string HMAC signature.
	 */
	protected static function _signature($data, $secret = null) {
		unset($data['__signature']);
		$secret = ($secret) ?: static::$_secret;
		return hash_hmac('sha1', serialize($data), $secret);
	}
}