Skip to content

Commit

Permalink
feat: Encrypt POST data before sending it to sentry
Browse files Browse the repository at this point in the history
  • Loading branch information
stephanschuler authored and paxuclus committed Oct 24, 2023
1 parent 3b7a878 commit 7b452e0
Show file tree
Hide file tree
Showing 17 changed files with 709 additions and 2 deletions.
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 @@ -31,7 +31,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 @@ -80,4 +83,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']
23 changes: 23 additions & 0 deletions Configuration/Settings.Encryption.Backend.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Neos:
Flow:

mvc:
routes:
'Netlogix.Sentry':
position: before Neos.Neos

# Use the following configuration snippet when this package is used in conjunction with the neos/neos CMS.
# Use something similar when another authentication provider will be used.
# Unfortunately, this cannot be done preemptively.
#
# security:
# authentication:
# providers:
# 'Neos.Neos:Backend':
# requestPatterns:
# 'Netlogix.Sentry:ShowEncryptedPayload':
# pattern: ControllerObjectName
# patternOptions:
# controllerObjectNamePattern: 'Netlogix\Sentry\Controller\.*'


Loading

0 comments on commit 7b452e0

Please sign in to comment.