diff options
Diffstat (limited to 'Core')
-rw-r--r-- | Core/Authentication/AuthenticationProviderManager.php | 2 | ||||
-rw-r--r-- | Core/Authentication/Provider/UserAuthenticationProvider.php | 2 | ||||
-rw-r--r-- | Core/Authentication/RememberMe/PersistentToken.php | 2 | ||||
-rw-r--r-- | Core/Authentication/Token/AbstractToken.php | 4 | ||||
-rw-r--r-- | Core/Authentication/Token/AnonymousToken.php | 10 | ||||
-rw-r--r-- | Core/Authentication/Token/RememberMeToken.php | 2 | ||||
-rw-r--r-- | Core/Authentication/Token/TokenInterface.php | 4 | ||||
-rw-r--r-- | Core/Authentication/Token/UsernamePasswordToken.php | 18 | ||||
-rw-r--r-- | Core/Authorization/AccessDecisionManager.php | 2 | ||||
-rw-r--r-- | Core/Encoder/BasePasswordEncoder.php | 15 | ||||
-rw-r--r-- | Core/Encoder/EncoderFactory.php | 2 | ||||
-rw-r--r-- | Core/Encoder/Pbkdf2PasswordEncoder.php | 97 | ||||
-rw-r--r-- | Core/Role/RoleHierarchyInterface.php | 4 | ||||
-rw-r--r-- | Core/Role/RoleInterface.php | 2 | ||||
-rw-r--r-- | Core/User/InMemoryUserProvider.php | 2 | ||||
-rw-r--r-- | Core/Util/ClassUtils.php | 5 | ||||
-rw-r--r-- | Core/Util/SecureRandom.php | 114 | ||||
-rw-r--r-- | Core/Util/SecureRandomInterface.php | 29 | ||||
-rw-r--r-- | Core/Util/StringUtils.php | 60 |
19 files changed, 350 insertions, 26 deletions
diff --git a/Core/Authentication/AuthenticationProviderManager.php b/Core/Authentication/AuthenticationProviderManager.php index 7ca46c0..b0414f0 100644 --- a/Core/Authentication/AuthenticationProviderManager.php +++ b/Core/Authentication/AuthenticationProviderManager.php @@ -39,6 +39,8 @@ class AuthenticationProviderManager implements AuthenticationManagerInterface * * @param AuthenticationProviderInterface[] $providers An array of AuthenticationProviderInterface instances * @param Boolean $eraseCredentials Whether to erase credentials after authentication or not + * + * @throws \InvalidArgumentException */ public function __construct(array $providers, $eraseCredentials = true) { diff --git a/Core/Authentication/Provider/UserAuthenticationProvider.php b/Core/Authentication/Provider/UserAuthenticationProvider.php index 32d7971..ed8f499 100644 --- a/Core/Authentication/Provider/UserAuthenticationProvider.php +++ b/Core/Authentication/Provider/UserAuthenticationProvider.php @@ -37,6 +37,8 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter * @param UserCheckerInterface $userChecker An UserCheckerInterface interface * @param string $providerKey A provider key * @param Boolean $hideUserNotFoundExceptions Whether to hide user not found exception or not + * + * @throws \InvalidArgumentException */ public function __construct(UserCheckerInterface $userChecker, $providerKey, $hideUserNotFoundExceptions = true) { diff --git a/Core/Authentication/RememberMe/PersistentToken.php b/Core/Authentication/RememberMe/PersistentToken.php index 88b0413..f3f6858 100644 --- a/Core/Authentication/RememberMe/PersistentToken.php +++ b/Core/Authentication/RememberMe/PersistentToken.php @@ -32,6 +32,8 @@ final class PersistentToken implements PersistentTokenInterface * @param string $series * @param string $tokenValue * @param \DateTime $lastUsed + * + * @throws \InvalidArgumentException */ public function __construct($class, $username, $series, $tokenValue, \DateTime $lastUsed) { diff --git a/Core/Authentication/Token/AbstractToken.php b/Core/Authentication/Token/AbstractToken.php index ed6e8de..f21aa76 100644 --- a/Core/Authentication/Token/AbstractToken.php +++ b/Core/Authentication/Token/AbstractToken.php @@ -33,7 +33,9 @@ abstract class AbstractToken implements TokenInterface /** * Constructor. * - * @param Role[] $roles An array of roles + * @param RoleInterface[] $roles An array of roles + * + * @throws \InvalidArgumentException */ public function __construct(array $roles = array()) { diff --git a/Core/Authentication/Token/AnonymousToken.php b/Core/Authentication/Token/AnonymousToken.php index ecdd4cc..9b0a084 100644 --- a/Core/Authentication/Token/AnonymousToken.php +++ b/Core/Authentication/Token/AnonymousToken.php @@ -24,9 +24,9 @@ class AnonymousToken extends AbstractToken /** * Constructor. * - * @param string $key The key shared with the authentication provider - * @param string $user The user - * @param Role[] $roles An array of roles + * @param string $key The key shared with the authentication provider + * @param string $user The user + * @param RoleInterface[] $roles An array of roles */ public function __construct($key, $user, array $roles = array()) { @@ -66,9 +66,9 @@ class AnonymousToken extends AbstractToken /** * {@inheritDoc} */ - public function unserialize($str) + public function unserialize($serialized) { - list($this->key, $parentStr) = unserialize($str); + list($this->key, $parentStr) = unserialize($serialized); parent::unserialize($parentStr); } } diff --git a/Core/Authentication/Token/RememberMeToken.php b/Core/Authentication/Token/RememberMeToken.php index de50e5c..6f3d821 100644 --- a/Core/Authentication/Token/RememberMeToken.php +++ b/Core/Authentication/Token/RememberMeToken.php @@ -29,6 +29,8 @@ class RememberMeToken extends AbstractToken * @param UserInterface $user * @param string $providerKey * @param string $key + * + * @throws \InvalidArgumentException */ public function __construct(UserInterface $user, $providerKey, $key) { diff --git a/Core/Authentication/Token/TokenInterface.php b/Core/Authentication/Token/TokenInterface.php index dec31d5..11f69da 100644 --- a/Core/Authentication/Token/TokenInterface.php +++ b/Core/Authentication/Token/TokenInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Core\Authentication\Token; +use Symfony\Component\Security\Core\Role\RoleInterface; + /** * TokenInterface is the interface for the user authentication information. * @@ -31,7 +33,7 @@ interface TokenInterface extends \Serializable /** * Returns the user roles. * - * @return Role[] An array of Role instances. + * @return RoleInterface[] An array of RoleInterface instances. */ public function getRoles(); diff --git a/Core/Authentication/Token/UsernamePasswordToken.php b/Core/Authentication/Token/UsernamePasswordToken.php index 95eec54..d6e3998 100644 --- a/Core/Authentication/Token/UsernamePasswordToken.php +++ b/Core/Authentication/Token/UsernamePasswordToken.php @@ -24,10 +24,10 @@ class UsernamePasswordToken extends AbstractToken /** * Constructor. * - * @param string $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method. - * @param string $credentials This usually is the password of the user - * @param string $providerKey The provider key - * @param array $roles An array of roles + * @param string $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method. + * @param string $credentials This usually is the password of the user + * @param string $providerKey The provider key + * @param RoleInterface[] $roles An array of roles * * @throws \InvalidArgumentException */ @@ -78,14 +78,20 @@ class UsernamePasswordToken extends AbstractToken $this->credentials = null; } + /** + * {@inheritdoc} + */ public function serialize() { return serialize(array($this->credentials, $this->providerKey, parent::serialize())); } - public function unserialize($str) + /** + * {@inheritdoc} + */ + public function unserialize($serialized) { - list($this->credentials, $this->providerKey, $parentStr) = unserialize($str); + list($this->credentials, $this->providerKey, $parentStr) = unserialize($serialized); parent::unserialize($parentStr); } } diff --git a/Core/Authorization/AccessDecisionManager.php b/Core/Authorization/AccessDecisionManager.php index a8bb5cf..6028c42 100644 --- a/Core/Authorization/AccessDecisionManager.php +++ b/Core/Authorization/AccessDecisionManager.php @@ -34,6 +34,8 @@ class AccessDecisionManager implements AccessDecisionManagerInterface * @param string $strategy The vote strategy * @param Boolean $allowIfAllAbstainDecisions Whether to grant access if all voters abstained or not * @param Boolean $allowIfEqualGrantedDeniedDecisions Whether to grant access if result are equals + * + * @throws \InvalidArgumentException */ public function __construct(array $voters, $strategy = 'affirmative', $allowIfAllAbstainDecisions = false, $allowIfEqualGrantedDeniedDecisions = true) { diff --git a/Core/Encoder/BasePasswordEncoder.php b/Core/Encoder/BasePasswordEncoder.php index ae1c7d4..c26c9ce 100644 --- a/Core/Encoder/BasePasswordEncoder.php +++ b/Core/Encoder/BasePasswordEncoder.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Core\Encoder; +use Symfony\Component\Security\Core\Util\StringUtils; + /** * BasePasswordEncoder is the base class for all password encoders. * @@ -50,6 +52,8 @@ abstract class BasePasswordEncoder implements PasswordEncoderInterface * @param string $salt the salt to be used * * @return string a merged password and salt + * + * @throws \InvalidArgumentException */ protected function mergePasswordAndSalt($password, $salt) { @@ -77,15 +81,6 @@ abstract class BasePasswordEncoder implements PasswordEncoderInterface */ protected function comparePasswords($password1, $password2) { - if (strlen($password1) !== strlen($password2)) { - return false; - } - - $result = 0; - for ($i = 0; $i < strlen($password1); $i++) { - $result |= ord($password1[$i]) ^ ord($password2[$i]); - } - - return 0 === $result; + return StringUtils::equals($password1, $password2); } } diff --git a/Core/Encoder/EncoderFactory.php b/Core/Encoder/EncoderFactory.php index 9429441..8bad61f 100644 --- a/Core/Encoder/EncoderFactory.php +++ b/Core/Encoder/EncoderFactory.php @@ -51,6 +51,8 @@ class EncoderFactory implements EncoderFactoryInterface * @param array $config * * @return PasswordEncoderInterface + * + * @throws \InvalidArgumentException */ private function createEncoder(array $config) { diff --git a/Core/Encoder/Pbkdf2PasswordEncoder.php b/Core/Encoder/Pbkdf2PasswordEncoder.php new file mode 100644 index 0000000..656545f --- /dev/null +++ b/Core/Encoder/Pbkdf2PasswordEncoder.php @@ -0,0 +1,97 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Encoder; + +/** + * Pbkdf2PasswordEncoder uses the PBKDF2 (Password-Based Key Derivation Function 2). + * + * Providing a high level of Cryptographic security, + * PBKDF2 is recommended by the National Institute of Standards and Technology (NIST). + * + * But also warrants a warning, using PBKDF2 (with a high number of iterations) slows down the process. + * PBKDF2 should be used with caution and care. + * + * @author Sebastiaan Stok <s.stok@rollerscapes.net> + * @author Andrew Johnson + * @author Fabien Potencier <fabien@symfony.com> + */ +class Pbkdf2PasswordEncoder extends BasePasswordEncoder +{ + private $algorithm; + private $encodeHashAsBase64; + private $iterations; + private $length; + + /** + * Constructor. + * + * @param string $algorithm The digest algorithm to use + * @param Boolean $encodeHashAsBase64 Whether to base64 encode the password hash + * @param integer $iterations The number of iterations to use to stretch the password hash + * @param integer $length Length of derived key to create + */ + public function __construct($algorithm = 'sha512', $encodeHashAsBase64 = true, $iterations = 1000, $length = 40) + { + $this->algorithm = $algorithm; + $this->encodeHashAsBase64 = $encodeHashAsBase64; + $this->iterations = $iterations; + $this->length = $length; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the algorithm is not supported + */ + public function encodePassword($raw, $salt) + { + if (!in_array($this->algorithm, hash_algos(), true)) { + throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm)); + } + + if (function_exists('hash_pbkdf2')) { + $digest = hash_pbkdf2($this->algorithm, $raw, $salt, $this->iterations, $this->length, true); + } else { + $digest = $this->hashPbkdf2($this->algorithm, $raw, $salt, $this->iterations, $this->length); + } + + return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); + } + + /** + * {@inheritdoc} + */ + public function isPasswordValid($encoded, $raw, $salt) + { + return $this->comparePasswords($encoded, $this->encodePassword($raw, $salt)); + } + + private function hashPbkdf2($algorithm, $password, $salt, $iterations, $length = 0) + { + // Number of blocks needed to create the derived key + $blocks = ceil($length / strlen(hash($algorithm, null, true))); + $digest = ''; + + for ($i = 1; $i <= $blocks; $i++) { + $ib = $block = hash_hmac($algorithm, $salt . pack('N', $i), $password, true); + + // Iterations + for ($j = 1; $j < $iterations; $j++) { + $ib ^= ($block = hash_hmac($algorithm, $block, $password, true)); + } + + $digest .= $ib; + } + + return substr($digest, 0, $this->length); + } +} diff --git a/Core/Role/RoleHierarchyInterface.php b/Core/Role/RoleHierarchyInterface.php index c495a7f..2ea6ca3 100644 --- a/Core/Role/RoleHierarchyInterface.php +++ b/Core/Role/RoleHierarchyInterface.php @@ -24,9 +24,9 @@ interface RoleHierarchyInterface * Reachable roles are the roles directly assigned but also all roles that * are transitively reachable from them in the role hierarchy. * - * @param array $roles An array of directly assigned roles + * @param RoleInterface[] $roles An array of directly assigned roles * - * @return array An array of all reachable roles + * @return RoleInterface[] An array of all reachable roles */ public function getReachableRoles(array $roles); } diff --git a/Core/Role/RoleInterface.php b/Core/Role/RoleInterface.php index a3cb266..3d4cbea 100644 --- a/Core/Role/RoleInterface.php +++ b/Core/Role/RoleInterface.php @@ -15,7 +15,7 @@ namespace Symfony\Component\Security\Core\Role; * RoleInterface represents a role granted to a user. * * A role must either have a string representation or it needs to be explicitly - * supported by an at least one AccessDecisionManager. + * supported by at least one AccessDecisionManager. * * @author Fabien Potencier <fabien@symfony.com> */ diff --git a/Core/User/InMemoryUserProvider.php b/Core/User/InMemoryUserProvider.php index eae2083..bd74804 100644 --- a/Core/User/InMemoryUserProvider.php +++ b/Core/User/InMemoryUserProvider.php @@ -50,6 +50,8 @@ class InMemoryUserProvider implements UserProviderInterface * Adds a new User to the provider. * * @param UserInterface $user A UserInterface instance + * + * @throws \LogicException */ public function createUser(UserInterface $user) { diff --git a/Core/Util/ClassUtils.php b/Core/Util/ClassUtils.php index 7b583a3..26bf1a1 100644 --- a/Core/Util/ClassUtils.php +++ b/Core/Util/ClassUtils.php @@ -37,6 +37,11 @@ class ClassUtils const MARKER_LENGTH = 6; /** + * This class should not be instantiated + */ + private function __construct() {} + + /** * Gets the real class name of a class name that could be a proxy. * * @param string|object diff --git a/Core/Util/SecureRandom.php b/Core/Util/SecureRandom.php new file mode 100644 index 0000000..77f1d8c --- /dev/null +++ b/Core/Util/SecureRandom.php @@ -0,0 +1,114 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Util; + +use Symfony\Component\HttpKernel\Log\LoggerInterface; + +/** + * A secure random number generator implementation. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +final class SecureRandom implements SecureRandomInterface +{ + private $logger; + private $useOpenSsl; + private $seed; + private $seedUpdated; + private $seedLastUpdatedAt; + private $seedFile; + + /** + * Constructor. + * + * Be aware that a guessable seed will severely compromise the PRNG + * algorithm that is employed. + * + * @param string $seedFile + * @param LoggerInterface $logger + */ + public function __construct($seedFile = null, LoggerInterface $logger = null) + { + $this->seedFile = $seedFile; + $this->logger = $logger; + + // determine whether to use OpenSSL + if (defined('PHP_WINDOWS_VERSION_BUILD') && version_compare(PHP_VERSION, '5.3.4', '<')) { + $this->useOpenSsl = false; + } elseif (!function_exists('openssl_random_pseudo_bytes')) { + if (null !== $this->logger) { + $this->logger->notice('It is recommended that you enable the "openssl" extension for random number generation.'); + } + $this->useOpenSsl = false; + } else { + $this->useOpenSsl = true; + } + } + + /** + * {@inheritdoc} + */ + public function nextBytes($nbBytes) + { + // try OpenSSL + if ($this->useOpenSsl) { + $bytes = openssl_random_pseudo_bytes($nbBytes, $strong); + + if (false !== $bytes && true === $strong) { + return $bytes; + } + + if (null !== $this->logger) { + $this->logger->info('OpenSSL did not produce a secure random number.'); + } + } + + // initialize seed + if (null === $this->seed) { + if (null === $this->seedFile) { + throw new \RuntimeException('You need to specify a file path to store the seed.'); + } + + if (is_file($this->seedFile)) { + list($this->seed, $this->seedLastUpdatedAt) = $this->readSeed(); + } else { + $this->seed = uniqid(mt_rand(), true); + $this->updateSeed(); + } + } + + $bytes = ''; + while (strlen($bytes) < $nbBytes) { + static $incr = 1; + $bytes .= hash('sha512', $incr++.$this->seed.uniqid(mt_rand(), true).$nbBytes, true); + $this->seed = base64_encode(hash('sha512', $this->seed.$bytes.$nbBytes, true)); + $this->updateSeed(); + } + + return substr($bytes, 0, $nbBytes); + } + + private function readSeed() + { + return json_decode(file_get_contents($this->seedFile)); + } + + private function updateSeed() + { + if (!$this->seedUpdated && $this->seedLastUpdatedAt < time() - mt_rand(1, 10)) { + file_put_contents($this->seedFile, json_encode(array($this->seed, microtime(true)))); + } + + $this->seedUpdated = true; + } +} diff --git a/Core/Util/SecureRandomInterface.php b/Core/Util/SecureRandomInterface.php new file mode 100644 index 0000000..2c35a72 --- /dev/null +++ b/Core/Util/SecureRandomInterface.php @@ -0,0 +1,29 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Util; + +/** + * Interface that needs to be implemented by all secure random number generators. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface SecureRandomInterface +{ + /** + * Generates the specified number of secure random bytes. + * + * @param integer $nbBytes + * + * @return string + */ + public function nextBytes($nbBytes); +} diff --git a/Core/Util/StringUtils.php b/Core/Util/StringUtils.php new file mode 100644 index 0000000..2e8925d --- /dev/null +++ b/Core/Util/StringUtils.php @@ -0,0 +1,60 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Util; + +/** + * String utility functions. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class StringUtils +{ + /** + * This class should not be instantiated + */ + private function __construct() {} + + /** + * Compares two strings. + * + * This method implements a constant-time algorithm to compare strings. + * + * @param string $knownString The string of known length to compare against + * @param string $userInput The string that the user can control + * + * @return Boolean true if the two strings are the same, false otherwise + */ + public static function equals($knownString, $userInput) + { + // Prevent issues if string length is 0 + $knownString .= chr(0); + $userInput .= chr(0); + + $knownLen = strlen($knownString); + $userLen = strlen($userInput); + + // Set the result to the difference between the lengths + $result = $knownLen - $userLen; + + // Note that we ALWAYS iterate over the user-supplied length + // This is to prevent leaking length information + for ($i = 0; $i < $userLen; $i++) { + // Using % here is a trick to prevent notices + // It's safe, since if the lengths are different + // $result is already non-0 + $result |= (ord($knownString[$i % $knownLen]) ^ ord($userInput[$i])); + } + + // They are only identical strings if $result is exactly 0... + return 0 === $result; + } +} |