Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encrypt POST data before sending it to sentry #32

Merged
merged 7 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/functionaltests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version: [ 7.3, 7.4, 8.0, 8.1 ]
php-version: [ 7.4, 8.0, 8.1 ]
flow-version: [ 7.3, 8.0 ]
exclude:
# Disable Flow 8.0 on PHP 7, as 8.0 is required
- php-version: 7.3
flow-version: 8.0
- php-version: 7.4
flow-version: 8.0

Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version: [ 7.3, 7.4, 8.0, 8.1 ]
php-version: [ 7.4, 8.0, 8.1 ]
flow-version: [ 7.3, 8.0 ]
exclude:
# Disable Flow 8.0 on PHP 7, as 8.0 is required
- php-version: 7.3
flow-version: 8.0
- php-version: 7.4
flow-version: 8.0

Expand Down
43 changes: 43 additions & 0 deletions Classes/Controller/EncryptedPayloadController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Netlogix\Sentry\Controller;

use function json_decode;
use function json_encode;
use const JSON_PRETTY_PRINT;
use Neos\Flow\Mvc\Controller\ActionController;
use Netlogix\Sentry\Encryption\EncryptionService;
use Netlogix\Sentry\Encryption\Sealed;

class EncryptedPayloadController extends ActionController
{
protected EncryptionService $encryptionService;

public function injectEncryptionService(EncryptionService $encryptionService): void
{
$this->encryptionService = $encryptionService;
}

/**
* @param string $encryptedData
* @param string $initializationVector
* @param string $envelopeKey
*/
public function decryptAction(
string $encryptedData,
string $initializationVector,
string $envelopeKey
): string {
$sealed = Sealed::fromArray([
'encryptedData' => $encryptedData,
'initializationVector' => $initializationVector,
'envelopeKey' => $envelopeKey,
]);
$unencrypted = $this->encryptionService->open($sealed);
$this->response->setContentType('application/json');

return json_encode(json_decode($unencrypted, true), JSON_PRETTY_PRINT);
}
}
114 changes: 114 additions & 0 deletions Classes/Encryption/EncryptionService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

declare(strict_types=1);

namespace Netlogix\Sentry\Encryption;

use function http_build_query;
use Neos\Flow\Http\BaseUriProvider;
use Neos\Flow\Security\Cryptography\RsaWalletServiceInterface;
use Neos\Flow\Security\Cryptography\RsaWalletServicePhp;
use Neos\Flow\Security\Exception\InvalidKeyPairIdException;
use Neos\Utility\ObjectAccess;
use function openssl_open;
use function openssl_random_pseudo_bytes;
use function openssl_seal;
use Psr\Http\Message\UriInterface;

class EncryptionService
{
const ALGORITHM = 'AES256';

private string $rsaKeyFingerprint;

private string $encryptionModuleUri;

private RsaWalletServiceInterface $rsaWallet;

private BaseUriProvider $baseUriProvider;

public function injectSettings(array $settings): void
{
$privacySettings = $settings['privacy'] ?? [];
$this->rsaKeyFingerprint = (string) ($privacySettings['rsaKeyFingerprint'] ?? '');
$this->encryptionModuleUri = (string) ($privacySettings['encryptionModuleUri'] ?? '');
}

public function injectRsaWalletService(RsaWalletServiceInterface $rsaWallet): void
{
$this->rsaWallet = $rsaWallet;
}

public function injectBaseUriProvider(BaseUriProvider $baseUriProvider): void
{
$this->baseUriProvider = $baseUriProvider;
}

public function seal(string $unencryptedData): Sealed
{
$publicKeyString = $this->getKeyString('publicKey');

$initializationVector = openssl_random_pseudo_bytes(32);

openssl_seal(
$unencryptedData,
$encryptedData,
$envelopeKeys,
[$publicKeyString],
self::ALGORITHM,
$initializationVector
);

return new Sealed($encryptedData, $initializationVector, $envelopeKeys[0]);
}

public function open(Sealed $package): string
{
$privateKeyString = $this->getKeyString('privateKey');

$encryptedData = $package->getEncryptedData();
$envelopeKey = $package->getEnvelopeKey();
$initializationVector = $package->getInitializationVector();

openssl_open(
$encryptedData,
$unencryptedData,
$envelopeKey,
$privateKeyString,
self::ALGORITHM,
$initializationVector
);

return $unencryptedData;
}

public function getEncryptionUriForSealedPayload(Sealed $sealed): UriInterface
{
return $this->baseUriProvider
->getConfiguredBaseUriOrFallbackToCurrentRequest()
->withPath($this->encryptionModuleUri)
->withQuery(http_build_query($sealed->toArray()));
}

/**
* @param 'privateKey' | 'publicKey' $slotName
*/
private function getKeyString(string $slotName): string
{
assert($this->rsaWallet instanceof RsaWalletServicePhp);
// Prime key pair, male rsaWallet load the key pair
$this->rsaWallet->getPublicKey($this->rsaKeyFingerprint);
// Private property
$keys = ObjectAccess::getProperty($this->rsaWallet, 'keys', true);
// Property path in plain array
$keyString = ObjectAccess::getPropertyPath(
$keys,
sprintf('%s.%s.keyString', $this->rsaKeyFingerprint, $slotName)
);
if (!\is_string($keyString)) {
throw new InvalidKeyPairIdException('Invalid key fingerprint given', 1693231337);
}

return $keyString;
}
}
61 changes: 61 additions & 0 deletions Classes/Encryption/Sealed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Netlogix\Sentry\Encryption;

