Compare commits
	
		
			1 Commits
		
	
	
		
			actor_outb
			...
			http-signa
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 7f704f34fa | 
| @@ -37,6 +37,10 @@ if (!defined('GNUSOCIAL')) { | |||||||
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 |  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||||
|  * @link      http://www.gnu.org/software/social/ |  * @link      http://www.gnu.org/software/social/ | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
|  | use GuzzleHttp\Psr7; | ||||||
|  | use HttpSignatures\Context; | ||||||
|  |  | ||||||
| class apInboxAction extends ManagedAction | class apInboxAction extends ManagedAction | ||||||
| { | { | ||||||
|     protected $needLogin = false; |     protected $needLogin = false; | ||||||
| @@ -71,8 +75,37 @@ class apInboxAction extends ManagedAction | |||||||
|  |  | ||||||
|         $headers = $this->get_all_headers(); |         $headers = $this->get_all_headers(); | ||||||
|         common_debug('ActivityPub Inbox: Request Headers: '.print_r($headers, true)); |         common_debug('ActivityPub Inbox: Request Headers: '.print_r($headers, true)); | ||||||
|  |         try { | ||||||
|  |             $res = HTTPSignature::parse($headers); | ||||||
|  |             common_debug('ActivityPub Inbox: Request Res: '.print_r($res, true)); | ||||||
|  |         } catch (HttpSignatureError $e) { | ||||||
|  |             common_debug('ActivityPub Inbox: HTTP Signature Error: '. $e->getMessage()); | ||||||
|  |             ActivityPubReturn::error('HTTP Signature Error: '. $e->getMessage()); | ||||||
|  |         } catch (Exception $e) { | ||||||
|  |             ActivityPubReturn::error($e->getMessage()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // TODO: Validate HTTP Signature, if it fails, attempt once with profile update |         /*if (HTTPSignature::verify($res, | ||||||
|  |                 $actor_public_key, 'rsa') == FALSE) { | ||||||
|  |            common_debug('ActivityPub Inbox: Could not authorize request.'); | ||||||
|  |            ActivityPubReturn::error('Unauthorized.', 403); | ||||||
|  |         }*/ | ||||||
|  |  | ||||||
|  |         $context = new Context([ | ||||||
|  |             'keys' => [$res['params']['keyId'] => $actor_public_key], | ||||||
|  |             'algorithm' => $res['params']['algorithm'], | ||||||
|  |             'headers' => $res['headers'], | ||||||
|  |         ]); | ||||||
|  |  | ||||||
|  |         $request = new Psr7\Request($_SERVER['REQUEST_METHOD'], | ||||||
|  |                 (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]", | ||||||
|  |                 $headers); | ||||||
|  |  | ||||||
|  |         if ($context->verifier()->isValid($request) == false) | ||||||
|  |         { | ||||||
|  |             common_debug('ActivityPub Inbox: HTTP Signature: Unauthorized request.'); | ||||||
|  |             ActivityPubReturn::error('Unauthorized.', 403); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         common_debug('ActivityPub Inbox: HTTP Signature: Authorized request. Will now start the inbox handler.'); |         common_debug('ActivityPub Inbox: HTTP Signature: Authorized request. Will now start the inbox handler.'); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										348
									
								
								utils/http-signature-auth.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								utils/http-signature-auth.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,348 @@ | |||||||
|  | <?php | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2014 David Gwynne <david@gwynne.id.au> | ||||||
|  |  * | ||||||
|  |  * Permission to use, copy, modify, and distribute this software for any | ||||||
|  |  * purpose with or without fee is hereby granted, provided that the above | ||||||
|  |  * copyright notice and this permission notice appear in all copies. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||||||
|  |  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||||||
|  |  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||||||
|  |  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||||||
|  |  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||||||
|  |  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||||||
|  |  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | class HttpSignatureError extends Exception { }; | ||||||
|  | class ExpiredRequestError extends HttpSignatureError { }; | ||||||
|  | class InvalidHeaderError extends HttpSignatureError { }; | ||||||
|  | class InvalidParamsError extends HttpSignatureError { }; | ||||||
|  | class MissingHeaderError extends HttpSignatureError { }; | ||||||
|  | class InvalidAlgorithmError extends HttpSignatureError { }; | ||||||
|  |  | ||||||
|  | class HTTPSignature { | ||||||
|  |  | ||||||
|  | 	static function parse(array $headers, array $options = array()) | ||||||
|  | 	{ | ||||||
|  | 		if (!array_key_exists('signature', $headers)) { | ||||||
|  | 			throw new MissingHeaderError('no signature header in the request'); | ||||||
|  | 		} | ||||||
|  | 		$auth = 'Signature '.$headers['signature']; | ||||||
|  |  | ||||||
|  | 		if (!array_key_exists('headers', $options)) { | ||||||
|  | 			$options['headers'] = array(isset($headers['x-date']) ? 'x-date' : 'date'); | ||||||
|  | 		} else { | ||||||
|  | 			if (!is_array($options['headers'])) { | ||||||
|  | 				throw new Exception('headers option is not an array'); | ||||||
|  | 			} | ||||||
|  | 			if (sizeof(array_filter($options['headers'], function ($a) { return (!is_string($a)); }))) { | ||||||
|  | 				throw new Exception('headers option is not an array of strings'); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (!array_key_exists('clockSkew', $options)) { | ||||||
|  | 			$options['clockSkew'] = 300; | ||||||
|  | 		} elseif (!is_numeric($options['clockSkew'])) { | ||||||
|  | 			throw new Exception('clockSkew option is not numeric'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (array_key_exists('algorithms', $options)) { | ||||||
|  | 			if (!is_array($options['algorithms'])) { | ||||||
|  | 				throw new Exception('algorithms option is not an array'); | ||||||
|  | 			} | ||||||
|  | 			if (sizeof(array_filter($options['algorithms'], function ($a) { return (!is_string($a)); }))) { | ||||||
|  | 				throw new Exception('algorithms option is not an array of strings'); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$headers['request-line'] = array_key_exists('requestLine', $options) ? | ||||||
|  | 		    $options['requestLine'] : | ||||||
|  | 		    sprintf("%s %s %s", $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']); | ||||||
|  |  | ||||||
|  | 		foreach ($options['headers'] as $header) { | ||||||
|  | 			if (!array_key_exists($header, $headers)) { | ||||||
|  | 				throw new MissingHeaderError("$header was not in the request"); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$states = array( | ||||||
|  | 			'start' => 0, | ||||||
|  | 			'scheme' => 1, | ||||||
|  | 			'space' => 2, | ||||||
|  | 			'param' => 3, | ||||||
|  | 			'quote' => 4, | ||||||
|  | 			'value' => 5, | ||||||
|  | 			'comma' => 6 | ||||||
|  | 		); | ||||||
|  |  | ||||||
|  | 		$scheme = ''; | ||||||
|  | 		$params = array(); | ||||||
|  |  | ||||||
|  | 		$param = ''; | ||||||
|  | 		$value = ''; | ||||||
|  | 		$state = $states['start']; | ||||||
|  |  | ||||||
|  | 		for ($i = 0; $i < strlen($auth); $i++) { | ||||||
|  | 			$ch = $auth[$i]; | ||||||
|  |  | ||||||
|  | 			switch ($state) { | ||||||
|  | 			case $states['start']: | ||||||
|  | 				if (ctype_space($ch)) { | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				$state = $states['scheme']; | ||||||
|  | 				/* FALLTHROUGH */ | ||||||
|  | 			case $states['scheme']: | ||||||
|  | 				if (ctype_space($ch)) { | ||||||
|  | 					$state = $states['space']; | ||||||
|  | 				} else { | ||||||
|  | 					$scheme .= $ch; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 			case $states['space']; | ||||||
|  | 				if (ctype_space($ch)) { | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				$state = $states['param']; | ||||||
|  | 				/* FALLTHROUGH */ | ||||||
|  | 			case $states['param']: | ||||||
|  | 				if ($ch === '=') { | ||||||
|  | 					if ($param === '') { | ||||||
|  | 						throw new InvalidHeaderError('bad param name'); | ||||||
|  | 					} | ||||||
|  | 					if (array_key_exists($param, $params)) { | ||||||
|  | 						throw new InvalidHeaderError('param specified again'); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					$state = $states['quote']; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 				if (!ctype_alpha($ch)) { | ||||||
|  | 					throw new InvalidHeaderError('bad param format'); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				$param .= $ch; | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 			case $states['quote']; | ||||||
|  | 				if ($ch !== '"') { | ||||||
|  | 					throw new InvalidHeaderError('bad param format'); | ||||||
|  | 				} | ||||||
|  | 				$state = $states['value']; | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 			case $states['value']: | ||||||
|  | 				if ($ch === '"') { | ||||||
|  | 					$params[$param] = $value; | ||||||
|  | 					$param = ''; | ||||||
|  | 					$value = ''; | ||||||
|  |  | ||||||
|  | 					$state = $states['comma']; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				$value .= $ch; | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 			case $states['comma']: | ||||||
|  | 				if ($ch !== ',') { | ||||||
|  | 					throw new InvalidHeaderError('bad param format'); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				$state = $states['param']; | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 			default: | ||||||
|  | 				throw new Error('invalid state'); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if ($state !== $states['comma']) { | ||||||
|  | 			throw new InvalidHeaderError("bad param format"); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if ($scheme !== 'Signature') { | ||||||
|  | 			throw new InvalidHeaderError('scheme was not "Signature"'); | ||||||
|  | 		} | ||||||
|  | 		$required = array('keyId', 'algorithm', 'signature'); | ||||||
|  | 		foreach ($required as $param) { | ||||||
|  | 			if (!array_key_exists($param, $params)) { | ||||||
|  | 				throw new InvalidHeaderError("$param was not specified"); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (array_key_exists('headers', $params)) { | ||||||
|  | 			$params['headers'] = explode(' ', $params['headers']); | ||||||
|  | 		} else { | ||||||
|  | 			$params['headers'] = array(isset($headers['x-date']) ? 'x-date' : 'date'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		foreach ($options['headers'] as $header) { | ||||||
|  | 			if (!in_array($header, $params['headers'])) { | ||||||
|  | 				throw new MissingHeaderError("$header was not a signed header"); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (isset($options['algorithms']) && !in_array($params['algorithm'], $options['algorithms'])) { | ||||||
|  | 			throw new InvalidParamsError($params['algorithm'] . " is not a supported algorithm"); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$date = null; | ||||||
|  | 		if (isset($headers['date'])) { | ||||||
|  | 			$date = strtotime($headers['date']); | ||||||
|  | 		} elseif (isset($headers['x-date'])) { | ||||||
|  | 			$date = strtotime($headers['x-date']); | ||||||
|  | 		} | ||||||
|  | 		if (!is_null($date)) { | ||||||
|  | 			if ($date === FALSE) { | ||||||
|  | 				throw new InvalidHeaderError('unable to parse date header'); | ||||||
|  | 			} | ||||||
|  | 			$skew = abs(time() - $date); | ||||||
|  | 			if ($skew > $options['clockSkew']) { | ||||||
|  | 				throw new ExpiredRequestError(sprintf("clock skew of %ds was greater than %ds", $skew, $options['clockSkew'])); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$sign = array(); | ||||||
|  | 		foreach ($params['headers'] as $header) { | ||||||
|  | 			$sign[] = $header === 'request-line' ? $headers['request-line'] : sprintf("%s: %s", $header, $headers[$header]); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return (array('scheme' => $scheme, 'params' => $params, 'signingString' => implode("\n", $sign))); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static function verify(array $res, $key, $keytype) | ||||||
|  | 	{ | ||||||
|  | 		if (!is_string($key)) { | ||||||
|  | 			throw new Exception('key is not a string'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$alg = explode('-', $res['params']['algorithm'], 2); | ||||||
|  | 		if (sizeof($alg) != 2) { | ||||||
|  | 			throw new InvalidAlgorithmError("unsupported algorithm"); | ||||||
|  | 		} | ||||||
|  | 		if ($alg[0] != $keytype) { | ||||||
|  | 			throw new InvalidAlgorithmError("algorithm type doesn't match key type"); | ||||||
|  | 		} | ||||||
|  | 		switch ($alg[0]) { | ||||||
|  | 		case 'rsa': | ||||||
|  | 			$map = array('sha1' => OPENSSL_ALGO_SHA1, 'sha256' => OPENSSL_ALGO_SHA256, 'sha512' => OPENSSL_ALGO_SHA512); | ||||||
|  | 			if (!array_key_exists($alg[1], $map)) { | ||||||
|  | 				throw new InvalidAlgorithmError('unsupported algorithm'); | ||||||
|  | 			} | ||||||
|  | 			$pkey = openssl_get_publickey($key); | ||||||
|  | 			if ($pkey === FALSE) { | ||||||
|  | 				throw new Exception('key could not be parsed'); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			$rv = openssl_verify($res['signingString'], base64_decode($res['params']['signature']), $pkey, $map[$alg[1]]); | ||||||
|  | 			openssl_free_key($pkey); | ||||||
|  |  | ||||||
|  | 			switch ($rv) { | ||||||
|  | 			case 0: | ||||||
|  | 				return (FALSE); | ||||||
|  | 			case 1: | ||||||
|  | 				return (TRUE); | ||||||
|  | 			default: | ||||||
|  | 				throw new Exception('key could not be verified'); | ||||||
|  | 			} | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 		case 'hmac': | ||||||
|  | 			return (hash_hmac($alg[1], $res['signingString'], $key, true) === base64_decode($res['params']['signature'])); | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			throw new InvalidAlgorithmError("unsupported algorithm"); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static function sign(&$headers = array(), array $options = array()) | ||||||
|  | 	{ | ||||||
|  | 		if (is_null($headers)) { | ||||||
|  | 			$headers = array(); | ||||||
|  | 		} elseif (!is_array($headers)) { | ||||||
|  | 			throw new Exception('headers are not an array'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (!array_key_exists('keyId', $options)) { | ||||||
|  | 			throw new Exception('keyId option is missing'); | ||||||
|  | 		} elseif (!is_string($options['keyId'])) { | ||||||
|  | 			throw new Exception('keyId option is not a string'); | ||||||
|  | 		} | ||||||
|  | 		if (!array_key_exists('key', $options)) { | ||||||
|  | 			throw new Exception('key option is missing'); | ||||||
|  | 		} elseif (!is_string($options['key'])) { | ||||||
|  | 			throw new Exception('key option is not a string'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (!array_key_exists('headers', $options)) { | ||||||
|  | 			$options['headers'] = array('date'); | ||||||
|  | 		} else { | ||||||
|  | 			if (!is_array($options['headers'])) { | ||||||
|  | 				throw new Exception('headers option is not an array'); | ||||||
|  | 			} | ||||||
|  | 			if (sizeof(array_filter($options['headers'], function ($a) { return (!is_string($a)); }))) { | ||||||
|  | 				throw new Exception('headers option is not an array of strings'); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (!array_key_exists('algorithm', $options)) { | ||||||
|  | 			$options['algorithm'] = 'rsa-sha256'; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (!array_key_exists('date', $headers)) { | ||||||
|  | 			$headers['date'] = date(DATE_RFC1123); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$headers['request-line'] = array_key_exists('requestLine', $options) ? | ||||||
|  | 		    $options['requestLine'] : | ||||||
|  | 		    sprintf("%s %s %s", $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']); | ||||||
|  |  | ||||||
|  | 		$sign = array(); | ||||||
|  | 		foreach ($options['headers'] as $header) { | ||||||
|  | 			if (!array_key_exists($header, $headers)) { | ||||||
|  | 				throw new MissingHeaderError("$header was not in the request"); | ||||||
|  | 			} | ||||||
|  | 			$sign[] = $header === 'request-line' ? $headers['request-line'] : sprintf("%s: %s", $header, $headers[$header]); | ||||||
|  | 		} | ||||||
|  | 		$data = join("\n", $sign); | ||||||
|  |  | ||||||
|  | 		$alg = explode('-', $options['algorithm'], 2); | ||||||
|  | 		if (sizeof($alg) != 2) { | ||||||
|  | 			throw new InvalidAlgorithmError("unsupported algorithm"); | ||||||
|  | 		} | ||||||
|  | 		switch ($alg[0]) { | ||||||
|  | 		case 'rsa': | ||||||
|  | 			$map = array('sha1' => OPENSSL_ALGO_SHA1, 'sha256' => OPENSSL_ALGO_SHA256, 'sha512' => OPENSSL_ALGO_SHA512); | ||||||
|  | 			if (!array_key_exists($alg[1], $map)) { | ||||||
|  | 				throw new InvalidAlgorithmError('unsupported algorithm'); | ||||||
|  | 			} | ||||||
|  | 			$key = openssl_get_privatekey($options['key']); | ||||||
|  | 			if ($key === FALSE) { | ||||||
|  | 				error_log(openssl_error_string()); | ||||||
|  | 				throw new Exception('key option could not be parsed'); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (openssl_sign($data, $signature, $key, $map[$alg[1]]) === FALSE) { | ||||||
|  | 				throw new Exception('unable to sign'); | ||||||
|  | 			} | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 		case 'hmac': | ||||||
|  | 			$signature = hash_hmac($alg[1], $data, $options['key'], true); | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			throw new InvalidAlgorithmError("unsupported algorithm"); | ||||||
|  | 		} | ||||||
|  | 		unset($headers['request-line']); | ||||||
|  | 		$headers['authorization'] = sprintf('Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"', | ||||||
|  | 		    $options['keyId'], $options['algorithm'], implode(' ', $options['headers']), | ||||||
|  | 		    base64_encode($signature)); | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user