bf4ed9620fdda40bb488b197019d12bbf4738195
[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\Utility\CommandUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Core\Utility\StringUtility;
20
21 /**
22 * This class contains an OpenSSL backend for the TYPO3 RSA authentication
23 * service. It uses shell version of OpenSSL to perform tasks. See class
24 * \TYPO3\CMS\Rsaauth\Backend\AbstractBackend for the information on using backends.
25 */
26 class CommandLineBackend extends AbstractBackend
27 {
28 /**
29 * @var int
30 */
31 const DEFAULT_EXPONENT = 65537;
32
33 /**
34 * A path to the openssl binary or FALSE if the binary does not exist
35 *
36 * @var string|bool
37 */
38 protected $opensslPath;
39
40 /**
41 * Temporary directory. It is best of it is outside of the web site root and
42 * not publicly readable.
43 * For now we use typo3temp/.
44 *
45 * @var string
46 */
47 protected $temporaryDirectory;
48
49 /**
50 * Creates an instance of this class. It obtains a path to the OpenSSL
51 * binary.
52 */
53 public function __construct()
54 {
55 $this->opensslPath = CommandUtility::getCommand('openssl');
56 $this->temporaryDirectory = PATH_site . 'typo3temp';
57 // Get temporary directory from the configuration
58 $extconf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['rsaauth']);
59 if (
60 $extconf['temporaryDirectory'] !== ''
61 && $extconf['temporaryDirectory'][0] === '/'
62 && @is_dir($extconf['temporaryDirectory'])
63 && is_writable($extconf['temporaryDirectory'])
64 ) {
65 $this->temporaryDirectory = $extconf['temporaryDirectory'];
66 }
67 }
68
69 /**
70 * Creates a new key pair for the encryption or gets the existing key pair (if one already has been generated).
71 *
72 * There should only be one key pair per request because the second private key would overwrites the first private
73 * key. So the submitting the form with the first public key would not work anymore.
74 *
75 * @return \TYPO3\CMS\Rsaauth\Keypair|NULL a key pair or NULL in case of error
76 */
77 public function createNewKeyPair()
78 {
79 /** @var $keyPair \TYPO3\CMS\Rsaauth\Keypair */
80 $keyPair = GeneralUtility::makeInstance(\TYPO3\CMS\Rsaauth\Keypair::class);
81 if ($keyPair->isReady()) {
82 return $keyPair;
83 }
84
85 if ($this->opensslPath === false) {
86 return null;
87 }
88
89 // Create a temporary file. Security: tempnam() sets permissions to 0600
90 $privateKeyFile = tempnam($this->temporaryDirectory, StringUtility::getUniqueId());
91
92 // Generate the private key.
93 //
94 // PHP generates 1024 bit key files. We force command line version
95 // to do the same and use the F4 (0x10001) exponent. This is the most
96 // secure.
97 $command = $this->opensslPath . ' genrsa -out ' . escapeshellarg($privateKeyFile) . ' 1024';
98 if (TYPO3_OS === 'WIN') {
99 $command .= ' 2>NUL';
100 } else {
101 $command .= ' 2>/dev/null';
102 }
103 CommandUtility::exec($command);
104 // Test that we got a private key
105 $privateKey = file_get_contents($privateKeyFile);
106 if (false !== strpos($privateKey, 'BEGIN RSA PRIVATE KEY')) {
107 // Ok, we got the private key. Get the modulus.
108 $command = $this->opensslPath . ' rsa -noout -modulus -in ' . escapeshellarg($privateKeyFile);
109 $value = CommandUtility::exec($command);
110 if (substr($value, 0, 8) === 'Modulus=') {
111 $publicKey = substr($value, 8);
112
113 $keyPair->setExponent(self::DEFAULT_EXPONENT);
114 $keyPair->setPrivateKey($privateKey);
115 $keyPair->setPublicKey($publicKey);
116 }
117 } else {
118 $keyPair = null;
119 }
120
121 @unlink($privateKeyFile);
122 return $keyPair;
123 }
124
125 /**
126 * @param string $privateKey The private key (obtained from a call to createNewKeyPair())
127 * @param string $data Data to decrypt (base64-encoded)
128 * @return string Decrypted data or NULL in case of an error
129 * @see \TYPO3\CMS\Rsaauth\Backend\AbstractBackend::decrypt()
130 */
131 public function decrypt($privateKey, $data)
132 {
133 // Key must be put to the file
134 $privateKeyFile = tempnam($this->temporaryDirectory, StringUtility::getUniqueId());
135 file_put_contents($privateKeyFile, $privateKey);
136 $dataFile = tempnam($this->temporaryDirectory, StringUtility::getUniqueId());
137 file_put_contents($dataFile, base64_decode($data));
138 // Prepare the command
139 $command = $this->opensslPath . ' rsautl -inkey ' . escapeshellarg($privateKeyFile) . ' -in ' . escapeshellarg($dataFile) . ' -decrypt';
140 // Execute the command and capture the result
141 $output = array();
142 CommandUtility::exec($command, $output);
143 // Remove the file
144 @unlink($privateKeyFile);
145 @unlink($dataFile);
146 return implode(LF, $output);
147 }
148
149 /**
150 * Checks if command line version of the OpenSSL is available and can be
151 * executed successfully.
152 *
153 * @return bool
154 * @see \TYPO3\CMS\Rsaauth\Backend\AbstractBackend::isAvailable()
155 */
156 public function isAvailable()
157 {
158 $result = false;
159 if ($this->opensslPath) {
160 // If path exists, test that command runs and can produce output
161 $test = CommandUtility::exec($this->opensslPath . ' version');
162 $result = substr($test, 0, 8) === 'OpenSSL ';
163 }
164 return $result;
165 }
166 }