sessionless-1.x-dev/src/SessionlessEncryptAndSign.php

src/SessionlessEncryptAndSign.php
<?php

namespace Drupal\sessionless;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\sessionless\KeyStorage\KeyStorageInterface;
use Drupal\sessionless\Serialization\JsonSafeCompressingObjectAwareSerializationInterface;
use Drupal\sessionless\Utility\CacheTool;
use Drupal\sessionless\Utility\UnauthenticatedFooterParser;
use ParagonIE\Paserk\Operations\Key\SealingSecretKey;
use ParagonIE\Paserk\PaserkException;
use ParagonIE\Paserk\Types\Seal;
use ParagonIE\Paseto\Builder;
use ParagonIE\Paseto\Exception\PasetoException;
use ParagonIE\Paseto\Keys\SymmetricKey;
use ParagonIE\Paseto\Parser;

/**
 * Sessionless encryption service.
 */
final class SessionlessEncryptAndSign implements SessionlessInterface {

  public function __construct(
    protected KeyStorageInterface $keyStorage,
    protected JsonSafeCompressingObjectAwareSerializationInterface $serializer,
    protected CacheBackendInterface $cache,
  ) {}

  public function encode(mixed $data): string {
    $sealingSecretKey = $this->keyStorage->getSealingSecretKey();
    // @todo Add key change cache tag instead.
    $cid = serialize([__METHOD__, $sealingSecretKey, $data]);
    return CacheTool::getOrCompute($this->cache, $cid,
      fn() => $this->doEncode($sealingSecretKey, $data));
  }

  private function doEncode(SealingSecretKey $sealingSecretKey, mixed $data): string {
    $sealer = new Seal($sealingSecretKey->getPublicKey());
    $sessionKey = SymmetricKey::generate();
    $sealedSessionKey = $sealer->encode($sessionKey);
    $dataToken = Builder::getLocal($sessionKey)
      ->set('data', $this->serializer->encode($data))
      ->withFooterArray(['wpk' => $sealedSessionKey]);
    return $dataToken->toString();
  }

  public function decode(string $token): mixed {
    $sealingSecretKey = $this->keyStorage->getSealingSecretKey();
    $cid = serialize([__METHOD__, $sealingSecretKey, $token]);
    return CacheTool::getOrCompute($this->cache, $cid,
      fn() => $this->doDecode($sealingSecretKey, $token));
  }

  private function doDecode(SealingSecretKey $sealingSecretKey, string $token): mixed {
    // Get sealed session key from raw footer data. This is safe because the
    // session key is sealed with our secret private key.
    $footerArray = (new UnauthenticatedFooterParser($token))->getFooterArray();
    $sealedSessionKey = $footerArray['wpk'] ?? NULL;
    if (!$sealedSessionKey) {
      return NULL;
    }
    $sealer = new Seal($sealingSecretKey->getPublicKey(), $sealingSecretKey);
    try {
      $sessionKey = $sealer->decode($sealedSessionKey);
    }
    catch (PaserkException) {
      return NULL;
    }
    if (!$sessionKey instanceof SymmetricKey) {
      return NULL;
    }
    try {
      $parser = Parser::getLocal($sessionKey);
      $jsonToken = $parser->parse($token);
      $serialized = $jsonToken->get('data');
      // Unserializing is no attack vector, as at this point the cryptographic
      // data signature is validated, and whatever classes are unserialized, it
      // is us, that put it in there.
      $data = $this->serializer->decode($serialized);
    }
    catch (PasetoException) {
      $data = NULL;
    }
    return $data;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc