From 70305f88c5652ef2e5c36cbd1ab0a9a8cd8ab92c Mon Sep 17 00:00:00 2001
From: Torben Hansen <derhansen@gmail.com>
Date: Wed, 6 Mar 2024 18:07:31 +0100
Subject: [PATCH] [TASK] Replace GeneralUtility::hmac usage in ext:form
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

With #102761, the new `HashService` has been introduced to ensure
that HMAC generation will always use an additional secret. As a
follow-up patch, `GeneralUtility::hmac` will be deprecated when all
usages in the core have been replaced with the new `HashService`.

With this change, usages of `GeneralUtility::hmac` in ext:form
are replaced by the new HashService.

Resolves: #103249
Related: #102761
Related: #103245
Releases: main
Change-Id: I636d224764890e31ef33038eb7ec2dd0472786c1
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/83266
Tested-by: core-ci <typo3@b13.com>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 ...HmacDataToFormElementPropertyConverter.php |  4 ++-
 ...taToPropertyCollectionElementConverter.php |  4 ++-
 .../FormDefinitionValidationService.php       |  7 +++--
 .../form/Classes/Hooks/ImportExportHook.php   |  6 ++--
 .../UploadedFileReferenceConverter.php        |  2 +-
 .../form/Classes/Slot/FilePersistenceSlot.php |  6 ++--
 .../Classes/Slot/ResourcePublicationSlot.php  |  5 +++-
 .../FormDefinitionValidationServiceTest.php   | 28 +++++++++++--------
 .../FormDefinitionArrayConverterTest.php      | 16 ++++++-----
 9 files changed, 49 insertions(+), 29 deletions(-)

diff --git a/typo3/sysext/form/Classes/Domain/Configuration/FormDefinition/Converters/AddHmacDataToFormElementPropertyConverter.php b/typo3/sysext/form/Classes/Domain/Configuration/FormDefinition/Converters/AddHmacDataToFormElementPropertyConverter.php
index 4f7ee9a7b5ba..ee659081ba2c 100644
--- a/typo3/sysext/form/Classes/Domain/Configuration/FormDefinition/Converters/AddHmacDataToFormElementPropertyConverter.php
+++ b/typo3/sysext/form/Classes/Domain/Configuration/FormDefinition/Converters/AddHmacDataToFormElementPropertyConverter.php
@@ -17,6 +17,7 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
 
+use TYPO3\CMS\Core\Crypto\HashService;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -36,10 +37,11 @@ class AddHmacDataToFormElementPropertyConverter extends AbstractConverter
         $lastKeySegment = array_pop($propertyPathParts);
         $propertyPathParts[] = '_orig_' . $lastKeySegment;
 
+        $hashService = GeneralUtility::makeInstance(HashService::class);
         $hmacValuePath = implode('.', array_merge($this->converterDto->getRenderablePathParts(), $propertyPathParts));
         $hmacValue = [
             'value' => $value,
-            'hmac' => GeneralUtility::hmac(serialize([$this->converterDto->getFormElementIdentifier(), $key, $value]), $this->sessionToken),
+            'hmac' => $hashService->hmac(serialize([$this->converterDto->getFormElementIdentifier(), $key, $value]), $this->sessionToken),
         ];
 
         $formDefinition = ArrayUtility::setValueByPath($formDefinition, $hmacValuePath, $hmacValue, '.');
diff --git a/typo3/sysext/form/Classes/Domain/Configuration/FormDefinition/Converters/AddHmacDataToPropertyCollectionElementConverter.php b/typo3/sysext/form/Classes/Domain/Configuration/FormDefinition/Converters/AddHmacDataToPropertyCollectionElementConverter.php
index e065b832ffd8..d1870ad0339f 100644
--- a/typo3/sysext/form/Classes/Domain/Configuration/FormDefinition/Converters/AddHmacDataToPropertyCollectionElementConverter.php
+++ b/typo3/sysext/form/Classes/Domain/Configuration/FormDefinition/Converters/AddHmacDataToPropertyCollectionElementConverter.php
@@ -17,6 +17,7 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
 
+use TYPO3\CMS\Core\Crypto\HashService;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -42,9 +43,10 @@ class AddHmacDataToPropertyCollectionElementConverter extends AbstractConverter
             $propertyPathParts
         ));
 
