activitypub-1.0.x-dev/src/Services/ActivityPubSignature.php

src/Services/ActivityPubSignature.php
<?php

namespace Drupal\activitypub\Services;

use ActivityPhp\Server;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\File\FileSystemInterface;
use phpseclib3\Crypt\RSA;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;

class ActivityPubSignature implements ActivityPubSignatureInterface {

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * A logger instance.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * The file system service.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * ActivityPubUtility constructor
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\File\FileSystemInterface $file_system
   *   The file system service.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger
   */
  public function __construct(ConfigFactoryInterface $config_factory, FileSystemInterface $file_system, LoggerInterface $logger) {
    $this->configFactory = $config_factory;
    $this->fileSystem = $file_system;
    $this->logger = $logger;
  }

  /**
   * {@inheritdoc}
   */
  public function getPublicKey($path) {
    $key_path = rtrim(activitypub_keys_path(), '/') . '/' . $path . '/public.pem';
    return file_get_contents($key_path);
  }

  /**
   * {@inheritdoc}
   */
  public function getPrivateKey($path) {
    $key_path = rtrim(activitypub_keys_path(), '/') . '/' . $path . '/private.pem';
    return file_get_contents($key_path);
  }

  /**
   * {@inheritdoc}
   */
  public function createSignature($private_key_path, $host, $path, $digest, $date = NULL) {
    if (!isset($date)) {
      $date = gmdate('D, d M Y H:i:s T', time());
    }
    try {
      $plaintext = "(request-target): post $path\nhost: $host\ndate: $date\ndigest: $digest";

      $rsa = RSA::loadPrivateKey($this->getPrivateKey($private_key_path)
        )->withHash("sha256")->withPadding(RSA::SIGNATURE_PKCS1);

      return $rsa->sign($plaintext);
    }
    catch (\Exception $e) {
      $this->logger->error('Error creating the signature: @message', ['@message' => $e->getMessage()]);
      return NULL;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function createDigest($message) {
    return 'SHA-256=' . base64_encode(hash('sha256', $message, true));
  }

  /**
   * {@inheritdoc}
   */
  public function generateKeys($path) {
    $return = FALSE;

    try {

      $private_key = RSA::createKey(4096);

      $dir_path = rtrim(activitypub_keys_path(), '/') . '/' . $path;
      $this->fileSystem->prepareDirectory($dir_path, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);

      foreach (['public' => 'publickey', 'private' => 'privatekey'] as $filename => $rsakey) {

        // Key uri.
        $key_uri = "$dir_path/$filename.pem";

        // Write key content to key file.
        $this->fileSystem->saveData(($rsakey == "publickey") ? (string) $private_key->getPublicKey() : $private_key, $key_uri, FileSystemInterface::EXISTS_REPLACE);

        // Set correct permission to key file.
        $this->fileSystem->chmod($key_uri, 0600);

      }

      $return = TRUE;
    }
    catch (\Exception $e) {
      $this->logger->error('Error generating keys: @message', ['@message' => $e->getMessage()]);
    }

    return $return;
  }

  /**
   * {@inheritdoc}
   */
  public function deleteKeys($path) {
    $dir_path = rtrim(activitypub_keys_path(), '/') . '/' . $path;
    $this->fileSystem->deleteRecursive($dir_path);
  }

  /**
   * {@inheritdoc}
   */
  public function verifySignature(Request $request, string $actor, Server $server) {
    $verified = FALSE;

    try {

      $actor = $server->actor($actor);
      $publicKeyPem = $actor->getPublicKeyPem();

      $signature = '';
      $strings = [];
      $header_signature = $request->headers->get('signature');
      $this->debug('request: ' . $request);
      $this->debug("method:\n" . $request->getMethod());
      $this->debug("path:\n" . $request->getPathInfo());
      $this->debug("signature:\n" . $header_signature);
      $sig_explode = explode(",", $header_signature ?: '');
      $this->debug("sig explode:\n" . print_r($sig_explode, 1));
      foreach ($sig_explode as $entry) {
        $entry_ex = explode("=", $entry ?: '');
        if (!empty($entry_ex[0]) && !empty($entry_ex[1])) {
          if ($entry_ex[0] == 'headers') {
            $ex = explode(" ", str_replace('"', '', $entry_ex[1]));
            $this->debug("header explode: \n" . print_r($ex, 1));
            foreach ($ex as $header_key) {
              if ($header_key == '(request-target)') {
                $parsed = parse_url($request->getRequestUri());
                $strings[] = '(request-target): ' . strtolower($request->getMethod()) . ' ' . $parsed['path'];
              }
              else {
                $strings[] = $header_key .': ' . $request->headers->get($header_key);
              }
            }
          }
          if ($entry_ex[0] == 'signature') {
            $signature = str_replace(['"', 'signature='], '', $entry);
          }
        }
      }
      $this->debug("strings:\n" . print_r($strings, 1));
      $this->debug("signature: \n" . print_r($signature, 1));

      $data = implode("\n", $strings);
      $this->debug("data:\n" . print_r($data, 1));
      //'Signature' => 'keyId="' . $keyId . '#main-key",headers="(request-target) host date digest",signature="' . base64_encode($signature) . '",algorithm="rsa-sha256"',
      //$plaintext = "(request-target): post $path\nhost: $host\ndate: $date\ndigest: $digest";

      if (!empty($data) && !empty($signature)) {
        $rsa = RSA::loadPublicKey($publicKeyPem)
          ->withHash('sha256')
          ->withPadding(RSA::SIGNATURE_PKCS1);
        $verified = $rsa->verify($data, base64_decode($signature, true));
      }
    }
    catch (\Exception $e) {
      $store = TRUE;
      $message = $e->getMessage();
      if ($this->configFactory->get('activitypub.settings')->get('log_ignore_error_signature')) {
        foreach ($this->ignoreSignatureExceptionMessages() as $m) {
          if (strpos($message, $m) !== FALSE) {
            $store = FALSE;
            break;
          }
        }
      }

      if ($store && $this->configFactory->get('activitypub.settings')->get('log_error_signature')) {
        $this->logger->error('Signature verifying exception: @message', ['@message' => $message]);
      }
    }

    return $verified;
  }

  /**
   * Returns exceptions we'll ignore.
   *
   * @return string[]
   */
  protected function ignoreSignatureExceptionMessages() {
    return [
      // Client error: `GET example.com` resulted in a `410 Gone` response: {"error":"Gone"}
      '410 Gone',
      // Client error: `GET example.com` resulted in a `404 Not Found` response:
      '404 Not Found'
    ];
  }

  /**
   * Debug helper function.
   *
   * @param $var
   */
  protected function debug($var) {
    //print_r($var);
    //echo "<br /><br />-------------------------------<br /><br />";
  }

}

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

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