[FEATURE] Use dynamic path for typo3temp/var/
[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 * Creates a new key pair for the encryption or gets the existing key pair (if one already has been generated).
69 *
70 * There should only be one key pair per request because the second private key would overwrites the first private
71 * key. So the submitting the form with the first public key would not work anymore.
72 *
73 * @return \TYPO3\CMS\Rsaauth\Keypair|null a key pair or NULL in case of error
74 */
75 public function createNewKeyPair()
76 {
77 /** @var $keyPair \TYPO3\CMS\Rsaauth\Keypair */
78 $keyPair = GeneralUtility::makeInstance(\TYPO3\CMS\Rsaauth\Keypair::class);
79 if ($keyPair->isReady()) {
80 return $keyPair;
81 }
82
83 if ($this->opensslPath === false) {
84 return null;
85 }
86
87 // Create a temporary file. Security: tempnam() sets permissions to 0600
88 $privateKeyFile = tempnam($this->temporaryDirectory, StringUtility::getUniqueId());
89
90 // Generate the private key.
91 //
92 // PHP generates 1024 bit key files. We force command line version
93 // to do the same and use the F4 (0x10001) exponent. This is the most
94 // secure.
95 $command = $this->opensslPath . ' genrsa -out ' . escapeshellarg($privateKeyFile) . ' 1024';
96 if (TYPO3_OS === 'WIN') {
97 $command .= ' 2>NUL';
98 } else {
99 $command .= ' 2>/dev/null';
100 }
101 CommandUtility::exec($command);
102 // Test that we got a private key
103 $privateKey = file_get_contents($privateKeyFile);
104 if (false !== strpos($privateKey, 'BEGIN RSA PRIVATE KEY')) {
105 // Ok, we got the private key. Get the modulus.
106 $command = $this->opensslPath . ' rsa -noout -modulus -in ' . escapeshellarg($privateKeyFile);
107 $value = CommandUtility::exec($command);
108 if (substr($value, 0, 8) === 'Modulus=') {
109 $publicKey = substr($value, 8);
110
111 $keyPair->setExponent(self::DEFAULT_EXPONENT);
112 $keyPair->setPrivateKey($privateKey);
113 $keyPair->setPublicKey($publicKey);
114 }
115 } else {
116 $keyPair = null;
117 }
118
119 @unlink($privateKeyFile);
120 return $keyPair;
121 }
122
123 /**
124 * @param string $privateKey The private key (obtained from a call to createNewKeyPair())
125 * @param string $data Data to decrypt (base64-encoded)
126 * @return string Decrypted data or NULL in case of an error
127 * @see \TYPO3\CMS\Rsaauth\Backend\AbstractBackend::decrypt()
128 */
129 public function decrypt($privateKey, $data)
130 {
131 // Key must be put to the file
132 $privateKeyFile = tempnam($this->temporaryDirectory, StringUtility::getUniqueId());
133 file_put_contents($privateKeyFile, $privateKey);
134 $dataFile = tempnam($this->temporaryDirectory, StringUtility::getUniqueId());
135 file_put_contents($dataFile, base64_decode($data));
136 // Prepare the command
137 $command = $this->opensslPath . ' rsautl -inkey ' . escapeshellarg($privateKeyFile) . ' -in ' . escapeshellarg($dataFile) . ' -decrypt';
138 // Execute the command and capture the result
139 $output = [];
140 CommandUtility::exec($command, $output);
141 // Remove the file
142 @unlink($privateKeyFile);
143 @unlink($dataFile);
144 return implode(LF, $output);
145 }
146
147 /**
148 * Checks if command line version of the OpenSSL is available and can be
149 * executed successfully.
150 *
151 * @return bool
152 * @see \TYPO3\CMS\Rsaauth\Backend\AbstractBackend::isAvailable()
153 */
154 public function isAvailable()
155 {
156 $result = false;
157 if ($this->opensslPath) {
158 // If path exists, test that command runs and can produce output
159 $test = CommandUtility::exec($this->opensslPath . ' version');
160 $result = substr($test, 0, 8) === 'OpenSSL ';
161 }
162 return $result;
163 }
164 }