+        $hashService = GeneralUtility::makeInstance(HashService::class);
         $hmacValue = [
             'value' => $value,
-            'hmac' => GeneralUtility::hmac(
+            'hmac' => $hashService->hmac(
                 serialize([
                     $this->converterDto->getFormElementIdentifier(),
                     $this->converterDto->getPropertyCollectionName(),
diff --git a/typo3/sysext/form/Classes/Domain/Configuration/FormDefinitionValidationService.php b/typo3/sysext/form/Classes/Domain/Configuration/FormDefinitionValidationService.php
index 648a97c7c379..5ab33a36ff3f 100644
--- a/typo3/sysext/form/Classes/Domain/Configuration/FormDefinitionValidationService.php
+++ b/typo3/sysext/form/Classes/Domain/Configuration/FormDefinitionValidationService.php
@@ -17,6 +17,7 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Form\Domain\Configuration;
 
+use TYPO3\CMS\Core\Crypto\HashService;
 use TYPO3\CMS\Core\SingletonInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
@@ -223,7 +224,8 @@ class FormDefinitionValidationService implements SingletonInterface
         $this->checkHmacDataIntegrity($hmacData, $hmacContent, $sessionToken);
         $hmacContent[] = $propertyValue;
 
-        $expectedHash = GeneralUtility::hmac(serialize($hmacContent), $sessionToken);
+        $hashService = GeneralUtility::makeInstance(HashService::class);
+        $expectedHash = $hashService->hmac(serialize($hmacContent), $sessionToken);
         return hash_equals($expectedHash, $hmacData['hmac']);
     }
 
@@ -242,7 +244,8 @@ class FormDefinitionValidationService implements SingletonInterface
         }
 
         $hmacContent[] = $hmacData['value'] ?? '';
-        $expectedHash = GeneralUtility::hmac(serialize($hmacContent), $sessionToken);
+        $hashService = GeneralUtility::makeInstance(HashService::class);
+        $expectedHash = $hashService->hmac(serialize($hmacContent), $sessionToken);
 
         if (!hash_equals($expectedHash, $hmac)) {
             throw new PropertyException('Unauthorized modification of historical data. #1528538252', 1528538252);
diff --git a/typo3/sysext/form/Classes/Hooks/ImportExportHook.php b/typo3/sysext/form/Classes/Hooks/ImportExportHook.php
index edada2f4cef7..199f09545794 100644
--- a/typo3/sysext/form/Classes/Hooks/ImportExportHook.php
+++ b/typo3/sysext/form/Classes/Hooks/ImportExportHook.php
@@ -28,11 +28,11 @@ class ImportExportHook
         $fileRecord = $params['fileRecord'];
         $temporaryFile = $params['temporaryFile'];
 
-        $formPersistenceSlot = GeneralUtility::makeInstance(FilePersistenceSlot::class);
-        $formPersistenceSlot->allowInvocation(
+        $filePersistenceSlot = GeneralUtility::makeInstance(FilePersistenceSlot::class);
+        $filePersistenceSlot->allowInvocation(
             FilePersistenceSlot::COMMAND_FILE_ADD,
             implode(':', [$fileRecord['storage'], $fileRecord['identifier']]),
-            $formPersistenceSlot->getContentSignature(file_get_contents($temporaryFile))
+            $filePersistenceSlot->getContentSignature(file_get_contents($temporaryFile))
         );
     }
 }
diff --git a/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php b/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php
index 366d8891f2e6..5040592a1936 100644
--- a/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php
+++ b/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php
@@ -221,7 +221,7 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter
 
         $uploadFolder = $this->provideUploadFolder($uploadFolderId);
         // current folder name, derived from public random seed (`formSession`)
-        $currentName = 'form_' . GeneralUtility::hmac($seed, self::class);
+        $currentName = 'form_' . $this->hashService->hmac($seed, self::class);
         $uploadFolder = $this->provideTargetFolder($uploadFolder, $currentName);
         // sub-folder in $uploadFolder with 160 bit of derived entropy (.../form_<40-chars-hash>/actual.file)
         $uploadedFile = $uploadFolder->addUploadedFile($uploadInfo, $conflictMode);
diff --git a/typo3/sysext/form/Classes/Slot/FilePersistenceSlot.php b/typo3/sysext/form/Classes/Slot/FilePersistenceSlot.php
index a655dc434034..2c454ca7a2d2 100644
--- a/typo3/sysext/form/Classes/Slot/FilePersistenceSlot.php
+++ b/typo3/sysext/form/Classes/Slot/FilePersistenceSlot.php
@@ -18,6 +18,7 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Form\Slot;
 
 use TYPO3\CMS\Core\Attribute\AsEventListener;
+use TYPO3\CMS\Core\Crypto\HashService;
 use TYPO3\CMS\Core\Resource\Event\BeforeFileAddedEvent;
 use TYPO3\CMS\Core\Resource\Event\BeforeFileContentsSetEvent;
 use TYPO3\CMS\Core\Resource\Event\BeforeFileCreatedEvent;
@@ -26,7 +27,6 @@ use TYPO3\CMS\Core\Resource\Event\BeforeFileRenamedEvent;
 use TYPO3\CMS\Core\Resource\Event\BeforeFileReplacedEvent;
 use TYPO3\CMS\Core\Resource\FolderInterface;
 use TYPO3\CMS\Core\SingletonInterface;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager;
 
 /**
@@ -53,9 +53,11 @@ final class FilePersistenceSlot implements SingletonInterface
      */
     protected $allowedInvocations = [];
 
+    public function __construct(private readonly HashService $hashService) {}
+
     public function getContentSignature(string $content): string
     {
-        return GeneralUtility::hmac($content);
+        return $this->hashService->hmac($content, self::class);
     }
 
     /**
diff --git a/typo3/sysext/form/Classes/Slot/ResourcePublicationSlot.php b/typo3/sysext/form/Classes/Slot/ResourcePublicationSlot.php
index 9dd9413b3f3e..1200f8e4710f 100644
--- a/typo3/sysext/form/Classes/Slot/ResourcePublicationSlot.php
+++ b/typo3/sysext/form/Classes/Slot/ResourcePublicationSlot.php
@@ -19,6 +19,7 @@ namespace TYPO3\CMS\Form\Slot;
 
 use TYPO3\CMS\Core\Attribute\AsEventListener;
 use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Crypto\HashService;
 use TYPO3\CMS\Core\Resource\Event\GeneratePublicUrlForResourceEvent;
 use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\FileInterface;
@@ -40,6 +41,8 @@ class ResourcePublicationSlot implements SingletonInterface
      */
     protected $fileIdentifiers = [];
 
+    public function __construct(private readonly HashService $hashService) {}
+
     #[AsEventListener('form-framework/resource-getPublicUrl')]
     public function getPublicUrl(GeneratePublicUrlForResourceEvent $event): void
     {
@@ -77,7 +80,7 @@ class ResourcePublicationSlot implements SingletonInterface
             $queryParameterArray['t'] = 'p';
         }
 
-        $queryParameterArray['token'] = GeneralUtility::hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
+        $queryParameterArray['token'] = $this->hashService->hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
         $publicUrl = GeneralUtility::locationHeaderUrl(PathUtility::getAbsoluteWebPath(Environment::getPublicPath() . '/index.php'));
         $publicUrl .= '?' . http_build_query($queryParameterArray, '', '&', PHP_QUERY_RFC3986);
         return $publicUrl;
diff --git a/typo3/sysext/form/Tests/Unit/Domain/Configuration/FormDefinitionValidationServiceTest.php b/typo3/sysext/form/Tests/Unit/Domain/Configuration/FormDefinitionValidationServiceTest.php
index 00d5cb672dc6..b17a5024508b 100644
--- a/typo3/sysext/form/Tests/Unit/Domain/Configuration/FormDefinitionValidationServiceTest.php
+++ b/typo3/sysext/form/Tests/Unit/Domain/Configuration/FormDefinitionValidationServiceTest.php
@@ -19,6 +19,7 @@ namespace TYPO3\CMS\Form\Tests\Unit\Domain\Configuration;
 
 use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\Attributes\Test;
+use TYPO3\CMS\Core\Crypto\HashService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
 use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
@@ -28,10 +29,13 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
 final class FormDefinitionValidationServiceTest extends UnitTestCase
 {
+    protected HashService $hashService;
+
     protected function setUp(): void
     {
         parent::setUp();
         $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] = '12345';
+        $this->hashService = new HashService();
     }
 
     public function tearDown(): void
@@ -59,7 +63,7 @@ final class FormDefinitionValidationServiceTest extends UnitTestCase
             'label' => 'xxx',
             '_orig_label' => [
                 'value' => 'aaa',
-                'hmac' => GeneralUtility::hmac(serialize([$identifier, 'label', 'aaa']), $sessionToken),
+                'hmac' => $this->hashService->hmac(serialize([$identifier, 'label', 'aaa']), $sessionToken),
             ],
         ];
 
@@ -104,7 +108,7 @@ final class FormDefinitionValidationServiceTest extends UnitTestCase
             'label' => 'aaa',
             '_orig_label' => [
                 'value' => 'aaa',
-                'hmac' => GeneralUtility::hmac(serialize([$identifier, 'label', 'aaa']), $sessionToken),
+                'hmac' => $this->hashService->hmac(serialize([$identifier, 'label', 'aaa']), $sessionToken),
             ],
         ];
 
@@ -141,13 +145,13 @@ final class FormDefinitionValidationServiceTest extends UnitTestCase
             'identifier' => 'StringLength',
             '_orig_identifier' => [
                 'value' => 'StringLength',
-                'hmac' => GeneralUtility::hmac(serialize([$identifier, 'validators', 'StringLength', 'identifier', 'StringLength']), $sessionToken),
+                'hmac' => $this->hashService->hmac(serialize([$identifier, 'validators', 'StringLength', 'identifier', 'StringLength']), $sessionToken),
             ],
             'options' => [
                 'test' => 'xxx',
                 '_orig_test' => [
                     'value' => 'aaa',
-                    'hmac' => GeneralUtility::hmac(serialize([$identifier, 'validators', 'StringLength', 'options.test', 'aaa']), $sessionToken),
+                    'hmac' => $this->hashService->hmac(serialize([$identifier, 'validators', 'StringLength', 'options.test', 'aaa']), $sessionToken),
                 ],
             ],
         ];
