[FEATURE] ext:install Verify checksum of downloaded core 92/24492/5
authorChristian Kuhn <lolli@schwarzbu.ch>
Tue, 8 Oct 2013 21:21:44 +0000 (23:21 +0200)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Thu, 10 Oct 2013 13:32:31 +0000 (15:32 +0200)
https://get.typo3.org/json provides checksums of the file content
of each TYPO3 CMS release. The expected sha1 is now compared with
the actual sha1 of the downloaded files to protect upgrades from
broken downloads and some attack vectors.

Change-Id: I1ec604ed2ef5f53abc930ff360ca2d7267e3c64d
Resolves: #52618
Releases: 6.2
Reviewed-on: https://review.typo3.org/24492
Reviewed-by: Sascha Egerer
Tested-by: Sascha Egerer
Reviewed-by: Markus Klein
Reviewed-by: Anja Leichsenring
Tested-by: Anja Leichsenring
typo3/sysext/install/Classes/Controller/Action/Ajax/CoreUpdateVerifyChecksum.php [new file with mode: 0644]
typo3/sysext/install/Classes/Controller/AjaxController.php
typo3/sysext/install/Classes/Service/CoreUpdateService.php
typo3/sysext/install/Classes/Service/CoreVersionService.php
typo3/sysext/install/Resources/Public/Javascript/Install.js
typo3/sysext/install/Tests/Unit/Service/CoreVersionServiceTest.php

