InboxWorker.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <?php
  2. namespace App\Jobs\InboxPipeline;
  3. use App\Profile;
  4. use App\Util\ActivityPub\{
  5. Helpers,
  6. HttpSignature,
  7. Inbox
  8. };
  9. use Illuminate\Bus\Queueable;
  10. use Illuminate\Contracts\Queue\ShouldQueue;
  11. use Illuminate\Foundation\Bus\Dispatchable;
  12. use Illuminate\Queue\InteractsWithQueue;
  13. use Illuminate\Queue\SerializesModels;
  14. use Zttp\Zttp;
  15. class InboxWorker implements ShouldQueue
  16. {
  17. use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
  18. protected $headers;
  19. protected $profile;
  20. protected $payload;
  21. public $timeout = 60;
  22. public $tries = 1;
  23. /**
  24. * Create a new job instance.
  25. *
  26. * @return void
  27. */
  28. public function __construct($headers, $payload)
  29. {
  30. $this->headers = $headers;
  31. $this->payload = $payload;
  32. }
  33. /**
  34. * Execute the job.
  35. *
  36. * @return void
  37. */
  38. public function handle()
  39. {
  40. $profile = null;
  41. $headers = $this->headers;
  42. $payload = json_decode($this->payload, true, 8);
  43. if(!isset($headers['signature']) || !isset($headers['date'])) {
  44. return;
  45. }
  46. if(empty($headers) || empty($payload)) {
  47. return;
  48. }
  49. if($this->verifySignature($headers, $payload) == true) {
  50. (new Inbox($headers, $profile, $payload))->handle();
  51. return;
  52. } else if($this->blindKeyRotation($headers, $payload) == true) {
  53. (new Inbox($headers, $profile, $payload))->handle();
  54. return;
  55. } else {
  56. return;
  57. }
  58. }
  59. protected function verifySignature($headers, $payload)
  60. {
  61. $body = $this->payload;
  62. $bodyDecoded = $payload;
  63. $signature = is_array($headers['signature']) ? $headers['signature'][0] : $headers['signature'];
  64. $date = is_array($headers['date']) ? $headers['date'][0] : $headers['date'];
  65. if(!$signature) {
  66. return;
  67. }
  68. if(!$date) {
  69. return;
  70. }
  71. if(!now()->parse($date)->gt(now()->subDays(1)) ||
  72. !now()->parse($date)->lt(now()->addDays(1))
  73. ) {
  74. return;
  75. }
  76. $signatureData = HttpSignature::parseSignatureHeader($signature);
  77. $keyId = Helpers::validateUrl($signatureData['keyId']);
  78. $id = Helpers::validateUrl($bodyDecoded['id']);
  79. $keyDomain = parse_url($keyId, PHP_URL_HOST);
  80. $idDomain = parse_url($id, PHP_URL_HOST);
  81. if(isset($bodyDecoded['object'])
  82. && is_array($bodyDecoded['object'])
  83. && isset($bodyDecoded['object']['attributedTo'])
  84. ) {
  85. if(parse_url($bodyDecoded['object']['attributedTo'], PHP_URL_HOST) !== $keyDomain) {
  86. return;
  87. abort(400, 'Invalid request');
  88. }
  89. }
  90. if(!$keyDomain || !$idDomain || $keyDomain !== $idDomain) {
  91. return;
  92. abort(400, 'Invalid request');
  93. }
  94. $actor = Profile::whereKeyId($keyId)->first();
  95. if(!$actor) {
  96. $actorUrl = is_array($bodyDecoded['actor']) ? $bodyDecoded['actor'][0] : $bodyDecoded['actor'];
  97. $actor = Helpers::profileFirstOrNew($actorUrl);
  98. }
  99. if(!$actor) {
  100. return;
  101. }
  102. $pkey = openssl_pkey_get_public($actor->public_key);
  103. $inboxPath = "/f/inbox";
  104. list($verified, $headers) = HttpSignature::verify($pkey, $signatureData, $headers, $inboxPath, $body);
  105. if($verified == 1) {
  106. return true;
  107. } else {
  108. return false;
  109. }
  110. }
  111. protected function blindKeyRotation($headers, $payload)
  112. {
  113. $signature = is_array($headers['signature']) ? $headers['signature'][0] : $headers['signature'];
  114. $date = is_array($headers['date']) ? $headers['date'][0] : $headers['date'];
  115. if(!$signature) {
  116. return;
  117. }
  118. if(!$date) {
  119. return;
  120. }
  121. if(!now()->parse($date)->gt(now()->subDays(1)) ||
  122. !now()->parse($date)->lt(now()->addDays(1))
  123. ) {
  124. return;
  125. }
  126. $signatureData = HttpSignature::parseSignatureHeader($signature);
  127. $keyId = Helpers::validateUrl($signatureData['keyId']);
  128. $actor = Profile::whereKeyId($keyId)->whereNotNull('remote_url')->first();
  129. if(!$actor) {
  130. return;
  131. }
  132. if(Helpers::validateUrl($actor->remote_url) == false) {
  133. return;
  134. }
  135. $res = Zttp::timeout(5)->withHeaders([
  136. 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
  137. 'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
  138. ])->get($actor->remote_url);
  139. $res = json_decode($res->body(), true, 8);
  140. if($res['publicKey']['id'] !== $actor->key_id) {
  141. return;
  142. }
  143. $actor->public_key = $res['publicKey']['publicKeyPem'];
  144. $actor->save();
  145. return $this->verifySignature($headers, $payload);
  146. }
  147. }