[SECURITY] Explicitly deny object deserialization
[Packages/TYPO3.CMS.git] / typo3 / sysext / rsaauth / Classes / Backend / CommandLineBackend.php
1 <?php
2 namespace TYPO3\CMS\Rsaauth\Backend;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
18 use TYPO3\CMS\Core\Core\Environment;
19 use TYPO3\CMS\Core\Utility\CommandUtility;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Core\Utility\StringUtility;
22
23 /**
24 * This class contains an OpenSSL backend for the TYPO3 RSA authentication
25 * service. It uses shell version of OpenSSL to perform tasks. See class
26 * \TYPO3\CMS\Rsaauth\Backend\AbstractBackend for the information on using backends.
27 */
28 class CommandLineBackend extends AbstractBackend
29 {
30 /**
31 * @var int
32 */
33 const DEFAULT_EXPONENT = 65537;
34
35 /**
36 * A path to the openssl binary or FALSE if the binary does not exist
37 *
38 * @var string|bool
39 */
40 protected $opensslPath;
41
42 /**
43 * Temporary directory. It is best of it is outside of the web site root and
44 * not publicly readable.
45 * For now we use Environment::getVarPath() . '/transient' (stored in the variable without the trailing slash).
46 *
47 * @var string
48 */
49 protected $temporaryDirectory;
50
51 /**
52 * Creates an instance of this class. It obtains a path to the OpenSSL
53 * binary.
54 */
55 public function __construct()
56 {
57 $this->opensslPath = CommandUtility::getCommand('openssl');
58 // Get temporary directory from the configuration
59 $path = trim(GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('rsaauth', 'temporaryDirectory'));
60 if ($path !== '' && $path[0] === '/' && @is_dir($path) && is_writable($path)) {
61 $this->temporaryDirectory = $path;
62 } else {
63 $this->temporaryDirectory = Environment::getVarPath() . '/transient';
64 }
65 }
66
67 /**
68 * Denies deserialization.
69 */
70 public function __wakeup()
71 {
72 $this->opensslPath = null;
73 $this->temporaryDirectory = null;
74
75 throw new \RuntimeException(
76 __CLASS__ . ' cannot be unserialized',
77 1531336156
78 );
79 }
80
81 /**
82 * Creates a new key pair for the encryption or gets the existing key pair (if one already has been generated).
83 *
84 * There should only be one key pair per request because the second private key would overwrites the first private
85 * key. So the submitting the form with the first public key would not work anymore.
86 *
87 * @return \TYPO3\CMS\Rsaauth\Keypair|null a key pair or NULL in case of error
88 */
89 public function createNewKeyPair()
90 {
91 /** @var $keyPair \TYPO3\CMS\Rsaauth\Keypair */
92 $keyPair = GeneralUtility::makeInstance(\TYPO3\CMS\Rsaauth\Keypair::class);
93 if ($keyPair->isReady()) {
94 return $keyPair;
95 }
96
97 if ($this->opensslPath === false) {
98 return null;
99 }
100
101 // Create a temporary file. Security: tempnam() sets permissions to 0600
102 $privateKeyFile = tempnam($this->temporaryDirectory, StringUtility::getUniqueId());
103
104 // Generate the private key.
105 //
106 // PHP generates 1024 bit key files. We force command line version
107 // to do the same and use the F4 (0x10001) exponent. This is the most
108 // secure.
109 $command = $this->opensslPath . ' genrsa -out ' . escapeshellarg($privateKeyFile) . ' 1024';
110 if (Environment::isWindows()) {
111 $command .= ' 2>NUL';
112 } else {
113 $command .= ' 2>/dev/null';
114 }
115 CommandUtility::exec($command);
116 // Test that we got a private key
117 $privateKey = file_get_contents($privateKeyFile);
118 if (false !== strpos($privateKey, 'BEGIN RSA PRIVATE KEY')) {
119 // Ok, we got the private key. Get the modulus.
120 $command = $this->opensslPath . ' rsa -noout -modulus -in ' . escapeshellarg($privateKeyFile);
121 $value = CommandUtility::exec($command);
122 if (substr($value, 0, 8) === 'Modulus=') {
123 $publicKey = substr($value, 8);
124
125 $keyPair->setExponent(self::DEFAULT_EXPONENT);
126 $keyPair->setPrivateKey($privateKey);
127 $keyPair->setPublicKey($publicKey);
128 }
129 } else {
130 $keyPair = null;
131 }
132
133 @unlink($privateKeyFile);
134 return $keyPair;
135 }
136
137 /**
138 * @param string $privateKey The private key (obtained from a call to createNewKeyPair())
139 * @param string $data Data to decrypt (base64-encoded)
140 * @return string Decrypted data or NULL in case of an error
141 * @see \TYPO3\CMS\Rsaauth\Backend\AbstractBackend::decrypt()
142 */
143 public function decrypt($privateKey, $data)
144 {
145 // Key must be put to the file
146 $privateKeyFile = tempnam($this->temporaryDirectory, StringUtility::getUniqueId());
147 file_put_contents($privateKeyFile, $privateKey);
148 $dataFile = tempnam($this->temporaryDirectory, StringUtility::getUniqueId());
149 file_put_contents($dataFile, base64_decode($data));
150 // Prepare the command
151 $command = $this->opensslPath . ' rsautl -inkey ' . escapeshellarg($privateKeyFile) . ' -in ' . escapeshellarg($dataFile) . ' -decrypt';
152 // Execute the command and capture the result
153 $output = [];
154 CommandUtility::exec($command, $output);
155 // Remove the file
156 @unlink($privateKeyFile);
157 @unlink($dataFile);
158 return implode(LF, $output);
159 }
160
161 /**
162 * Checks if command line version of the OpenSSL is available and can be
163 * executed successfully.
164 *
165 * @return bool
166 * @see \TYPO3\CMS\Rsaauth\Backend\AbstractBackend::isAvailable()
167 */
168 public function isAvailable()
169 {
170 $result = false;
171 if ($this->opensslPath) {
172 // If path exists, test that command runs and can produce output
173 $test = CommandUtility::exec($this->opensslPath . ' version');
174 $result = substr($test, 0, 8) === 'OpenSSL ';
175 }
176 return $result;
177 }
178 }