X-Git-Url: https://git.cyclocoop.org/%242?a=blobdiff_plain;f=includes%2Fsession%2FSession.php;h=12f16b662eb0dc50548d9b0b63ecac81a9e1161b;hb=8803fab2707184a1e26e676afdecff7f296b78d1;hp=719f905ee24dc7ca49c6f13a71b6ae598b7d6160;hpb=ff13c3d92a3357d2cbb03c4d58dfc33999942b0a;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/session/Session.php b/includes/session/Session.php index 719f905ee2..12f16b662e 100644 --- a/includes/session/Session.php +++ b/includes/session/Session.php @@ -46,6 +46,9 @@ use WebRequest; * @since 1.27 */ final class Session implements \Countable, \Iterator, \ArrayAccess { + /** @var null|string[] Encryption algorithm to use */ + private static $encryptionAlgorithm = null; + /** @var SessionBackend Session backend */ private $backend; @@ -126,6 +129,11 @@ final class Session implements \Countable, \Iterator, \ArrayAccess { /** * Make this session not be persisted across requests + * + * This will remove persistence information (e.g. delete cookies) + * from the associated WebRequest(s), and delete session data in the + * backend. The session data will still be available via get() until + * the end of the request. */ public function unpersist() { $this->backend->unpersist(); @@ -409,24 +417,42 @@ final class Session implements \Countable, \Iterator, \ArrayAccess { * Decide what type of encryption to use, based on system capabilities. * @return array */ - private function getEncryptionAlgorithm() { + private static function getEncryptionAlgorithm() { global $wgSessionInsecureSecrets; - if ( - function_exists( 'openssl_encrypt' ) - && in_array( 'aes-256-ctr', openssl_get_cipher_methods(), true ) - ) { - return [ 'openssl', 'aes-256-ctr' ]; - } elseif ( - function_exists( 'mcrypt_encrypt' ) - && in_array( 'rijndael-128', mcrypt_list_algorithms(), true ) - && in_array( 'ctr', mcrypt_list_modes(), true ) - ) { - return [ 'mcrypt', 'rijndael-128', 'ctr' ]; - } elseif ( $wgSessionInsecureSecrets ) { - // @todo: import a pure-PHP library for AES instead of this - return [ 'insecure' ]; - } else { + if ( self::$encryptionAlgorithm === null ) { + if ( function_exists( 'openssl_encrypt' ) ) { + $methods = openssl_get_cipher_methods(); + if ( in_array( 'aes-256-ctr', $methods, true ) ) { + self::$encryptionAlgorithm = [ 'openssl', 'aes-256-ctr' ]; + return self::$encryptionAlgorithm; + } + if ( in_array( 'aes-256-cbc', $methods, true ) ) { + self::$encryptionAlgorithm = [ 'openssl', 'aes-256-cbc' ]; + return self::$encryptionAlgorithm; + } + } + + if ( function_exists( 'mcrypt_encrypt' ) + && in_array( 'rijndael-128', mcrypt_list_algorithms(), true ) + ) { + $modes = mcrypt_list_modes(); + if ( in_array( 'ctr', $modes, true ) ) { + self::$encryptionAlgorithm = [ 'mcrypt', 'rijndael-128', 'ctr' ]; + return self::$encryptionAlgorithm; + } + if ( in_array( 'cbc', $modes, true ) ) { + self::$encryptionAlgorithm = [ 'mcrypt', 'rijndael-128', 'cbc' ]; + return self::$encryptionAlgorithm; + } + } + + if ( $wgSessionInsecureSecrets ) { + // @todo: import a pure-PHP library for AES instead of this + self::$encryptionAlgorithm = [ 'insecure' ]; + return self::$encryptionAlgorithm; + } + throw new \BadMethodCallException( 'Encryption is not available. You really should install the PHP OpenSSL extension, ' . 'or failing that the mcrypt extension. But if you really can\'t and you\'re willing ' . @@ -435,6 +461,7 @@ final class Session implements \Countable, \Iterator, \ArrayAccess { ); } + return self::$encryptionAlgorithm; } /** @@ -455,7 +482,7 @@ final class Session implements \Countable, \Iterator, \ArrayAccess { // Encrypt // @todo: import a pure-PHP library for AES instead of doing $wgSessionInsecureSecrets $iv = \MWCryptRand::generate( 16, true ); - $algorithm = $this->getEncryptionAlgorithm(); + $algorithm = self::getEncryptionAlgorithm(); switch ( $algorithm[0] ) { case 'openssl': $ciphertext = openssl_encrypt( $serialized, $algorithm[1], $encKey, OPENSSL_RAW_DATA, $iv ); @@ -464,6 +491,11 @@ final class Session implements \Countable, \Iterator, \ArrayAccess { } break; case 'mcrypt': + // PKCS7 padding + $blocksize = mcrypt_get_block_size( $algorithm[1], $algorithm[2] ); + $pad = $blocksize - ( strlen( $serialized ) % $blocksize ); + $serialized .= str_repeat( chr( $pad ), $pad ); + $ciphertext = mcrypt_encrypt( $algorithm[1], $encKey, $serialized, $algorithm[2], $iv ); if ( $ciphertext === false ) { throw new \UnexpectedValueException( 'Encryption failed' ); @@ -521,7 +553,7 @@ final class Session implements \Countable, \Iterator, \ArrayAccess { } // Decrypt - $algorithm = $this->getEncryptionAlgorithm(); + $algorithm = self::getEncryptionAlgorithm(); switch ( $algorithm[0] ) { case 'openssl': $serialized = openssl_decrypt( base64_decode( $ciphertext ), $algorithm[1], $encKey, @@ -540,6 +572,10 @@ final class Session implements \Countable, \Iterator, \ArrayAccess { $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] ); return $default; } + + // Remove PKCS7 padding + $pad = ord( substr( $serialized, -1 ) ); + $serialized = substr( $serialized, 0, -$pad ); break; case 'insecure': $ex = new \Exception( @@ -564,7 +600,7 @@ final class Session implements \Countable, \Iterator, \ArrayAccess { * * Calls to save() or clear() will not be delayed. * - * @return \ScopedCallback When this goes out of scope, a save will be triggered + * @return \Wikimedia\ScopedCallback When this goes out of scope, a save will be triggered */ public function delaySave() { return $this->backend->delaySave(); @@ -572,6 +608,9 @@ final class Session implements \Countable, \Iterator, \ArrayAccess { /** * Save the session + * + * This will update the backend data and might re-persist the session + * if needed. */ public function save() { $this->backend->save();