浏览代码

Update AP helpers

Daniel Supernault 11 月之前
父节点
当前提交
8fea821504
共有 2 个文件被更改,包括 181 次插入148 次删除
  1. 45 25
      app/Util/ActivityPub/Helpers.php
  2. 136 123
      app/Util/ActivityPub/HttpSignature.php

+ 45 - 25
app/Util/ActivityPub/Helpers.php

@@ -25,6 +25,8 @@ use Cache;
 use Carbon\Carbon;
 use Illuminate\Support\Str;
 use Illuminate\Validation\Rule;
+use League\Uri\Exceptions\UriException;
+use League\Uri\Uri;
 use Purify;
 use Validator;
 
@@ -153,61 +155,74 @@ class Helpers
         return in_array($url, $audience['to']) || in_array($url, $audience['cc']);
     }
 
-    public static function validateUrl($url)
+    public static function validateUrl($url = null, $disableDNSCheck = false)
     {
-        if (is_array($url)) {
+        if (is_array($url) && ! empty($url)) {
             $url = $url[0];
         }
+        if (! $url || strlen($url) === 0) {
+            return false;
+        }
+        try {
+            $uri = Uri::new($url);
 
-        $hash = hash('sha256', $url);
-        $key = "helpers:url:valid:sha256-{$hash}";
-
-        $valid = Cache::remember($key, 900, function () use ($url) {
-            $localhosts = [
-                '127.0.0.1', 'localhost', '::1',
-            ];
-
-            if (strtolower(mb_substr($url, 0, 8)) !== 'https://') {
+            if (! $uri) {
                 return false;
             }
 
-            if (substr_count($url, '://') !== 1) {
+            if ($uri->getScheme() !== 'https') {
                 return false;
             }
 
-            if (mb_substr($url, 0, 8) !== 'https://') {
-                $url = 'https://'.substr($url, 8);
+            $host = $uri->getHost();
+
+            if (! $host || $host === '') {
+                return false;
             }
 
-            $valid = filter_var($url, FILTER_VALIDATE_URL);
+            if (! filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
+                return false;
+            }
 
-            if (! $valid) {
+            if (! str_contains($host, '.')) {
                 return false;
             }
 
-            $host = parse_url($valid, PHP_URL_HOST);
+            $localhosts = [
+                'localhost',
+                '127.0.0.1',
+                '::1',
+                'broadcasthost',
+                'ip6-localhost',
+                'ip6-loopback',
+            ];
 
             if (in_array($host, $localhosts)) {
                 return false;
             }
 
-            if (config('security.url.verify_dns')) {
-                if (DomainService::hasValidDns($host) === false) {
+            if ($disableDNSCheck !== true && app()->environment() === 'production' && (bool) config('security.url.verify_dns')) {
+                $hash = hash('sha256', $host);
+                $key = "helpers:url:valid-dns:sha256-{$hash}";
+                $domainValidDns = Cache::remember($key, 14440, function () use ($host) {
+                    return DomainService::hasValidDns($host);
+                });
+                if (! $domainValidDns) {
                     return false;
                 }
             }
 
-            if (app()->environment() === 'production') {
+            if ($disableDNSCheck !== true && app()->environment() === 'production') {
                 $bannedInstances = InstanceService::getBannedDomains();
                 if (in_array($host, $bannedInstances)) {
                     return false;
                 }
             }
 
-            return $url;
-        });
-
-        return $valid;
+            return $uri->toString();
+        } catch (UriException $e) {
+            return false;
+        }
     }
 
     public static function validateLocalUrl($url)
@@ -215,7 +230,12 @@ class Helpers
         $url = self::validateUrl($url);
         if ($url == true) {
             $domain = config('pixelfed.domain.app');
-            $host = parse_url($url, PHP_URL_HOST);
+
+            $uri = Uri::new($url);
+            $host = $uri->getHost();
+            if (! $host || empty($host)) {
+                return false;
+            }
             $url = strtolower($domain) === strtolower($host) ? $url : false;
 
             return $url;

+ 136 - 123
app/Util/ActivityPub/HttpSignature.php

@@ -2,146 +2,159 @@
 
 namespace App\Util\ActivityPub;
 
-use Cache, Log;
 use App\Models\InstanceActor;
 use App\Profile;
-use \DateTime;
-
-class HttpSignature {
-
-  /*
-   * source: https://github.com/aaronpk/Nautilus/blob/master/app/ActivityPub/HTTPSignature.php
-   * thanks aaronpk!
-   */
-
-  public static function sign(Profile $profile, $url, $body = false, $addlHeaders = []) {
-    if($body) {
-      $digest = self::_digest($body);
-    }
-    $user = $profile;
-    $headers = self::_headersToSign($url, $body ? $digest : false);
-    $headers = array_merge($headers, $addlHeaders);
-    $stringToSign = self::_headersToSigningString($headers);
-    $signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
-    $key = openssl_pkey_get_private($user->private_key);
-    openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
-    $signature = base64_encode($signature);
-    $signatureHeader = 'keyId="'.$user->keyId().'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"';
-    unset($headers['(request-target)']);
-    $headers['Signature'] = $signatureHeader;
-
-    return self::_headersToCurlArray($headers);
-  }
-
-  public static function instanceActorSign($url, $body = false, $addlHeaders = [], $method = 'post')
-  {
-    $keyId = config('app.url') . '/i/actor#main-key';
-    $privateKey = Cache::rememberForever(InstanceActor::PKI_PRIVATE, function() {
-      return InstanceActor::first()->private_key;
-    });
-    if($body) {
-      $digest = self::_digest($body);
-    }
-    $headers = self::_headersToSign($url, $body ? $digest : false, $method);
-    $headers = array_merge($headers, $addlHeaders);
-    $stringToSign = self::_headersToSigningString($headers);
-    $signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
-    $key = openssl_pkey_get_private($privateKey);
-    openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
-    $signature = base64_encode($signature);
-    $signatureHeader = 'keyId="'.$keyId.'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"';
-    unset($headers['(request-target)']);
-    $headers['Signature'] = $signatureHeader;
-
-    return $headers;
-  }
-
-  public static function parseSignatureHeader($signature) {
-    $parts = explode(',', $signature);
-    $signatureData = [];
-
-    foreach($parts as $part) {
-      if(preg_match('/(.+)="(.+)"/', $part, $match)) {
-        $signatureData[$match[1]] = $match[2];
-      }
+use Cache;
+use DateTime;
+
+class HttpSignature
+{
+    /*
+     * source: https://github.com/aaronpk/Nautilus/blob/master/app/ActivityPub/HTTPSignature.php
+     * thanks aaronpk!
+     */
+
+    public static function sign(Profile $profile, $url, $body = false, $addlHeaders = [])
+    {
+        if ($body) {
+            $digest = self::_digest($body);
+        }
+        $user = $profile;
+        $headers = self::_headersToSign($url, $body ? $digest : false);
+        $headers = array_merge($headers, $addlHeaders);
+        $stringToSign = self::_headersToSigningString($headers);
+        $signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
+        $key = openssl_pkey_get_private($user->private_key);
+        openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
+        $signature = base64_encode($signature);
+        $signatureHeader = 'keyId="'.$user->keyId().'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"';
+        unset($headers['(request-target)']);
+        $headers['Signature'] = $signatureHeader;
+
+        return self::_headersToCurlArray($headers);
     }
 
-    if(!isset($signatureData['keyId'])) {
-      return [
-        'error' => 'No keyId was found in the signature header. Found: '.implode(', ', array_keys($signatureData))
-      ];
+    public static function instanceActorSign($url, $body = false, $addlHeaders = [], $method = 'post')
+    {
+        $keyId = config('app.url').'/i/actor#main-key';
+        $privateKey = Cache::rememberForever(InstanceActor::PKI_PRIVATE, function () {
+            return InstanceActor::first()->private_key;
+        });
+        if ($body) {
+            $digest = self::_digest($body);
+        }
+        $headers = self::_headersToSign($url, $body ? $digest : false, $method);
+        $headers = array_merge($headers, $addlHeaders);
+        $stringToSign = self::_headersToSigningString($headers);
+        $signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
+        $key = openssl_pkey_get_private($privateKey);
+        openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
+        $signature = base64_encode($signature);
+        $signatureHeader = 'keyId="'.$keyId.'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"';
+        unset($headers['(request-target)']);
+        $headers['Signature'] = $signatureHeader;
+
+        return $headers;
     }
 
-    if(!filter_var($signatureData['keyId'], FILTER_VALIDATE_URL)) {
-      return [
-        'error' => 'keyId is not a URL: '.$signatureData['keyId']
-      ];
+    public static function parseSignatureHeader($signature)
+    {
+        $parts = explode(',', $signature);
+        $signatureData = [];
+
+        foreach ($parts as $part) {
+            if (preg_match('/(.+)="(.+)"/', $part, $match)) {
+                $signatureData[$match[1]] = $match[2];
+            }
+        }
+
+        if (! isset($signatureData['keyId'])) {
+            return [
+                'error' => 'No keyId was found in the signature header. Found: '.implode(', ', array_keys($signatureData)),
+            ];
+        }
+
+        if (! filter_var($signatureData['keyId'], FILTER_VALIDATE_URL)) {
+            return [
+                'error' => 'keyId is not a URL: '.$signatureData['keyId'],
+            ];
+        }
+
+        if (! Helpers::validateUrl($signatureData['keyId'])) {
+            return [
+                'error' => 'keyId is not a URL: '.$signatureData['keyId'],
+            ];
+        }
+
+        if (! isset($signatureData['headers']) || ! isset($signatureData['signature'])) {
+            return [
+                'error' => 'Signature is missing headers or signature parts',
+            ];
+        }
+
+        return $signatureData;
     }
 
-    if(!isset($signatureData['headers']) || !isset($signatureData['signature'])) {
-      return [
-        'error' => 'Signature is missing headers or signature parts'
-      ];
+    public static function verify($publicKey, $signatureData, $inputHeaders, $path, $body)
+    {
+        $digest = 'SHA-256='.base64_encode(hash('sha256', $body, true));
+        $headersToSign = [];
+        foreach (explode(' ', $signatureData['headers']) as $h) {
+            if ($h == '(request-target)') {
+                $headersToSign[$h] = 'post '.$path;
+            } elseif ($h == 'digest') {
+                $headersToSign[$h] = $digest;
+            } elseif (isset($inputHeaders[$h][0])) {
+                $headersToSign[$h] = $inputHeaders[$h][0];
+            }
+        }
+        $signingString = self::_headersToSigningString($headersToSign);
+
+        $verified = openssl_verify($signingString, base64_decode($signatureData['signature']), $publicKey, OPENSSL_ALGO_SHA256);
+
+        return [$verified, $signingString];
     }
 
-    return $signatureData;
-  }
-
-  public static function verify($publicKey, $signatureData, $inputHeaders, $path, $body) {
-    $digest = 'SHA-256='.base64_encode(hash('sha256', $body, true));
-    $headersToSign = [];
-    foreach(explode(' ',$signatureData['headers']) as $h) {
-      if($h == '(request-target)') {
-        $headersToSign[$h] = 'post '.$path;
-      } elseif($h == 'digest') {
-        $headersToSign[$h] = $digest;
-      } elseif(isset($inputHeaders[$h][0])) {
-        $headersToSign[$h] = $inputHeaders[$h][0];
-      }
+    private static function _headersToSigningString($headers)
+    {
+        return implode("\n", array_map(function ($k, $v) {
+            return strtolower($k).': '.$v;
+        }, array_keys($headers), $headers));
     }
-    $signingString = self::_headersToSigningString($headersToSign);
-
-    $verified = openssl_verify($signingString, base64_decode($signatureData['signature']), $publicKey, OPENSSL_ALGO_SHA256);
 
-    return [$verified, $signingString];
-  }
-
-  private static function _headersToSigningString($headers) {
-    return implode("\n", array_map(function($k, $v){
-             return strtolower($k).': '.$v;
-           }, array_keys($headers), $headers));
-  }
+    private static function _headersToCurlArray($headers)
+    {
+        return array_map(function ($k, $v) {
+            return "$k: $v";
+        }, array_keys($headers), $headers);
+    }
 
-  private static function _headersToCurlArray($headers) {
-    return array_map(function($k, $v){
-             return "$k: $v";
-           }, array_keys($headers), $headers);
-  }
+    private static function _digest($body)
+    {
+        if (is_array($body)) {
+            $body = json_encode($body);
+        }
 
-  private static function _digest($body) {
-    if(is_array($body)) {
-      $body = json_encode($body);
+        return base64_encode(hash('sha256', $body, true));
     }
-    return base64_encode(hash('sha256', $body, true));
-  }
 
-  protected static function _headersToSign($url, $digest = false, $method = 'post') {
-    $date = new DateTime('UTC');
+    protected static function _headersToSign($url, $digest = false, $method = 'post')
+    {
+        $date = new DateTime('UTC');
 
-    if(!in_array($method, ['post', 'get'])) {
-        throw new \Exception('Invalid method used to sign headers in HttpSignature');
-    }
-    $headers = [
-      '(request-target)' => $method . ' '.parse_url($url, PHP_URL_PATH),
-      'Host' => parse_url($url, PHP_URL_HOST),
-      'Date' => $date->format('D, d M Y H:i:s \G\M\T'),
-    ];
-
-    if($digest) {
-      $headers['Digest'] = 'SHA-256='.$digest;
-    }
+        if (! in_array($method, ['post', 'get'])) {
+            throw new \Exception('Invalid method used to sign headers in HttpSignature');
+        }
+        $headers = [
+            '(request-target)' => $method.' '.parse_url($url, PHP_URL_PATH),
+            'Host' => parse_url($url, PHP_URL_HOST),
+            'Date' => $date->format('D, d M Y H:i:s \G\M\T'),
+        ];
 
-    return $headers;
-  }
+        if ($digest) {
+            $headers['Digest'] = 'SHA-256='.$digest;
+        }
 
+        return $headers;
+    }
 }