From 100f102396beed2a218ca2f8ffaef392e94f4acc Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 16 Dec 2018 22:44:01 -0700 Subject: [PATCH] Add HttpSignature handlers --- app/Util/ActivityPub/HttpSignature.php | 121 +++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 app/Util/ActivityPub/HttpSignature.php diff --git a/app/Util/ActivityPub/HttpSignature.php b/app/Util/ActivityPub/HttpSignature.php new file mode 100644 index 000000000..d4e4cbecd --- /dev/null +++ b/app/Util/ActivityPub/HttpSignature.php @@ -0,0 +1,121 @@ +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 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(!isset($signatureData['headers']) || !isset($signatureData['signature'])) { + return [ + 'error' => 'Signature is missing headers or signature parts' + ]; + } + + 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]; + } + } + $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 _digest($body) { + if(is_array($body)) { + $body = json_encode($body); + } + return base64_encode(hash('sha256', $body, true)); + } + + protected static function _headersToSign($url, $digest = false) { + $date = new DateTime('UTC'); + + $headers = [ + '(request-target)' => 'post '.parse_url($url, PHP_URL_PATH), + 'Date' => $date->format('D, d M Y H:i:s \G\M\T'), + 'Host' => parse_url($url, PHP_URL_HOST), + 'Content-Type' => 'application/activity+json', + ]; + + if($digest) { + $headers['Digest'] = 'SHA-256='.$digest; + } + + return $headers; + } + +}