diff --git a/typo3/sysext/install/Classes/Controller/Action/Ajax/CoreUpdateVerifyChecksum.php b/typo3/sysext/install/Classes/Controller/Action/Ajax/CoreUpdateVerifyChecksum.php
new file mode 100644 (file)
index 0000000..63f839a
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+namespace TYPO3\CMS\Install\Controller\Action\Ajax;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2013 Christian Kuhn <lolli@schwarzbu.ch>
+ *  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!
+ ***************************************************************/
+
+use TYPO3\CMS\Install\Controller\Action;
+
+/**
+ * Verify checksum of a downloaded core
+ */
+class CoreUpdateVerifyChecksum extends AbstractCoreUpdate implements Action\ActionInterface {
+
+       /**
+        * Handle this action
+        *
+        * @return string content
+        */
+       public function handle() {
+               $this->initializeCoreUpdate();
+               $this->view->assignMultiple(
+                       array(
+                               'success' => $this->coreUpdateService->verifyFileChecksum($this->getVersionToHandle()),
+                               'status' => $this->coreUpdateService->getMessages(),
+                       )
+               );
+               return $this->view->render();
+       }
+}
\ No newline at end of file
index 224f26e..1941e4b 100644 (file)
@@ -49,6 +49,7 @@ class AjaxController extends AbstractController {
                'coreUpdateIsUpdateAvailable',
                'coreUpdateCheckPreConditions',
                'coreUpdateDownload',
+               'coreUpdateVerifyChecksum',
                'coreUpdateUnpack',
                'coreUpdateMove',
                'coreUpdateActivate',
index 848b4ff..08acd8d 100644 (file)
@@ -239,7 +239,7 @@ class CoreUpdateService {
         */
        public function downloadVersion($version) {
                $downloadUri = $this->downloadBaseUri . $version;
-               $fileLocation = $this->downloadTargetPath . $version . '.tar.gz';
+               $fileLocation = $this->getDownloadTarGzTargetPath($version);
 
                $messages = array();
                $success = TRUE;
@@ -274,6 +274,45 @@ class CoreUpdateService {
        }
 
        /**
+        * Verify checksum of downloaded version
+        *
+        * @param string $version A downloaded version to check
+        * @return boolean TRUE on success
+        */
+       public function verifyFileChecksum($version) {
+               $fileLocation = $this->getDownloadTarGzTargetPath($version);
+               $expectedChecksum = $this->coreVersionService->getTarGzSha1OfVersion($version);
+
+               $messages = array();
+               $success = TRUE;
+
+               if (!file_exists($fileLocation)) {
+                       $success = FALSE;
+                       /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
+                       $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
+                       $message->setTitle('Downloaded core not found');
+                       $messages[] = $message;
+               } else {
+                       $actualChecksum = sha1_file($fileLocation);
+                       if ($actualChecksum !== $expectedChecksum) {
+                               $success = FALSE;
+                               /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
+                               $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
+                               $message->setTitle('New core checksum mismatch');
+                               $message->setMessage(
+                                       'The official TYPO3 CMS version system on https://get.typo3.org expects a sha1 checksum of '
+                                       . $expectedChecksum . ' from the content of the downloaded new core version ' . $version . '.'
+                                       . ' The actual checksum is ' . $actualChecksum . '. The update is stopped. This may be a'
+                                       . ' failed download, an attack, or an issue with the typo3.org infrastructure.'
+                               );
+                               $messages[] = $message;
+                       }
+               }
+               $this->messages = $messages;
+               return $success;
+       }
+
+       /**
         * Unpack a downloaded core
         *
         * @param string $version A version to unpack
@@ -408,4 +447,14 @@ class CoreUpdateService {
                $this->messages = $messages;
                return $success;
        }
+
+       /**
+        * Absolute path of downloaded .tar.gz
+        *
+        * @param string $version A version number
+        * @return string
+        */
+       protected function getDownloadTarGzTargetPath($version) {
+               return $this->downloadTargetPath . $version . '.tar.gz';
+       }
 }
\ No newline at end of file
index 6464d43..600fdd3 100644 (file)
@@ -99,6 +99,30 @@ class CoreVersionService {
        }
 
        /**
+        * Get sha1 of a version from version matrix
+        *
+        * @param string $version A version to get sha1 of
+        * @return string sha1 of version
+        * @throws Exception\CoreVersionServiceException
+        */
+       public function getTarGzSha1OfVersion($version) {
+               $this->ensureVersionExistsInMatrix($version);
+
+               $minorVersion = $this->getMinorVersion($version);
+               $versionMatrix = $this->getVersionMatrix();
+
+               if (empty($versionMatrix[$minorVersion]['releases'][$version]['checksums']['tar']['sha1'])) {
+                       throw new Exception\CoreVersionServiceException(
+                               'Release sha1 of version ' . $version . ' not found in version matrix.'
+                               . ' This is probably a bug on get.typo3.org.',
+                               1381263173
+                       );
+               }
+
+               return $versionMatrix[$minorVersion]['releases'][$version]['checksums']['tar']['sha1'];
+       }
+
+       /**
         * Get current installed version number
         *
         * @return string
index 4c1ce04..0b7cb73 100644 (file)
@@ -49,6 +49,11 @@ TYPO3.Install.coreUpdate = {
                coreUpdateDownload: {
                        loadingMessage: 'Downloading new core',
                        finishMessage: 'Core download finished',
+                       nextActionName: 'coreUpdateVerifyChecksum'
+               },
+               coreUpdateVerifyChecksum: {
+                       loadingMessage: 'Verifying checksum of downloaded core',
+                       finishMessage: 'Checksum verified',
                        nextActionName: 'coreUpdateUnpack'
                },
                coreUpdateUnpack: {
index 44ae76c..a4c1e30 100644 (file)
@@ -87,6 +87,49 @@ class CoreVersionServiceTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
        }
 
        /**
+        * @test
+        * @expectedException \TYPO3\CMS\Install\Service\Exception\CoreVersionServiceException
+        */
+       public function getTarGzSha1OfVersionThrowsExceptionIfSha1DoesNotExistInMatrix() {
+               /** @var $instance \TYPO3\CMS\Install\Service\CoreVersionService|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
+               $instance = $this->getAccessibleMock(
+                       'TYPO3\\CMS\\Install\\Service\\CoreVersionService',
+                       array('getVersionMatrix', 'getMinorVersion', 'ensureVersionExistsInMatrix'),
+                       array(),
+                       '',
+                       FALSE
+               );
+               $versionMatrix = array(
+                       '6.2' => array(
+                               'releases' => array(
+                                       '6.2.42' => array(),
+                               ),
+                       ),
+               );
+               $instance->expects($this->once())->method('getMinorVersion')->will($this->returnValue('6.2'));
+               $instance->expects($this->any())->method('getVersionMatrix')->will($this->returnValue($versionMatrix));
+               $this->assertTrue($instance->getTarGzSha1OfVersion('6.2.42'));
+       }
+
+       /**
+        * @test
+        */
+       public function getTarGzSha1OfVersionReturnsSha1OfSpecifiedVersion() {
+               $versionMatrixFixtureFile = __DIR__ . '/Fixtures/VersionMatrixFixture.php';
+               /** @var $instance \TYPO3\CMS\Install\Service\CoreVersionService|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
+               $instance = $this->getAccessibleMock(
+                       'TYPO3\\CMS\\Install\\Service\\CoreVersionService',
+                       array('getVersionMatrix', 'getMinorVersion', 'ensureVersionExistsInMatrix'),
+                       array(),
+                       '',
+                       FALSE
+               );
+               $instance->expects($this->any())->method('getMinorVersion')->will($this->returnValue('6.2'));
+               $instance->expects($this->any())->method('getVersionMatrix')->will($this->returnValue(require($versionMatrixFixtureFile)));
+               $this->assertSame('3dc156eed4b99577232f537d798a8691493f8a83', $instance->getTarGzSha1OfVersion('6.2.0alpha3'));
+       }
+
+       /**
         * Whitebox test of API method: This tests multiple methods, only 'current version' and 'version matrix' are mocked.
         *
         * @test