/storage/session/strategy/Hmac.php

https://github.com/niel/lithium · PHP · 155 lines · 51 code · 17 blank · 87 comment · 3 complexity · af807a511d53f4c7eac10513fdf8f284 MD5 · raw file

  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\storage\session\strategy;
  9. use RuntimeException;
  10. use lithium\core\ConfigException;
  11. use lithium\storage\session\strategy\MissingSignatureException;
  12. /**
  13. * This strategy allows you to sign your `Session` and / or `Cookie` data with a passphrase
  14. * to ensure that it has not been tampered with.
  15. *
  16. * Example configuration:
  17. *
  18. * {{{
  19. * Session::config(array('default' => array(
  20. * 'adapter' => 'Cookie',
  21. * 'strategies' => array('Hmac' => array('secret' => 'foobar'))
  22. * )));
  23. * }}}
  24. *
  25. * This will configure the `HMAC` strategy to be used for all `Session` operations with the
  26. * `default` named configuration. A hash-based message authentication code (HMAC) will be
  27. * calculated for all data stored in your cookies, and will be compared to the signature
  28. * stored in your cookie data. If the two do not match, then your data has been tampered with
  29. * (or you have modified the data directly _without_ passing through the `Session` class, which
  30. * amounts to the same), then a catchable `RuntimeException` is thrown.
  31. *
  32. * Please note that this strategy is very finnicky, and is so by design. If you attempt to access
  33. * or modify the stored data in any way other than through the `Session` class configured with the
  34. * `Hmac` strategy with the properly configured `secret`, then it will probably blow up.
  35. *
  36. * @link http://en.wikipedia.org/wiki/HMAC Wikipedia: Hash-based Message Authentication Code
  37. */
  38. class Hmac extends \lithium\core\Object {
  39. /**
  40. * The HMAC secret.
  41. *
  42. * @var string HMAC secret string.
  43. */
  44. protected static $_secret = null;
  45. /**
  46. * Constructor.
  47. *
  48. * @param array $config Configuration array. Will throw an exception if the 'secret'
  49. * configuration key is not set.
  50. */
  51. public function __construct(array $config = array()) {
  52. if (!isset($config['secret'])) {
  53. throw new ConfigException("HMAC strategy requires a secret key.");
  54. }
  55. static::$_secret = $config['secret'];
  56. }
  57. /**
  58. * Write strategy method.
  59. * Adds an HMAC signature to the data. Note that this will transform the
  60. * passed `$data` to an array, and add a `__signature` key with the HMAC-caculated
  61. * value.
  62. *
  63. * @see lithium\storage\Session
  64. * @see lithium\core\Adaptable::config()
  65. * @link http://php.net/manual/en/function.hash-hmac.php PHP Manual: hash_hmac()
  66. * @param mixed $data The data to be signed.
  67. * @param array $options Options for this method.
  68. * @return array Data & signature.
  69. */
  70. public function write($data, array $options = array()) {
  71. $class = $options['class'];
  72. $futureData = $class::read(null, array('strategies' => false));
  73. $futureData = array($options['key'] => $data) + $futureData;
  74. unset($futureData['__signature']);
  75. $signature = static::_signature($futureData);
  76. $class::write('__signature', $signature, array('strategies' => false) + $options);
  77. return $data;
  78. }
  79. /**
  80. * Read strategy method.
  81. * Validates the HMAC signature of the stored data. If the signatures match, then
  82. * the data is safe, and the 'valid' key in the returned data will be
  83. *
  84. * If the store being read does not contain a `__signature` field, a `MissingSignatureException`
  85. * is thrown. When catching this exception, you may choose to handle it by either writing
  86. * out a signature (e.g. in cases where you know that no pre-existing signature may exist), or
  87. * you can blackhole it as a possible tampering attempt.
  88. *
  89. * @param array $data the Data being read.
  90. * @param array $options Options for this method.
  91. * @return array validated data
  92. */
  93. public function read($data, array $options = array()) {
  94. $class = $options['class'];
  95. $currentData = $class::read(null, array('strategies' => false));
  96. if (!isset($currentData['__signature'])) {
  97. throw new MissingSignatureException('HMAC signature not found.');
  98. }
  99. $currentSignature = $currentData['__signature'];
  100. $signature = static::_signature($currentData);
  101. if ($signature !== $currentSignature) {
  102. $message = "Possible data tampering: HMAC signature does not match data.";
  103. throw new RuntimeException($message);
  104. }
  105. return $data;
  106. }
  107. /**
  108. * Delete strategy method.
  109. *
  110. * @see lithium\storage\Session
  111. * @see lithium\core\Adaptable::config()
  112. * @link http://php.net/manual/en/function.hash-hmac.php PHP Manual: hash_hmac()
  113. * @param mixed $data The data to be signed.
  114. * @param array $options Options for this method.
  115. * @return array Data & signature.
  116. */
  117. public function delete($data, array $options = array()) {
  118. $class = $options['class'];
  119. $futureData = $class::read(null, array('strategies' => false));
  120. unset($futureData[$options['key']]);
  121. $signature = static::_signature($futureData);
  122. $class::write('__signature', $signature, array('strategies' => false) + $options);
  123. return $data;
  124. }
  125. /**
  126. * Calculate the HMAC signature based on the data and a secret key.
  127. *
  128. * @param mixed $data
  129. * @param null|string $secret Secret key for HMAC signature creation.
  130. * @return string HMAC signature.
  131. */
  132. protected static function _signature($data, $secret = null) {
  133. unset($data['__signature']);
  134. $secret = ($secret) ?: static::$_secret;
  135. return hash_hmac('sha1', serialize($data), $secret);
  136. }
  137. }
  138. ?>