[BUGFIX] Fetch RSA public key by Ajax before login 93/28893/5
authorHelmut Hummel <helmut.hummel@typo3.org>
Wed, 26 Mar 2014 22:44:54 +0000 (23:44 +0100)
committerStefan Neufeind <typo3.neufeind@speedpartner.de>
Thu, 3 Apr 2014 20:02:07 +0000 (22:02 +0200)
Currently public and private RSA keys are generated when rendering the
login form. This has several drawbacks.

It can lead to strange and hard to debug errors when a second request is
done in the same browser, which invalidates the key for the current
login form (#38660), opening a second login in a different tab
invalidates the key on the first tab and finally when the login form
stays open until the PHP session expires (parts of the private key are
stored in the PHP session), the key is also invalid for the form.

Solution is to create a new key pair on the fly when a user clicks the
submit button and fetch the public key via Ajax.

This change implements this for the backend login. Frontend login should
be tackled in a different patch.

Resolves: #37421
Releases: 6.2
Change-Id: I0cd9a049d892ee872436347153a0e1114b17585d
Reviewed-on: https://review.typo3.org/28893
Reviewed-by: Nicole Cordes
Tested-by: Nicole Cordes
Reviewed-by: Stefan Neufeind
Tested-by: Stefan Neufeind
typo3/sysext/rsaauth/Classes/Hook/LoginFormHook.php
typo3/sysext/rsaauth/ext_localconf.php
typo3/sysext/rsaauth/resources/BackendLoginFormRsaEncryption.js [new file with mode: 0644]
typo3/sysext/rsaauth/resources/rsaauth.js
typo3/sysext/rsaauth/resources/rsaauth_min.js

index ddbda53..ed261e5 100644 (file)
@@ -28,6 +28,7 @@ namespace TYPO3\CMS\Rsaauth\Hook;
  * and supply a proper form tag.
  *
  * @author Dmitry Dulepov <dmitry@typo3.org>
+ * @author Helmut Hummel <helmut@typo3.org>
  */
 class LoginFormHook {
 
@@ -41,53 +42,24 @@ class LoginFormHook {
         */
        public function getLoginFormTag(array $params, \TYPO3\CMS\Backend\Controller\LoginController &$pObj) {
                $form = NULL;
-               if ($pObj->loginSecurityLevel == 'rsa') {
-                       // If we can get the backend, we can proceed
-                       $backend = \TYPO3\CMS\Rsaauth\Backend\BackendFactory::getBackend();
-                       if (!is_null($backend)) {
-                               // Add form tag
-                               $form = '<form action="index.php" method="post" name="loginform" onsubmit="tx_rsaauth_encrypt();">';
-                               // Generate a new key pair
-                               $keyPair = $backend->createNewKeyPair();
-                               // Save private key
-                               $storage = \TYPO3\CMS\Rsaauth\Storage\StorageFactory::getStorage();
-                               /** @var $storage \TYPO3\CMS\Rsaauth\Storage\AbstractStorage */
-                               $storage->put($keyPair->getPrivateKey());
-                               // Add RSA hidden fields
-                               $form .= '<input type="hidden" id="rsa_n" name="n" value="' . htmlspecialchars($keyPair->getPublicKeyModulus()) . '" />';
-                               $form .= '<input type="hidden" id="rsa_e" name="e" value="' . sprintf('%x', $keyPair->getExponent()) . '" />';
-                       } else {
-                               throw new \TYPO3\CMS\Core\Error\Exception('No OpenSSL backend could be obtained for rsaauth.', 1318283565);
-                       }
-               }
-               return $form;
-       }
-
-       /**
-        * Provides form code for the superchallenged authentication.
-        *
-        * @param array $params Parameters to the script
-        * @param \TYPO3\CMS\Backend\Controller\LoginController $pObj Calling object
-        * @return string The code for the login form
-        */
-       public function getLoginScripts(array $params, \TYPO3\CMS\Backend\Controller\LoginController &$pObj) {
-               $content = '';
-               if ($pObj->loginSecurityLevel == 'rsa') {
-                       $javascriptPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath('rsaauth') . 'resources/';
+               if ($pObj->loginSecurityLevel === 'rsa') {
+                       /** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
+                       $pageRenderer = $GLOBALS['TBE_TEMPLATE']->getPageRenderer();
+                       $javascriptPath = '../' . \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath('rsaauth') . 'resources/';
                        $files = array(
                                'jsbn/jsbn.js',
                                'jsbn/prng4.js',
                                'jsbn/rng.js',
                                'jsbn/rsa.js',
                                'jsbn/base64.js',
-                               'rsaauth_min.js'
+                               'BackendLoginFormRsaEncryption.js'
                        );
-                       $content = '';
                        foreach ($files as $file) {
-                               $content .= '<script type="text/javascript" src="' . \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . $javascriptPath . $file . '"></script>';
+                               $pageRenderer->addJsFooterFile($javascriptPath . $file);
                        }
+
+                       return '<form action="index.php" id="typo3-login-form" method="post" name="loginform">';
                }
-               return $content;
+               return $form;
        }
-
 }
index c5fe3b5..067009a 100644 (file)
@@ -20,7 +20,6 @@ if (!defined('TYPO3_MODE')) {
 
 // Add a hook to the BE login form
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/index.php']['loginFormHook'][$_EXTKEY] = 'TYPO3\\CMS\\Rsaauth\\Hook\\LoginFormHook->getLoginFormTag';
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/index.php']['loginScriptHook'][$_EXTKEY] = 'TYPO3\\CMS\\Rsaauth\\Hook\\LoginFormHook->getLoginScripts';
 // Add hook for user setup module
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/setup/mod/index.php']['setupScriptHook'][$_EXTKEY] = 'TYPO3\\CMS\\Rsaauth\\Hook\\UserSetupHook->getLoginScripts';
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/setup/mod/index.php']['modifyUserDataBeforeSave'][$_EXTKEY] = 'TYPO3\\CMS\\Rsaauth\\Hook\\UserSetupHook->decryptPassword';
diff --git a/typo3/sysext/rsaauth/resources/BackendLoginFormRsaEncryption.js b/typo3/sysext/rsaauth/resources/BackendLoginFormRsaEncryption.js
new file mode 100644 (file)
index 0000000..7d47588
--- /dev/null
@@ -0,0 +1,108 @@
+/**
+ * Object that handles RSA encryption and submission of the form
+ */
+TYPO3RsaBackendLogin = {
+
+       /**
+        * Field in which users enter their password
+        */
+       userPasswordField: document.loginform.p_field,
+
+       /**
+        * Field that is used by TYPO3 to evaluate the password during login process
+        */
+       typo3PasswordField: document.loginform.userident,
+
+       /**
+        * Replace event handler of submit button
+        */
+       initialize: function() {
+               var submitButton = $('t3-login-submit');
+               Event.stopObserving(
+                       submitButton,
+                       'click',
+                       TYPO3BackendLogin.showLoginProcess
+               );
+               Event.observe(
+                       submitButton,
+                       'click',
+                       TYPO3RsaBackendLogin.handleFormSubmitRequest
+               );
+       },
+
+       /**
+        * Fetches a new public key by Ajax and encrypts the password for transmission
+        *
+        * @param event
+        */
+       handleFormSubmitRequest: function(event) {
+               event.preventDefault();
+               // Call the original event handler
+               TYPO3BackendLogin.showLoginProcess();
+
+               Ext.Ajax.request({
+                       url: TYPO3.settings.ajaxUrls['BackendLogin::getRsaPublicKey'],
+                       params: {
+                               'skipSessionUpdate': 1
+                       },
+                       method: 'GET',
+                       success: TYPO3RsaBackendLogin.handlePublicKeyResponse
+               });
+       },
+
+       /**
+        * Parses the Json response and triggers submission of the form
+        *
+        * @param response Ajax response object
+        */
+       handlePublicKeyResponse: function(response) {
+               var publicKey = Ext.util.JSON.decode(response.responseText);
+               if (publicKey.publicKeyModulus && publicKey.exponent) {
+                       TYPO3RsaBackendLogin.encryptPasswordAndSubmitForm(publicKey);
+               } else {
+                       alert('No public key could be generated. Please inform your TYPO3 administrator to check the OpenSSL settings.');
+               }
+       },
+
+       /**
+        * Uses the public key with the RSA library to encrypt the password.
+        *
+        * @param publicKey
+        */
+       encryptPasswordAndSubmitForm: function(publicKey) {
+               var form, rsa, inputField;
+
+               rsa = new RSAKey();
+               rsa.setPublic(publicKey.publicKeyModulus, publicKey.exponent);
+               var encryptedPassword = rsa.encrypt(TYPO3RsaBackendLogin.userPasswordField.value);
+
+               // Reset user password field to prevent it from being submitted
+               TYPO3RsaBackendLogin.userPasswordField.value = '';
+               TYPO3RsaBackendLogin.typo3PasswordField.value = 'rsa:' + hex2b64(encryptedPassword);
+
+               // Create a hidden input field to fake pressing the submit button
+               inputField = TYPO3RsaBackendLogin.getHiddenField('commandLI', 'Submit');
+               form = $('typo3-login-form');
+               form.appendChild(inputField);
+
+               // Submit the form
+               form.submit();
+       },
+
+       /**
+        * Creates a new hidden field DOM element
+        *
+        * @param name Name attribute of the field
+        * @param value Value attribute of the field
+        * @returns {HTMLElement}
+        */
+       getHiddenField: function(name, value) {
+               var input = document.createElement("input");
+               input.setAttribute("type", "hidden");
+               input.setAttribute("name", name);
+               input.setAttribute("value", value);
+               return input;
+       }
+};
+
+Ext.onReady(TYPO3RsaBackendLogin.initialize, TYPO3RsaBackendLogin);
index 4c95e33..34e1780 100644 (file)
@@ -1,3 +1,4 @@
+// Deprecated since 6.2 will be removed 2 versions later
 function tx_rsaauth_encrypt() {
        var rsa = new RSAKey();
        rsa.setPublic(document.loginform.n.value, document.loginform.e.value);
index 25c5062..ac038a6 100644 (file)
@@ -1 +1,2 @@
+// Deprecated since 6.2 will be removed 2 versions later
 function tx_rsaauth_encrypt(){var rsa=new RSAKey();rsa.setPublic(document.loginform.n.value,document.loginform.e.value);var username=document.loginform.username.value;var password=document.loginform.p_field.value;var res=rsa.encrypt(password);document.loginform.p_field.value="";document.loginform.e.value="";document.loginform.n.value="";if(res){document.loginform.userident.value="rsa:"+hex2b64(res)}}function tx_rsaauth_feencrypt(form){if(form.pass.value.match(/^rsa:/)||form.n.value==""||form.e.value==""){return}var rsa=new RSAKey();rsa.setPublic(form.n.value,form.e.value);var username=form.user.value;var password=form.pass.value;var res=rsa.encrypt(password);form.pass.value="";form.e.value="";form.n.value="";if(res){form.pass.value="rsa:"+hex2b64(res)}}function tx_rsaauth_encryptUserSetup(){var rsa=new RSAKey();rsa.setPublic(document.usersetup.n.value,document.usersetup.e.value);var password=document.getElementById("field_password").value;var password2=document.getElementById("field_password2").value;if(password||password2){var res=rsa.encrypt(password);var res2=rsa.encrypt(password2);if(res&&res2){document.getElementById("field_password").value="rsa:"+hex2b64(res);document.getElementById("field_password2").value="rsa:"+hex2b64(res2)}}return false};