[BUGFIX] Fetch RSA public key by Ajax before FE login 64/30364/11
authorFrans Saris <franssaris@gmail.com>
Fri, 23 May 2014 13:29:02 +0000 (15:29 +0200)
committerMarkus Klein <klein.t3@mfc-linz.at>
Tue, 10 Jun 2014 11:17:09 +0000 (13:17 +0200)
Instead of generating and saving the public/private keys for
an RSA enabled login form on rendering the form the keys are
fetched on submit. This resolves the issue with outdated keys
and the problem that the login box on first page doesn't work
anymore when a page in a second tab is opened.

Resolves: #59041
Releases: 6.2
Change-Id: I0ad13775f029a29f6f67e302a1a63f3860e902b5
Reviewed-on: https://review.typo3.org/30364
Reviewed-by: Xavier Perseguers
Tested-by: Xavier Perseguers
Reviewed-by: Zbigniew Jacko
Tested-by: Zbigniew Jacko
Reviewed-by: Steffen Ritter
Reviewed-by: Markus Klein
Tested-by: Markus Klein
typo3/sysext/rsaauth/Classes/Hook/FrontendLoginHook.php
typo3/sysext/rsaauth/ext_localconf.php
typo3/sysext/rsaauth/resources/FrontendLoginFormRsaEncryption.js [new file with mode: 0644]
typo3/sysext/rsaauth/resources/FrontendLoginFormRsaEncryption.min.js [new file with mode: 0644]
typo3/sysext/rsaauth/resources/Private/Php/FrontendLoginRsaPublicKey.php [new file with mode: 0644]
typo3/sysext/rsaauth/resources/rsaauth.js