use function base64_decode;
use function base64_encode;
use Neos\Flow\Annotations as Flow;

/**
* @Flow\Proxy(false)
*/
final class Sealed
{
private string $encryptedData;

private string $initializationVector;

private string $envelopeKey;

public function __construct(string $encryptedData, string $initializationVector, string $envelopeKeys)
{
$this->encryptedData = $encryptedData;
$this->initializationVector = $initializationVector;
$this->envelopeKey = $envelopeKeys;
}

public static function fromArray(array $package): self
{
return new self(
$package['encryptedData'] ? base64_decode($package['encryptedData'], true) : '',
$package['initializationVector'] ? base64_decode($package['initializationVector'], true) : '',
$package['envelopeKey'] ? base64_decode($package['envelopeKey'], true) : ''
);
}

public function toArray(): array
{
return [
'encryptedData' => base64_encode($this->encryptedData),
'initializationVector' => base64_encode($this->initializationVector),
'envelopeKey' => base64_encode($this->envelopeKey),
];
}

public function getEncryptedData(): string
{
return $this->encryptedData;
}

public function getInitializationVector(): string
{
return $this->initializationVector;
}

public function getEnvelopeKey(): string
{
return $this->envelopeKey;
}
}
64 changes: 64 additions & 0 deletions Classes/EventProcessor/EncryptedPayload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Netlogix\Sentry\EventProcessor;

use function json_encode;
use Neos\Flow\Annotations as Flow;
use Netlogix\Sentry\Encryption\EncryptionService;
use Sentry\Event;
use Sentry\EventHint;

/**
* @Flow\Scope("singleton")
*/
final class EncryptedPayload implements EventProcessor
{
private EncryptionService $encryption;

private bool $encryptPostBody;

public function injectEncryptionService(EncryptionService $encryption): void
{
$this->encryption = $encryption;
}

public function injectSettings(array $settings): void
{
$privacyCettings = $settings['privacy'] ?? [];
$this->encryptPostBody = (bool) ($privacyCettings['encryptPostBody'] ?? false);
}

public function rewriteEvent(Event $event, EventHint $hint): Event
{
if (!$this->encryptPostBody) {
return $event;
}

$request = $event->getRequest();

$data = $request['data'] ?? [];
if (!$data || !is_array($data)) {
return $event;
}

$unencrypted = (string) json_encode($data, \JSON_PRETTY_PRINT);

$encrypted = $this->encryption
->seal($unencrypted);
$uri = (string) $this->encryption
->getEncryptionUriForSealedPayload($encrypted);

$request['data'] = [
'__ENCRYPTED__DATA__' => $encrypted->toArray(),
];
$event->setRequest($request);

$extra = $event->getExtra() ?? [];
$extra['Encrypted POST Data'] = $uri;
$event->setExtra($extra);

return $event;
}
}
13 changes: 13 additions & 0 deletions Classes/EventProcessor/EventProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Netlogix\Sentry\EventProcessor;

use Sentry\Event;
use Sentry\EventHint;

interface EventProcessor
{
public function rewriteEvent(Event $event, EventHint $hint): Event;
}
23 changes: 22 additions & 1 deletion Classes/Integration/NetlogixIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ public function setupOnce(): void
return $event;
}

return self::handleEvent($event, $hint);
$event = self::handleEvent($event, $hint);
$event = self::encryptPostBody($event, $hint);

return $event;
});
}

Expand Down Expand Up @@ -178,4 +181,22 @@ private static function configureScopeForEvent(Event $event, EventHint $hint): v
}
}

/**
* FIXME: Remove this method, replace by yaml configuration once `EventProcessor` chain is implemented
*
* @see https://github.com/netlogix/Netlogix.Sentry/issues/29
*
* TODO:
* - Crate `EventProcessor` chain as YAML configuration
* - Execute `EventProcessor` (instanciate and call) in self::configureScopeForEvent()
* - Replace Every ScopeProvider::collect*() method by individual classes
* - Configure `EncryptedPayload` as `EventProcessor` in YAML
* - Remove this method
*/
public static function encryptPostBody(Event $event, EventHint $hint): Event
{
$encryptedPayload = Bootstrap::$staticObjectManager->get(\Netlogix\Sentry\EventProcessor\EncryptedPayload::class);
return $encryptedPayload->rewriteEvent($event, $hint);
}

}
20 changes: 20 additions & 0 deletions Configuration/Policy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
privilegeTargets:

'Neos\Flow\Security\Authorization\Privilege\Method\MethodPrivilege':

'Netlogix.Sentry:Backend.EncryptedPayload':
matcher: 'method(Netlogix\Sentry\Controller\EncryptedPayloadController->.*())'

roles:

'Neos.Flow:Anonymous':
privileges:
-
privilegeTarget: 'Netlogix.Sentry:Backend.EncryptedPayload'
permission: DENY

'Neos.Neos:Administrator':
privileges:
-
privilegeTarget: 'Netlogix.Sentry:Backend.EncryptedPayload'
permission: GRANT
9 changes: 9 additions & 0 deletions Configuration/Routes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- name: 'HashToken Login'
uriPattern: 'neos/sentry/show-parameters'
defaults:
'@package': 'Netlogix.Sentry'
'@controller': 'EncryptedPayload'
'@action': 'decrypt'
'@format': 'html'
appendExceedingArguments: TRUE
httpMethods: ['GET']
Loading
Loading