@@ -179,7 +183,7 @@ final class FormDefinitionValidationServiceTest extends UnitTestCase
             'identifier' => 'StringLength',
             '_orig_identifier' => [
                 'value' => 'StringLength',
-                'hmac' => GeneralUtility::hmac(serialize([$identifier, 'validators', 'StringLength', 'identifier', 'StringLength']), $sessionToken),
+                'hmac' => $this->hashService->hmac(serialize([$identifier, 'validators', 'StringLength', 'identifier', 'StringLength']), $sessionToken),
             ],
             'options' => [
                 'test' => 'xxx',
@@ -210,13 +214,13 @@ final class FormDefinitionValidationServiceTest extends UnitTestCase
             'identifier' => 'StringLength',
             '_orig_identifier' => [
                 'value' => 'StringLength',
-                'hmac' => GeneralUtility::hmac(serialize([$identifier, 'validators', 'StringLength', 'identifier', 'StringLength']), $sessionToken),
+                'hmac' => $this->hashService->hmac(serialize([$identifier, 'validators', 'StringLength', 'identifier', 'StringLength']), $sessionToken),
             ],
             'options' => [
                 'test' => 'aaa',
                 '_orig_test' => [
                     'value' => 'aaa',
-                    'hmac' => GeneralUtility::hmac(serialize([$identifier, 'validators', 'StringLength', 'options.test', 'aaa']), $sessionToken),
+                    'hmac' => $this->hashService->hmac(serialize([$identifier, 'validators', 'StringLength', 'options.test', 'aaa']), $sessionToken),
                 ],
             ],
         ];
