forked from yiisoft/cookies
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix yiisoft#4: Add CookieEncryptor and CookieSigner classes
- Loading branch information
Showing
7 changed files
with
528 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,11 @@ | ||
# Yii Cookies Change Log | ||
|
||
|
||
## 1.0.1 under development | ||
## 1.1.0 under development | ||
|
||
- no changes in this release. | ||
- Add #19: Add the `Yiisoft\Cookies\CookieEncryptor` class to encrypt the value of the cookie and verify that it is tampered (devanych) | ||
- Add #19: Add the `Yiisoft\Cookies\CookieSigner` class to sign the value of the cookie and verify that it is tampered (devanych) | ||
|
||
## 1.0.0 December 02, 2020 | ||
|
||
|
||
- Initial release. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Yiisoft\Cookies; | ||
|
||
use RuntimeException; | ||
use Yiisoft\Security\AuthenticationException; | ||
use Yiisoft\Security\Crypt; | ||
|
||
use function md5; | ||
use function rawurldecode; | ||
use function rawurlencode; | ||
use function strlen; | ||
use function strpos; | ||
use function substr; | ||
|
||
/** | ||
* A CookieEncryptor encrypts the cookie value and validates whether the encrypted cookie value has been tampered with. | ||
* | ||
* @see Cookie | ||
*/ | ||
final class CookieEncryptor | ||
{ | ||
/** | ||
* @var Crypt The Crypt instance. | ||
*/ | ||
private Crypt $crypt; | ||
|
||
/** | ||
* @var string The secret key used to encrypt and decrypt cookie values. | ||
*/ | ||
private string $key; | ||
|
||
/** | ||
* @param string $key The secret key used to encrypt and decrypt cookie values. | ||
*/ | ||
public function __construct(string $key) | ||
{ | ||
$this->crypt = new Crypt(); | ||
$this->key = $key; | ||
} | ||
|
||
/** | ||
* Returns a new cookie instance with the encrypted cookie value. | ||
* | ||
* @param Cookie $cookie The cookie with clean value. | ||
* | ||
* @throws RuntimeException If the cookie value is already encrypted. | ||
* | ||
* @return Cookie The cookie with encrypted value. | ||
*/ | ||
public function encrypt(Cookie $cookie): Cookie | ||
{ | ||
if ($this->isEncrypted($cookie)) { | ||
throw new RuntimeException("The \"{$cookie->getName()}\" cookie value is already encrypted."); | ||
} | ||
|
||
$value = $this->crypt->encryptByKey($cookie->getValue(), $this->key, $cookie->getName()); | ||
return $cookie->withValue($this->prefix($cookie) . rawurlencode($value)); | ||
} | ||
|
||
/** | ||
* Returns a new cookie instance with the decrypted cookie value. | ||
* | ||
* @param Cookie $cookie The cookie with encrypted value. | ||
* | ||
* @throws RuntimeException If the cookie value is tampered with or not validly encrypted. If you are not sure | ||
* that the value of the cookie file was encrypted earlier, then first use the {@see isEncrypted()}. | ||
* | ||
* @return Cookie The cookie with decrypted value. | ||
*/ | ||
public function decrypt(Cookie $cookie): Cookie | ||
{ | ||
if (!$this->isEncrypted($cookie)) { | ||
throw new RuntimeException("The \"{$cookie->getName()}\" cookie value is not validly encrypted."); | ||
} | ||
|
||
try { | ||
$value = rawurldecode(substr($cookie->getValue(), 32)); | ||
return $cookie->withValue($this->crypt->decryptByKey($value, $this->key, $cookie->getName())); | ||
} catch (AuthenticationException $e) { | ||
throw new RuntimeException("The \"{$cookie->getName()}\" cookie value was tampered with."); | ||
} | ||
} | ||
|
||
/** | ||
* Checks whether the cookie value is validly encrypted. | ||
* | ||
* @param Cookie $cookie The cookie to check. | ||
* | ||
* @return bool Whether the cookie value is validly encrypted. | ||
*/ | ||
public function isEncrypted(Cookie $cookie): bool | ||
{ | ||
return strlen($cookie->getValue()) > 32 && strpos($cookie->getValue(), $this->prefix($cookie)) === 0; | ||
} | ||
|
||
/** | ||
* Returns a prefix for cookie. | ||
* | ||
* @param Cookie $cookie The cookie to prefix. | ||
* | ||
* @return string The prefix for cookie. | ||
*/ | ||
private function prefix(Cookie $cookie): string | ||
{ | ||
return md5(self::class . $cookie->getName()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Yiisoft\Cookies; | ||
|
||
use RuntimeException; | ||
use Yiisoft\Security\DataIsTamperedException; | ||
use Yiisoft\Security\Mac; | ||
|
||
use function md5; | ||
use function strpos; | ||
use function strlen; | ||
use function substr; | ||
|
||
/** | ||
* A CookieSigner signs the cookie value and validates whether the signed cookie value has been tampered with. | ||
* | ||
* @see Cookie | ||
*/ | ||
final class CookieSigner | ||
{ | ||
/** | ||
* @var Mac The Mac instance. | ||
*/ | ||
private Mac $mac; | ||
|
||
/** | ||
* @var string The secret key used to sign and validate cookie values. | ||
*/ | ||
private string $key; | ||
|
||
/** | ||
* @param string $key The secret key used to sign and validate cookie values. | ||
*/ | ||
public function __construct(string $key) | ||
{ | ||
$this->mac = new Mac(); | ||
$this->key = $key; | ||
} | ||
|
||
/** | ||
* Returns a new cookie instance with the signed cookie value. | ||
* | ||
* @param Cookie $cookie The cookie with clean value. | ||
* | ||
* @throws RuntimeException If the cookie value is already signed. | ||
* | ||
* @return Cookie The cookie with signed value. | ||
*/ | ||
public function sign(Cookie $cookie): Cookie | ||
{ | ||
if ($this->isSigned($cookie)) { | ||
throw new RuntimeException("The \"{$cookie->getName()}\" cookie value is already signed."); | ||
} | ||
|
||
$prefix = $this->prefix($cookie); | ||
$value = $this->mac->sign($prefix . $cookie->getValue(), $this->key); | ||
return $cookie->withValue($prefix . $value); | ||
} | ||
|
||
/** | ||
* Returns a new cookie instance with the clean cookie value or throws an exception if signature is not valid. | ||
* | ||
* @param Cookie $cookie The cookie with signed value. | ||
* | ||
* @throws RuntimeException If the cookie value is tampered with or not validly signed. | ||
* If you are not sure that the value of the cookie file was signed earlier, then first use the {@see isSigned()}. | ||
* | ||
* @return Cookie The cookie with unsigned value. | ||
*/ | ||
public function validate(Cookie $cookie): Cookie | ||
{ | ||
if (!$this->isSigned($cookie)) { | ||
throw new RuntimeException("The \"{$cookie->getName()}\" cookie value is not validly signed."); | ||
} | ||
|
||
try { | ||
$value = $this->mac->getMessage(substr($cookie->getValue(), 32), $this->key); | ||
return $cookie->withValue(substr($value, 32)); | ||
} catch (DataIsTamperedException $e) { | ||
throw new RuntimeException("The \"{$cookie->getName()}\" cookie value was tampered with."); | ||
} | ||
} | ||
|
||
/** | ||
* Checks whether the cookie value is validly signed. | ||
* | ||
* @param Cookie $cookie The cookie to check. | ||
* | ||
* @return bool Whether the cookie value is validly signed. | ||
*/ | ||
public function isSigned(Cookie $cookie): bool | ||
{ | ||
return strlen($cookie->getValue()) > 32 && strpos($cookie->getValue(), $this->prefix($cookie)) === 0; | ||
} | ||
|
||
/** | ||
* Returns a prefix for cookie. | ||
* | ||
* @param Cookie $cookie The cookie to prefix. | ||
* | ||
* @return string The prefix for cookie. | ||
*/ | ||
private function prefix(Cookie $cookie): string | ||
{ | ||
return md5(self::class . $cookie->getName()); | ||
} | ||
} |
Oops, something went wrong.