index 741c7f6..7ac3435 100644 (file)
@@ -41,7 +41,7 @@ class FrontendLoginHook {
                if (trim($GLOBALS['TYPO3_CONF_VARS']['FE']['loginSecurityLevel']) === 'rsa') {
                        $backend = \TYPO3\CMS\Rsaauth\Backend\BackendFactory::getBackend();
                        if ($backend) {
-                               $result[0] = 'tx_rsaauth_feencrypt(this);';
+                               $result[0] = 'return TYPO3FrontendLoginFormRsaEncryption.submitForm(this, TYPO3FrontendLoginFormRsaEncryptionPublicKeyUrl);';
                                $javascriptPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath('rsaauth') . 'resources/';
                                $files = array(
                                        'jsbn/jsbn.js',
@@ -49,24 +49,14 @@ class FrontendLoginHook {
                                        'jsbn/rng.js',
                                        'jsbn/rsa.js',
                                        'jsbn/base64.js',
-                                       'rsaauth_min.js'
+                                       'FrontendLoginFormRsaEncryption.min.js'
                                );
-
-                               $additionalHeader = '';
+                               $eIdUrl = \TYPO3\CMS\Core\Utility\GeneralUtility::quoteJSvalue($GLOBALS['TSFE']->absRefPrefix . 'index.php?eID=FrontendLoginRsaPublicKey');
+                               $additionalHeader = '<script type="text/javascript">var TYPO3FrontendLoginFormRsaEncryptionPublicKeyUrl = ' . $eIdUrl . ';</script>';
                                foreach ($files as $file) {
                                        $additionalHeader .= '<script type="text/javascript" src="' . \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . $javascriptPath . $file . '"></script>';
                                }
                                $GLOBALS['TSFE']->additionalHeaderData['rsaauth_js'] = $additionalHeader;
-
-                               // 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
-                               $result[1] .= '<input type="hidden" id="rsa_n" name="n" value="' . htmlspecialchars($keyPair->getPublicKeyModulus()) . '" />';
-                               $result[1] .= '<input type="hidden" id="rsa_e" name="e" value="' . sprintf('%x', $keyPair->getExponent()) . '" />';
                        }
                }
                return $result;
index 067009a..2d9ac57 100644 (file)
@@ -34,4 +34,8 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['displ
        FALSE
 );
 
+// eID for FrontendLoginRsaPublicKey
+$GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include']['FrontendLoginRsaPublicKey'] =
+       'EXT:rsaauth/resources/Private/Php/FrontendLoginRsaPublicKey.php';
+
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']['constructPostProcess'][] = 'TYPO3\\CMS\\Rsaauth\\Hook\\BackendHookForAjaxLogin->addRsaJsLibraries';
\ No newline at end of file
diff --git a/typo3/sysext/rsaauth/resources/FrontendLoginFormRsaEncryption.js b/typo3/sysext/rsaauth/resources/FrontendLoginFormRsaEncryption.js
new file mode 100644 (file)
index 0000000..311ba3c
--- /dev/null
@@ -0,0 +1,141 @@
+/**
+ * Object that handles RSA encryption and submission of the FE login form
+ */
+TYPO3FrontendLoginFormRsaEncryption = function() {
+
+       var rsaFrontendLogin = function(form, publicKeyEndpointUrl) {
+
+               /**
+                * Submitted form element
+                */
+               this.form = form;
+
+               /**
+                * XMLHttpRequest
+                */
+               this.xhr = null;
+
+               /**
+                * Endpoint URL to fetch the public key for encryption
+                */
+               this.publicKeyEndpointUrl = publicKeyEndpointUrl;
+
+               /**
+                * Field in which users enter their password
+                */
+               this.userPasswordField = form.pass;
+
+               /**
+                * Fetches a new public key by Ajax and encrypts the password for transmission
+                */
+               this.handleFormSubmitRequest = function() {
+                       var rsaFrontendLogin = this;
+                       this.ajaxCall(
+                               this.publicKeyEndpointUrl,
+                               function(response) {
+                                       rsaFrontendLogin.handlePublicKeyResponse(response, rsaFrontendLogin);
+                               }
+                       );
+               };
+
+               /**
+                * Do Ajax call to fetch RSA public key
+                */
+               this.ajaxCall = function(url, callback) {
+
+                       // abort previous request, only last request/generated key pair can be used
+                       if (this.xhr) {
+                               this.xhr.abort();
+                       }
+
+                       if (typeof XMLHttpRequest !== 'undefined') {
+                               this.xhr = new XMLHttpRequest();
+                       } else {
+                               var versions = [
+                                       "MSXML2.XmlHttp.5.0",
+                                       "MSXML2.XmlHttp.4.0",
+                                       "MSXML2.XmlHttp.3.0",
+                                       "MSXML2.XmlHttp.2.0",
+                                       "Microsoft.XmlHttp"
+                               ];
+                               for (var i = 0, len = versions.length; i < len; i++) {
+                                       try {
+                                               this.xhr = new ActiveXObject(versions[i]);
+                                               break;
+                                       } catch(e) {}
+                               }
+                       }
+
+                       this.xhr.onreadystatechange = function() {
+                               // only process requests that are ready and have a status (not aborted)
+                               if (this.readyState === 4 && this.status > 0) {
+                                       callback(this);
+                               }
+                       };
+
+                       this.xhr.open('GET', url, true);
+                       this.xhr.send('');
+               };
+
+               /**
+                * Parses the response and triggers submission of the form
+                *
+                * @param response Ajax response object
+                * @param rsaFrontendLogin current processed object
+                */
+               this.handlePublicKeyResponse = function(response, rsaFrontendLogin) {
+                       var publicKey = response.responseText.split(':');
+                       if (publicKey[0] && publicKey[1]) {
+                               rsaFrontendLogin.encryptPasswordAndSubmitForm(publicKey[0], publicKey[1]);
+                       } 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 publicKeyModulus
+                * @param exponent
+                */
+               this.encryptPasswordAndSubmitForm = function(publicKeyModulus, exponent) {
+                       var rsa, encryptedPassword;
+
+                       rsa = new RSAKey();
+                       rsa.setPublic(publicKeyModulus, exponent);
+                       encryptedPassword = rsa.encrypt(this.userPasswordField.value);
+
+                       // replace password value with encrypted password
+                       this.userPasswordField.value = 'rsa:' + hex2b64(encryptedPassword);
+
+                       // Submit the form again but now with encrypted pass
+                       document.createElement("form").submit.call(this.form);
+               };
+       };
+
+       /**
+        * Encrypt password on submit
+        *
+        * @param form
+        * @param publicKeyEndpointUrl
+        * @return boolean
+        */
+       this.submitForm = function(form, publicKeyEndpointUrl) {
+
+               if (!form.rsaFrontendLogin) {
+                       form.rsaFrontendLogin = new rsaFrontendLogin(form, publicKeyEndpointUrl);
+               }
+
+               // if pass is not encrypted yet fetch public key and encrypt pass
+               if (!form.pass.value.match(/^rsa:/) ) {
+                       form.rsaFrontendLogin.handleFormSubmitRequest();
+                       return false;
+
+               // pass is encrypted so form can be submitted
+               } else {
+                       return true;
+               }
+       };
+
+       return this;
+}();
diff --git a/typo3/sysext/rsaauth/resources/FrontendLoginFormRsaEncryption.min.js b/typo3/sysext/rsaauth/resources/FrontendLoginFormRsaEncryption.min.js
new file mode 100644 (file)
index 0000000..f3618c2
--- /dev/null
@@ -0,0 +1,2 @@
+/** Object that handles RSA encryption and submission of the FE login form */
+TYPO3FrontendLoginFormRsaEncryption=function(){var e=function(e,t){this.form=e;this.xhr=null;this.publicKeyEndpointUrl=t;this.userPasswordField=e.pass;this.handleFormSubmitRequest=function(){var e=this;this.ajaxCall(this.publicKeyEndpointUrl,function(t){e.handlePublicKeyResponse(t,e)})};this.ajaxCall=function(e,t){if(this.xhr){this.xhr.abort()}if(typeof XMLHttpRequest!=="undefined"){this.xhr=new XMLHttpRequest}else{var n=["MSXML2.XmlHttp.5.0","MSXML2.XmlHttp.4.0","MSXML2.XmlHttp.3.0","MSXML2.XmlHttp.2.0","Microsoft.XmlHttp"];for(var r=0,i=n.length;r<i;r++){try{this.xhr=new ActiveXObject(n[r]);break}catch(s){}}}this.xhr.onreadystatechange=function(){if(this.readyState===4&&this.status>0){t(this)}};this.xhr.open("GET",e,true);this.xhr.send("")};this.handlePublicKeyResponse=function(e,t){var n=e.responseText.split(":");if(n[0]&&n[1]){t.encryptPasswordAndSubmitForm(n[0],n[1])}else{alert("No public key could be generated. Please inform your TYPO3 administrator to check the OpenSSL settings.")}};this.encryptPasswordAndSubmitForm=function(e,t){var n,r;n=new RSAKey;n.setPublic(e,t);r=n.encrypt(this.userPasswordField.value);this.userPasswordField.value="rsa:"+hex2b64(r);document.createElement("form").submit.call(this.form)}};this.submitForm=function(t,n){if(!t.rsaFrontendLogin){t.rsaFrontendLogin=new e(t,n)}if(!t.pass.value.match(/^rsa:/)){t.rsaFrontendLogin.handleFormSubmitRequest();return false}else{return true}};return this}()
\ No newline at end of file
diff --git a/typo3/sysext/rsaauth/resources/Private/Php/FrontendLoginRsaPublicKey.php b/typo3/sysext/rsaauth/resources/Private/Php/FrontendLoginRsaPublicKey.php
new file mode 100644 (file)
index 0000000..43e0a1b
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+if (!defined('TYPO3_MODE')) {
+       die('Access denied.');
+}
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2014 Frans Saris <franssaris@gmail.com>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/** @var \TYPO3\CMS\Rsaauth\Backend\AbstractBackend $backend */
+$backend = \TYPO3\CMS\Rsaauth\Backend\BackendFactory::getBackend();
+if ($backend !== NULL) {
+       $keyPair = $backend->createNewKeyPair();
+       $storage = \TYPO3\CMS\Rsaauth\Storage\StorageFactory::getStorage();
+       $storage->put($keyPair->getPrivateKey());
+       session_commit();
+
+       echo $keyPair->getPublicKeyModulus() . ':' . sprintf('%x', $keyPair->getExponent()) . ':';
+}
index 34e1780..ec2c1e7 100644 (file)
@@ -18,6 +18,7 @@ function tx_rsaauth_encrypt() {
        }
 }
 
+// Deprecated since 6.2 will be removed 2 versions later
 function tx_rsaauth_feencrypt(form) {
        // check if the form was already sent (see #40085)
        if (form.pass.value.match(/^rsa:/) || form.n.value == '' || form.e.value == '') {