HttpSignature.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. <?php
  2. namespace App\Util\ActivityPub;
  3. use Log;
  4. use App\Profile;
  5. use \DateTime;
  6. class HttpSignature {
  7. /*
  8. * source: https://github.com/aaronpk/Nautilus/blob/master/app/ActivityPub/HTTPSignature.php
  9. * thanks aaronpk!
  10. */
  11. public static function sign(Profile $profile, $url, $body = false, $addlHeaders = []) {
  12. if($body) {
  13. $digest = self::_digest($body);
  14. }
  15. $user = $profile;
  16. $headers = self::_headersToSign($url, $body ? $digest : false);
  17. $headers = array_merge($headers, $addlHeaders);
  18. $stringToSign = self::_headersToSigningString($headers);
  19. $signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
  20. $key = openssl_pkey_get_private($user->private_key);
  21. openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
  22. $signature = base64_encode($signature);
  23. $signatureHeader = 'keyId="'.$user->keyId().'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"';
  24. unset($headers['(request-target)']);
  25. $headers['Signature'] = $signatureHeader;
  26. return self::_headersToCurlArray($headers);
  27. }
  28. public static function parseSignatureHeader($signature) {
  29. $parts = explode(',', $signature);
  30. $signatureData = [];
  31. foreach($parts as $part) {
  32. if(preg_match('/(.+)="(.+)"/', $part, $match)) {
  33. $signatureData[$match[1]] = $match[2];
  34. }
  35. }
  36. if(!isset($signatureData['keyId'])) {
  37. return [
  38. 'error' => 'No keyId was found in the signature header. Found: '.implode(', ', array_keys($signatureData))
  39. ];
  40. }
  41. if(!filter_var($signatureData['keyId'], FILTER_VALIDATE_URL)) {
  42. return [
  43. 'error' => 'keyId is not a URL: '.$signatureData['keyId']
  44. ];
  45. }
  46. if(!isset($signatureData['headers']) || !isset($signatureData['signature'])) {
  47. return [
  48. 'error' => 'Signature is missing headers or signature parts'
  49. ];
  50. }
  51. return $signatureData;
  52. }
  53. public static function verify($publicKey, $signatureData, $inputHeaders, $path, $body) {
  54. $digest = 'SHA-256='.base64_encode(hash('sha256', $body, true));
  55. $headersToSign = [];
  56. foreach(explode(' ',$signatureData['headers']) as $h) {
  57. if($h == '(request-target)') {
  58. $headersToSign[$h] = 'post '.$path;
  59. } elseif($h == 'digest') {
  60. $headersToSign[$h] = $digest;
  61. } elseif(isset($inputHeaders[$h][0])) {
  62. $headersToSign[$h] = $inputHeaders[$h][0];
  63. }
  64. }
  65. $signingString = self::_headersToSigningString($headersToSign);
  66. $verified = openssl_verify($signingString, base64_decode($signatureData['signature']), $publicKey, OPENSSL_ALGO_SHA256);
  67. return [$verified, $signingString];
  68. }
  69. private static function _headersToSigningString($headers) {
  70. return implode("\n", array_map(function($k, $v){
  71. return strtolower($k).': '.$v;
  72. }, array_keys($headers), $headers));
  73. }
  74. private static function _headersToCurlArray($headers) {
  75. return array_map(function($k, $v){
  76. return "$k: $v";
  77. }, array_keys($headers), $headers);
  78. }
  79. private static function _digest($body) {
  80. if(is_array($body)) {
  81. $body = json_encode($body);
  82. }
  83. return base64_encode(hash('sha256', $body, true));
  84. }
  85. protected static function _headersToSign($url, $digest = false) {
  86. $date = new DateTime('UTC');
  87. $headers = [
  88. '(request-target)' => 'post '.parse_url($url, PHP_URL_PATH),
  89. 'Date' => $date->format('D, d M Y H:i:s \G\M\T'),
  90. 'Host' => parse_url($url, PHP_URL_HOST),
  91. 'Accept' => 'application/activity+json, application/json',
  92. 'Content-Type' => 'application/activity+json'
  93. ];
  94. if($digest) {
  95. $headers['Digest'] = 'SHA-256='.$digest;
  96. }
  97. return $headers;
  98. }
  99. }