PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Classes/TYPO3/FLOW3/Security/Cryptography/RsaWalletServicePhp.php

https://github.com/christianjul/FLOW3-Composer
PHP | 364 lines | 148 code | 55 blank | 161 comment | 24 complexity | b29489f61ab134a2a2b4e579d39f397b MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. namespace TYPO3\FLOW3\Security\Cryptography;
  3. /* *
  4. * This script belongs to the FLOW3 framework. *
  5. * *
  6. * It is free software; you can redistribute it and/or modify it under *
  7. * the terms of the GNU Lesser General Public License, either version 3 *
  8. * of the License, or (at your option) any later version. *
  9. * *
  10. * The TYPO3 project - inspiring people to share! *
  11. * */
  12. use TYPO3\FLOW3\Annotations as FLOW3;
  13. /**
  14. * Implementation of the RSAWalletServiceInterface using PHP's OpenSSL extension
  15. *
  16. * @FLOW3\Scope("singleton")
  17. */
  18. class RsaWalletServicePhp implements \TYPO3\FLOW3\Security\Cryptography\RsaWalletServiceInterface {
  19. /**
  20. * @var string
  21. */
  22. protected $keystorePathAndFilename;
  23. /**
  24. * @var array
  25. */
  26. protected $keys = array();
  27. /**
  28. * The openSSL configuration
  29. * @var array
  30. */
  31. protected $openSSLConfiguration = array();
  32. /**
  33. * @var boolean
  34. */
  35. protected $saveKeysOnShutdown = TRUE;
  36. /**
  37. * Injects the OpenSSL configuration to be used
  38. *
  39. * @param array $settings
  40. * @return void
  41. * @throws \TYPO3\FLOW3\Security\Exception\MissingConfigurationException
  42. */
  43. public function injectSettings(array $settings) {
  44. if (isset($settings['security']['cryptography']['RSAWalletServicePHP']['openSSLConfiguration'])
  45. && is_array($settings['security']['cryptography']['RSAWalletServicePHP']['openSSLConfiguration'])) {
  46. $this->openSSLConfiguration = $settings['security']['cryptography']['RSAWalletServicePHP']['openSSLConfiguration'];
  47. }
  48. if (isset($settings['security']['cryptography']['RSAWalletServicePHP']['keystorePath'])) {
  49. $this->keystorePathAndFilename = $settings['security']['cryptography']['RSAWalletServicePHP']['keystorePath'];
  50. } else {
  51. throw new \TYPO3\FLOW3\Security\Exception\MissingConfigurationException('The configuration setting TYPO3.FLOW3.security.cryptography.RSAWalletServicePHP.keystorePath is missing. Please specify it in your Settings.yaml file. Beware: This file must not be accessible by the public!', 1305711354);
  52. }
  53. }
  54. /**
  55. * Initializes the rsa wallet service by fetching the keys from the keystore file
  56. *
  57. * @return void
  58. */
  59. public function initializeObject() {
  60. if (file_exists($this->keystorePathAndFilename)) {
  61. $this->keys = unserialize(file_get_contents($this->keystorePathAndFilename));
  62. }
  63. $this->saveKeysOnShutdown = FALSE;
  64. }
  65. /**
  66. * Generates a new keypair and returns a UUID to refer to it
  67. *
  68. * @param boolean $usedForPasswords TRUE if this keypair should be used to encrypt passwords (then decryption won't be allowed!).
  69. * @return string An UUID that identifies the generated keypair
  70. * @throws \TYPO3\FLOW3\Security\Exception
  71. */
  72. public function generateNewKeypair($usedForPasswords = FALSE) {
  73. $keyResource = openssl_pkey_new($this->openSSLConfiguration);
  74. if ($keyResource === FALSE) {
  75. throw new \TYPO3\FLOW3\Security\Exception('OpenSSL private key generation failed.', 1254838154);
  76. }
  77. $modulus = $this->getModulus($keyResource);
  78. $privateKeyString = $this->getPrivateKeyString($keyResource);
  79. $publicKeyString = $this->getPublicKeyString($keyResource);
  80. $privateKey = new OpenSslRsaKey($modulus, $privateKeyString);
  81. $publicKey = new OpenSslRsaKey($modulus, $publicKeyString);
  82. return $this->storeKeyPair($publicKey, $privateKey, $usedForPasswords);
  83. }
  84. /**
  85. * Adds the specified keypair to the local store and returns a UUID to refer to it.
  86. *
  87. * @param string $privateKeyString The private key in its string representation
  88. * @param boolean $usedForPasswords TRUE if this keypair should be used to encrypt passwords (then decryption won't be allowed!).
  89. * @return string The UUID used for storing
  90. */
  91. public function registerKeyPairFromPrivateKeyString($privateKeyString, $usedForPasswords = FALSE) {
  92. $keyResource = openssl_pkey_get_private($privateKeyString);
  93. $modulus = $this->getModulus($keyResource);
  94. $publicKeyString = $this->getPublicKeyString($keyResource);
  95. $privateKey = new OpenSslRsaKey($modulus, $privateKeyString);
  96. $publicKey = new OpenSslRsaKey($modulus, $publicKeyString);
  97. return $this->storeKeyPair($publicKey, $privateKey, $usedForPasswords);
  98. }
  99. /**
  100. * Adds the specified public key to the wallet and returns a UUID to refer to it.
  101. * This is helpful if you have not private key and want to use this key only to
  102. * verify incoming data.
  103. *
  104. * @param string $publicKeyString The public key in its string representation
  105. * @return string The UUID used for storing
  106. */
  107. public function registerPublicKeyFromString($publicKeyString) {
  108. $keyResource = openssl_pkey_get_public($publicKeyString);
  109. $modulus = $this->getModulus($keyResource);
  110. $publicKey = new OpenSslRsaKey($modulus, $publicKeyString);
  111. return $this->storeKeyPair($publicKey, NULL, FALSE);
  112. }
  113. /**
  114. * Returns the public key for the given UUID
  115. *
  116. * @param string $uuid The UUID
  117. * @return \TYPO3\FLOW3\Security\Cryptography\OpenSslRsaKey The public key
  118. * @throws \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException If the given UUID identifies no valid key pair
  119. */
  120. public function getPublicKey($uuid) {
  121. if ($uuid === NULL || !isset($this->keys[$uuid])) {
  122. throw new \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException('Invalid keypair UUID given', 1231438860);
  123. }
  124. return $this->keys[$uuid]['publicKey'];
  125. }
  126. /**
  127. * Encrypts the given plaintext with the public key identified by the given UUID
  128. *
  129. * @param string $plaintext The plaintext to encrypt
  130. * @param string $uuid The UUID to identify to correct public key
  131. * @return string The ciphertext
  132. */
  133. public function encryptWithPublicKey($plaintext, $uuid) {
  134. $cipher = '';
  135. openssl_public_encrypt($plaintext, $cipher, $this->getPublicKey($uuid)->getKeyString());
  136. return $cipher;
  137. }
  138. /**
  139. * Decrypts the given cipher with the private key identified by the given UUID
  140. * Note: You should never decrypt a password with this function. Use checkRSAEncryptedPassword()
  141. * to check passwords!
  142. *
  143. * @param string $cipher cipher text to decrypt
  144. * @param string $uuid The uuid to identify to correct private key
  145. * @return string The decrypted text
  146. * @throws \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException If the given UUID identifies no valid keypair
  147. * @throws \TYPO3\FLOW3\Security\Exception\DecryptionNotAllowedException If the given UUID identifies a keypair for encrypted passwords
  148. */
  149. public function decrypt($cipher, $uuid) {
  150. if ($uuid === NULL || !isset($this->keys[$uuid])) {
  151. throw new \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException('Invalid keypair UUID given', 1231438861);
  152. }
  153. $keyPair = $this->keys[$uuid];
  154. if ($keyPair['usedForPasswords']) {
  155. throw new \TYPO3\FLOW3\Security\Exception\DecryptionNotAllowedException('You are not allowed to decrypt passwords!', 1233655350);
  156. }
  157. return $this->decryptWithPrivateKey($cipher, $keyPair['privateKey']);
  158. }
  159. /**
  160. * Signs the given plaintext with the private key identified by the given UUID
  161. *
  162. * @param string $plaintext The plaintext to sign
  163. * @param string $uuid The uuid to identify to correct private key
  164. * @return string The signature of the given plaintext
  165. * @throws \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException If the given UUID identifies no valid keypair
  166. */
  167. public function sign($plaintext, $uuid) {
  168. if ($uuid === NULL || !isset($this->keys[$uuid])) {
  169. throw new \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException('Invalid keypair UUID given', 1299095799);
  170. }
  171. $signature = '';
  172. openssl_sign($plaintext, $signature, $this->keys[$uuid]['privateKey']);
  173. return $signature;
  174. }
  175. /**
  176. * Checks whether the given signature is valid for the given plaintext
  177. * with the public key identified by the given UUID
  178. *
  179. * @param string $plaintext The plaintext to sign
  180. * @param string $signature The signature that should be verified
  181. * @param string $uuid The uuid to identify to correct public key
  182. * @return boolean TRUE if the signature is correct for the given plaintext and public key
  183. * @throws \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException
  184. */
  185. public function verifySignature($plaintext, $signature, $uuid) {
  186. if ($uuid === NULL || !isset($this->keys[$uuid])) {
  187. throw new \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException('Invalid keypair UUID given', 1304959763);
  188. }
  189. $verifyResult = openssl_verify($plaintext, $signature, $this->getPublicKey($uuid)->getKeyString());
  190. return $verifyResult === 1;
  191. }
  192. /**
  193. * Checks if the given encrypted password is correct by
  194. * comparing it's md5 hash. The salt is appended to the decrypted password string before hashing.
  195. *
  196. * @param string $encryptedPassword The received, RSA encrypted password to check
  197. * @param string $passwordHash The md5 hashed password string (md5(md5(password) . salt))
  198. * @param string $salt The salt used in the md5 password hash
  199. * @param string $uuid The uuid to identify to correct private key
  200. * @return boolean TRUE if the password is correct
  201. * @throws \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException If the given UUID identifies no valid keypair
  202. */
  203. public function checkRSAEncryptedPassword($encryptedPassword, $passwordHash, $salt, $uuid) {
  204. if ($uuid === NULL || !isset($this->keys[$uuid])) {
  205. throw new \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException('Invalid keypair UUID given', 1233655216);
  206. }
  207. $decryptedPassword = $this->decryptWithPrivateKey($encryptedPassword, $this->keys[$uuid]['privateKey']);
  208. return ($passwordHash === md5(md5($decryptedPassword) . $salt));
  209. }
  210. /**
  211. * Destroys the keypair identified by the given UUID
  212. *
  213. * @param string $uuid The UUID
  214. * @return void
  215. * @throws \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException If the given UUID identifies no valid key pair
  216. */
  217. public function destroyKeypair($uuid) {
  218. if ($uuid === NULL || !isset($this->keys[$uuid])) {
  219. throw new \TYPO3\FLOW3\Security\Exception\InvalidKeyPairIdException('Invalid keypair UUID given', 1231438863);
  220. }
  221. unset($this->keys[$uuid]);
  222. $this->saveKeysOnShutdown = TRUE;
  223. }
  224. /**
  225. * Exports the private key string from the KeyResource
  226. *
  227. * @param resource $keyResource The key Resource
  228. * @return string The private key string
  229. */
  230. private function getPrivateKeyString($keyResource) {
  231. openssl_pkey_export($keyResource, $privateKeyString, NULL, $this->openSSLConfiguration);
  232. return $privateKeyString;
  233. }
  234. /**
  235. * Exports the public key string from the KeyResource
  236. *
  237. * @param resource $keyResource The key Resource
  238. * @return string The public key string
  239. */
  240. private function getPublicKeyString($keyResource) {
  241. $keyDetails = openssl_pkey_get_details($keyResource);
  242. return $keyDetails['key'];
  243. }
  244. /**
  245. * Exports the public modulus HEX string from the KeyResource
  246. *
  247. * @param resource $keyResource The key Resource
  248. * @return string The HEX public modulus string
  249. */
  250. private function getModulus($keyResource) {
  251. $keyDetails = openssl_pkey_get_details($keyResource);
  252. return strtoupper(bin2hex($keyDetails['rsa']['n']));
  253. }
  254. /**
  255. * Decrypts the given ciphertext with the given private key
  256. *
  257. * @param string $cipher The ciphertext to decrypt
  258. * @param \TYPO3\FLOW3\Security\Cryptography\OpenSslRsaKey $privateKey The private key
  259. * @return string The decrypted plaintext
  260. */
  261. private function decryptWithPrivateKey($cipher, \TYPO3\FLOW3\Security\Cryptography\OpenSslRsaKey $privateKey) {
  262. $decrypted = '';
  263. $key = openssl_pkey_get_private($privateKey->getKeyString());
  264. openssl_private_decrypt($cipher, $decrypted, $key);
  265. return $decrypted;
  266. }
  267. /**
  268. * Stores the given keypair under the returned UUID.
  269. *
  270. * @param \TYPO3\FLOW3\Security\Cryptography\OpenSslRsaKey $publicKey The public key
  271. * @param \TYPO3\FLOW3\Security\Cryptography\OpenSslRsaKey $privateKey The private key
  272. * @param boolean $usedForPasswords TRUE if this keypair should be used to encrypt passwords (then decryption won't be allowed!).
  273. * @return string The UUID used for storing
  274. */
  275. private function storeKeyPair($publicKey, $privateKey, $usedForPasswords) {
  276. $keyPairUUID = str_replace('-', '_', \TYPO3\FLOW3\Utility\Algorithms::generateUUID());
  277. $keyPair = array();
  278. $keyPair['publicKey'] = $publicKey;
  279. $keyPair['privateKey'] = $privateKey;
  280. $keyPair['usedForPasswords'] = $usedForPasswords;
  281. $this->keys[$keyPairUUID] = $keyPair;
  282. $this->saveKeysOnShutdown = TRUE;
  283. return $keyPairUUID;
  284. }
  285. /**
  286. * Stores the keys array in the keystore file
  287. *
  288. * @return void
  289. * @throws \TYPO3\FLOW3\Security\Exception
  290. */
  291. public function shutdownObject() {
  292. if ($this->saveKeysOnShutdown === FALSE) {
  293. return;
  294. }
  295. $temporaryKeystorePathAndFilename = $this->keystorePathAndFilename . uniqid() . '.temp';
  296. $result = file_put_contents($temporaryKeystorePathAndFilename, serialize($this->keys));
  297. if ($result === FALSE) {
  298. throw new \TYPO3\FLOW3\Security\Exception('The temporary keystore file "' . $temporaryKeystorePathAndFilename . '" could not be written.', 1305812921);
  299. }
  300. $i = 0;
  301. while (($result = rename($temporaryKeystorePathAndFilename, $this->keystorePathAndFilename)) === FALSE && $i < 5) {
  302. $i++;
  303. }
  304. if ($result === FALSE) {
  305. throw new \TYPO3\FLOW3\Security\Exception('The keystore file "' . $this->keystorePathAndFilename . '" could not be written.', 1305812938);
  306. }
  307. }
  308. }
  309. ?>