@@ -244,12 +248,13 @@ final class FormDefinitionValidationServiceTest extends UnitTestCase
         $sessionToken = '54321';
         $identifier = 'text-1';
 
+        $hashService = new HashService();
         $validationDto = new ValidationDto('standard', 'Text', $identifier);
         $formElement = [
             'test' => 'xxx',
             '_orig_test' => [
                 'value' => 'xxx',
-                'hmac' => GeneralUtility::hmac(serialize([$identifier, 'test', 'xxx']), $sessionToken),
+                'hmac' => $hashService->hmac(serialize([$identifier, 'test', 'xxx']), $sessionToken),
             ],
         ];
 
@@ -261,7 +266,7 @@ final class FormDefinitionValidationServiceTest extends UnitTestCase
             'test' => 'xxx1',
             '_orig_test' => [
                 'value' => 'xxx',
-                'hmac' => GeneralUtility::hmac(serialize([$identifier, 'test', 'xxx']), $sessionToken),
+                'hmac' => $hashService->hmac(serialize([$identifier, 'test', 'xxx']), $sessionToken),
             ],
         ];
 
@@ -420,11 +425,12 @@ final class FormDefinitionValidationServiceTest extends UnitTestCase
         $identifier = 'text-1';
 
         $validationDto = new ValidationDto('standard', 'Text', $identifier, null, 'validators', 'StringLength');
+        $hashService = new HashService();
         $formElement = [
             'test' => 'xxx',
             '_orig_test' => [
                 'value' => 'xxx',
-                'hmac' => GeneralUtility::hmac(serialize([$identifier, 'validators', 'StringLength', 'test', 'xxx']), $sessionToken),
+                'hmac' => $hashService->hmac(serialize([$identifier, 'validators', 'StringLength', 'test', 'xxx']), $sessionToken),
             ],
         ];
 
@@ -436,7 +442,7 @@ final class FormDefinitionValidationServiceTest extends UnitTestCase
             'test' => 'xxx1',
             '_orig_test' => [
                 'value' => 'xxx',
-                'hmac' => GeneralUtility::hmac(serialize([$identifier, 'validators', 'StringLength', 'test', 'xxx']), $sessionToken),
+                'hmac' => $hashService->hmac(serialize([$identifier, 'validators', 'StringLength', 'test', 'xxx']), $sessionToken),
             ],
         ];
 
diff --git a/typo3/sysext/form/Tests/Unit/Mvc/Property/TypeConverter/FormDefinitionArrayConverterTest.php b/typo3/sysext/form/Tests/Unit/Mvc/Property/TypeConverter/FormDefinitionArrayConverterTest.php
index 3d4b24d0e892..8daeaf7b0ecb 100644
--- a/typo3/sysext/form/Tests/Unit/Mvc/Property/TypeConverter/FormDefinitionArrayConverterTest.php
+++ b/typo3/sysext/form/Tests/Unit/Mvc/Property/TypeConverter/FormDefinitionArrayConverterTest.php
@@ -18,7 +18,7 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Form\Tests\Unit\Mvc\Property\TypeConverter;
 
 use PHPUnit\Framework\Attributes\Test;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Crypto\HashService;
 use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
 use TYPO3\CMS\Form\Domain\Configuration\FormDefinitionValidationService;
 use TYPO3\CMS\Form\Mvc\Property\TypeConverter\FormDefinitionArrayConverter;
@@ -28,11 +28,13 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 final class FormDefinitionArrayConverterTest extends UnitTestCase
 {
     protected bool $resetSingletonInstances = true;
+    protected HashService $hashService;
 
     public function setUp(): void
     {
         parent::setUp();
         $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] = '12345';
+        $this->hashService = new HashService();
     }
 
     #[Test]
@@ -55,11 +57,11 @@ final class FormDefinitionArrayConverterTest extends UnitTestCase
             ],
             '_orig_prototypeName' => [
                 'value' => 'standard',
-                'hmac' => GeneralUtility::hmac(serialize(['test', 'prototypeName', 'standard']), $sessionToken),
+                'hmac' => $this->hashService->hmac(serialize(['test', 'prototypeName', 'standard']), $sessionToken),
             ],
             '_orig_identifier' => [
                 'value' => 'test',
-                'hmac' => GeneralUtility::hmac(serialize(['test', 'identifier', 'test']), $sessionToken),
+                'hmac' => $this->hashService->hmac(serialize(['test', 'identifier', 'test']), $sessionToken),
             ],
         ];
 
@@ -170,11 +172,11 @@ final class FormDefinitionArrayConverterTest extends UnitTestCase
             'identifier' => 'test',
             '_orig_prototypeName' => [
                 'value' => 'standard',
-                'hmac' => GeneralUtility::hmac(serialize(['test', 'prototypeName', 'standard']), $sessionToken),
+                'hmac' => $this->hashService->hmac(serialize(['test', 'prototypeName', 'standard']), $sessionToken),
             ],
             '_orig_identifier' => [
                 'value' => 'test',
-                'hmac' => GeneralUtility::hmac(serialize(['test', 'identifier', 'test']), $sessionToken),
+                'hmac' => $this->hashService->hmac(serialize(['test', 'identifier', 'test']), $sessionToken),
             ],
         ];
 
@@ -199,11 +201,11 @@ final class FormDefinitionArrayConverterTest extends UnitTestCase
             'identifier' => 'xxx',
             '_orig_prototypeName' => [
                 'value' => 'standard',
-                'hmac' => GeneralUtility::hmac(serialize(['test', 'prototypeName', 'standard']), $sessionToken),
+                'hmac' => $this->hashService->hmac(serialize(['test', 'prototypeName', 'standard']), $sessionToken),
             ],
             '_orig_identifier' => [
                 'value' => 'test',
-                'hmac' => GeneralUtility::hmac(serialize(['test', 'prototypeName', 'test']), $sessionToken),
+                'hmac' => $this->hashService->hmac(serialize(['test', 'prototypeName', 'test']), $sessionToken),
             ],
         ];
 
-- 
GitLab