port hubzillas OpenWebAuth - remote authentification
This commit is contained in:
		
					parent
					
						
							
								5fb8c758fd
							
						
					
				
			
			
				commit
				
					
						1c7f4e3c63
					
				
			
		
					 16 changed files with 1151 additions and 41 deletions
				
			
		
							
								
								
									
										2
									
								
								boot.php
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								boot.php
									
										
									
									
									
								
							|  | @ -41,7 +41,7 @@ define('FRIENDICA_PLATFORM',     'Friendica'); | ||||||
| define('FRIENDICA_CODENAME',     'The Tazmans Flax-lily'); | define('FRIENDICA_CODENAME',     'The Tazmans Flax-lily'); | ||||||
| define('FRIENDICA_VERSION',      '2018.08-dev'); | define('FRIENDICA_VERSION',      '2018.08-dev'); | ||||||
| define('DFRN_PROTOCOL_VERSION',  '2.23'); | define('DFRN_PROTOCOL_VERSION',  '2.23'); | ||||||
| define('DB_UPDATE_VERSION',      1268); | define('DB_UPDATE_VERSION',      1269); | ||||||
| define('NEW_UPDATE_ROUTINE_VERSION', 1170); | define('NEW_UPDATE_ROUTINE_VERSION', 1170); | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								database.sql
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								database.sql
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | ||||||
| -- ------------------------------------------ | -- ------------------------------------------ | ||||||
| -- Friendica 2018.08-dev (The Tazmans Flax-lily) | -- Friendica 2018.08-dev (The Tazmans Flax-lily) | ||||||
| -- DB_UPDATE_VERSION 1268 | -- DB_UPDATE_VERSION 1269 | ||||||
| -- ------------------------------------------ | -- ------------------------------------------ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -1084,6 +1084,19 @@ CREATE TABLE IF NOT EXISTS `user-item` ( | ||||||
| 	 PRIMARY KEY(`uid`,`iid`) | 	 PRIMARY KEY(`uid`,`iid`) | ||||||
| ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific item data'; | ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific item data'; | ||||||
| 
 | 
 | ||||||
|  | -- | ||||||
|  | -- TABLE verify | ||||||
|  | -- | ||||||
|  | CREATE TABLE IF NOT EXISTS `verify` ( | ||||||
|  | 	`id` int(10) NOT NULL auto_increment COMMENT 'sequential ID', | ||||||
|  | 	`uid` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'User id', | ||||||
|  | 	`type` varchar(32) DEFAULT '' COMMENT 'Verify type', | ||||||
|  | 	`token` varchar(255) DEFAULT '' COMMENT 'A generated token', | ||||||
|  | 	`meta` varchar(255) DEFAULT '' COMMENT '', | ||||||
|  | 	`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime of creation', | ||||||
|  | 	 PRIMARY KEY(`id`) | ||||||
|  | ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Store token to verify contacts'; | ||||||
|  | 
 | ||||||
| -- | -- | ||||||
| -- TABLE worker-ipc | -- TABLE worker-ipc | ||||||
| -- | -- | ||||||
|  |  | ||||||
|  | @ -357,6 +357,13 @@ Hook data: | ||||||
|     'item' => item array (input) |     'item' => item array (input) | ||||||
|     'html' => converted item body (input/output) |     'html' => converted item body (input/output) | ||||||
| 
 | 
 | ||||||
|  | ### 'magic_auth_success' | ||||||
|  | Called when a magic-auth was successful. | ||||||
|  | Hook data: | ||||||
|  |     'visitor' => array with the contact record of the visitor | ||||||
|  |     'url' => the query string | ||||||
|  |     'session' => $_SESSION array | ||||||
|  | 
 | ||||||
| Current JavaScript hooks | Current JavaScript hooks | ||||||
| ------------- | ------------- | ||||||
| 
 | 
 | ||||||
|  | @ -557,6 +564,7 @@ Here is a complete list of all hook callbacks with file locations (as of 01-Apr- | ||||||
|     Addon::callHooks('profile_sidebar', $arr); |     Addon::callHooks('profile_sidebar', $arr); | ||||||
|     Addon::callHooks('profile_tabs', $arr); |     Addon::callHooks('profile_tabs', $arr); | ||||||
|     Addon::callHooks('zrl_init', $arr); |     Addon::callHooks('zrl_init', $arr); | ||||||
|  |     Addon::callHooks('magic_auth_success', $arr); | ||||||
| 
 | 
 | ||||||
| ### src/Model/Event.php | ### src/Model/Event.php | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								index.php
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								index.php
									
										
									
									
									
								
							|  | @ -122,13 +122,16 @@ if ((x($_SESSION, 'language')) && ($_SESSION['language'] !== $lang)) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| if ((x($_GET,'zrl')) && $a->mode == App::MODE_NORMAL) { | if ((x($_GET,'zrl')) && $a->mode == App::MODE_NORMAL) { | ||||||
|  | 	$a->query_string = Profile::stripZrls($a->query_string); | ||||||
|  | 	if (!local_user()) { | ||||||
| 		// Only continue when the given profile link seems valid
 | 		// Only continue when the given profile link seems valid
 | ||||||
| 		// Valid profile links contain a path with "/profile/" and no query parameters
 | 		// Valid profile links contain a path with "/profile/" and no query parameters
 | ||||||
| 	if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == "") | 		if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == "") && | ||||||
| 		&& strstr(parse_url($_GET['zrl'], PHP_URL_PATH), "/profile/") | 			strstr(parse_url($_GET['zrl'], PHP_URL_PATH), "/profile/")) { | ||||||
| 	) { | 			if ($_SESSION["visitor_home"] != $_GET["zrl"]) { | ||||||
| 				$_SESSION['my_url'] = $_GET['zrl']; | 				$_SESSION['my_url'] = $_GET['zrl']; | ||||||
| 		$a->query_string = preg_replace('/[\?&]zrl=(.*?)([\?&]|$)/is', '', $a->query_string); | 				$_SESSION['authenticated'] = 0; | ||||||
|  | 			} | ||||||
| 			Profile::zrlInit($a); | 			Profile::zrlInit($a); | ||||||
| 		} else { | 		} else { | ||||||
| 			// Someone came with an invalid parameter, maybe as a DDoS attempt
 | 			// Someone came with an invalid parameter, maybe as a DDoS attempt
 | ||||||
|  | @ -139,6 +142,13 @@ if ((x($_GET, 'zrl')) && $a->mode == App::MODE_NORMAL) { | ||||||
| 			killme(); | 			killme(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if ((x($_GET,'owt')) && $a->mode == App::MODE_NORMAL) { | ||||||
|  | 	$token = $_GET['owt']; | ||||||
|  | 	$a->query_string = Profile::stripQueryParam($a->query_string, 'owt'); | ||||||
|  | 	Profile::owtInit($token); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * For Mozilla auth manager - still needs sorting, and this might conflict with LRDD header. |  * For Mozilla auth manager - still needs sorting, and this might conflict with LRDD header. | ||||||
|  |  | ||||||
|  | @ -78,7 +78,8 @@ function xrd_json($a, $uri, $alias, $profile_url, $r) | ||||||
| 					['rel' => 'http://salmon-protocol.org/ns/salmon-replies', 'href' => System::baseUrl().'/salmon/'.$r['nickname']], | 					['rel' => 'http://salmon-protocol.org/ns/salmon-replies', 'href' => System::baseUrl().'/salmon/'.$r['nickname']], | ||||||
| 					['rel' => 'http://salmon-protocol.org/ns/salmon-mention', 'href' => System::baseUrl().'/salmon/'.$r['nickname'].'/mention'], | 					['rel' => 'http://salmon-protocol.org/ns/salmon-mention', 'href' => System::baseUrl().'/salmon/'.$r['nickname'].'/mention'], | ||||||
| 					['rel' => 'http://ostatus.org/schema/1.0/subscribe', 'template' => System::baseUrl().'/follow?url={uri}'], | 					['rel' => 'http://ostatus.org/schema/1.0/subscribe', 'template' => System::baseUrl().'/follow?url={uri}'], | ||||||
| 					['rel' => 'magic-public-key', 'href' => 'data:application/magic-public-key,'.$salmon_key] | 					['rel' => 'magic-public-key', 'href' => 'data:application/magic-public-key,'.$salmon_key], | ||||||
|  | 					array('rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa') | ||||||
| 	]]; | 	]]; | ||||||
| 	echo json_encode($json); | 	echo json_encode($json); | ||||||
| 	killme(); | 	killme(); | ||||||
|  | @ -106,6 +107,7 @@ function xrd_xml($a, $uri, $alias, $profile_url, $r) | ||||||
| 		'$salmon'      => System::baseUrl() . '/salmon/'        . $r['nickname'], | 		'$salmon'      => System::baseUrl() . '/salmon/'        . $r['nickname'], | ||||||
| 		'$salmen'      => System::baseUrl() . '/salmon/'        . $r['nickname'] . '/mention', | 		'$salmen'      => System::baseUrl() . '/salmon/'        . $r['nickname'] . '/mention', | ||||||
| 		'$subscribe'   => System::baseUrl() . '/follow?url={uri}', | 		'$subscribe'   => System::baseUrl() . '/follow?url={uri}', | ||||||
|  | 		'$openwebauth' => System::baseUrl() .'/owa', | ||||||
| 		'$modexp'      => 'data:application/magic-public-key,'  . $salmon_key] | 		'$modexp'      => 'data:application/magic-public-key,'  . $salmon_key] | ||||||
| 	); | 	); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -163,17 +163,17 @@ EOT; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * @brief Encodes content to json | 	 * @brief Encodes content to json. | ||||||
| 	 * | 	 * | ||||||
| 	 * This function encodes an array to json format | 	 * This function encodes an array to json format | ||||||
| 	 * and adds an application/json HTTP header to the output. | 	 * and adds an application/json HTTP header to the output. | ||||||
| 	 * After finishing the process is getting killed. | 	 * After finishing the process is getting killed. | ||||||
| 	 * | 	 * | ||||||
| 	 * @param array $x The input content | 	 * @param array  $x The input content. | ||||||
|  | 	 * @param string $content_type Type of the input (Default: 'application/json'). | ||||||
| 	 */ | 	 */ | ||||||
| 	public static function jsonExit($x) | 	public static function jsonExit($x, $content_type = 'application/json') { | ||||||
| 	{ | 		header("Content-type: $content_type"); | ||||||
| 		header("content-type: application/json"); |  | ||||||
| 		echo json_encode($x); | 		echo json_encode($x); | ||||||
| 		killme(); | 		killme(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -1818,6 +1818,20 @@ class DBStructure | ||||||
| 						"PRIMARY" => ["uid", "iid"], | 						"PRIMARY" => ["uid", "iid"], | ||||||
| 						] | 						] | ||||||
| 				]; | 				]; | ||||||
|  | 		$database["verify"] = [ | ||||||
|  | 				"comment" => "Store token to verify contacts", | ||||||
|  | 				"fields" => [ | ||||||
|  | 						"id" => ["type" => "int(10)", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"], | ||||||
|  | 						"uid" => ["type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "User id"], | ||||||
|  | 						"type" => ["type" => "varchar(32)", "not_null", "default" => "", "comment" => "Verify type"], | ||||||
|  | 						"token" => ["type" => "varchar(255)", "not_null" => "1", "default" => "", "comment" => "A generated token"], | ||||||
|  | 						"meta" => ["type" => "varchar(255)", "not_null" => "1", "default" => "", "comment" => ""], | ||||||
|  | 						"created" => ["type" => "datetime", "not null" => "1", "default" => NULL_DATE, "comment" => "datetime of creation"], | ||||||
|  | 					], | ||||||
|  | 				"indexes" => [ | ||||||
|  | 						"PRIMARY" => ["id"], | ||||||
|  | 						] | ||||||
|  | 				]; | ||||||
| 		$database["worker-ipc"] = [ | 		$database["worker-ipc"] = [ | ||||||
| 				"comment" => "Inter process communication between the frontend and the worker", | 				"comment" => "Inter process communication between the frontend and the worker", | ||||||
| 				"fields" => [ | 				"fields" => [ | ||||||
|  |  | ||||||
|  | @ -17,7 +17,9 @@ use Friendica\Core\System; | ||||||
| use Friendica\Core\Worker; | use Friendica\Core\Worker; | ||||||
| use Friendica\Database\DBM; | use Friendica\Database\DBM; | ||||||
| use Friendica\Model\Contact; | use Friendica\Model\Contact; | ||||||
|  | use Friendica\Model\Verify; | ||||||
| use Friendica\Protocol\Diaspora; | use Friendica\Protocol\Diaspora; | ||||||
|  | use Friendica\Network\Probe; | ||||||
| use Friendica\Util\DateTimeFormat; | use Friendica\Util\DateTimeFormat; | ||||||
| use Friendica\Util\Network; | use Friendica\Util\Network; | ||||||
| use Friendica\Util\Temporal; | use Friendica\Util\Temporal; | ||||||
|  | @ -978,11 +980,22 @@ class Profile | ||||||
| 		return null; | 		return null; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Process the 'zrl' parameter and initiate the remote authentication. | ||||||
|  | 	 *  | ||||||
|  | 	 * This method checks if the visitor has a public contact entry and | ||||||
|  | 	 * redirects the visitor to his/her instance to start the magic auth (Authentication) | ||||||
|  | 	 * process. | ||||||
|  | 	 *  | ||||||
|  | 	 * @param App $a Application instance. | ||||||
|  | 	 */ | ||||||
| 	public static function zrlInit(App $a) | 	public static function zrlInit(App $a) | ||||||
| 	{ | 	{ | ||||||
| 		$my_url = self::getMyURL(); | 		$my_url = self::getMyURL(); | ||||||
| 		$my_url = Network::isUrlValid($my_url); | 		$my_url = Network::isUrlValid($my_url); | ||||||
|  | 
 | ||||||
| 		if ($my_url) { | 		if ($my_url) { | ||||||
|  | 			if (!local_user()) { | ||||||
| 				// Is it a DDoS attempt?
 | 				// Is it a DDoS attempt?
 | ||||||
| 				// The check fetches the cached value from gprobe to reduce the load for this system
 | 				// The check fetches the cached value from gprobe to reduce the load for this system
 | ||||||
| 				$urlparts = parse_url($my_url); | 				$urlparts = parse_url($my_url); | ||||||
|  | @ -996,7 +1009,106 @@ class Profile | ||||||
| 				Worker::add(PRIORITY_LOW, 'GProbe', $my_url); | 				Worker::add(PRIORITY_LOW, 'GProbe', $my_url); | ||||||
| 				$arr = ['zrl' => $my_url, 'url' => $a->cmd]; | 				$arr = ['zrl' => $my_url, 'url' => $a->cmd]; | ||||||
| 				Addon::callHooks('zrl_init', $arr); | 				Addon::callHooks('zrl_init', $arr); | ||||||
|  | 
 | ||||||
|  | 				// Try to find the public contact entry of the visitor.
 | ||||||
|  | 				$fields = ["id", "url"]; | ||||||
|  | 				$condition = ['uid' => 0, 'nurl' => normalise_link($my_url)]; | ||||||
|  | 
 | ||||||
|  | 				$contact = dba::selectFirst('contact',$fields, $condition); | ||||||
|  | 
 | ||||||
|  | 				// Not found? Try to probe the visitor.
 | ||||||
|  | 				if (!DBM::is_result($contact)) { | ||||||
|  | 					Probe::uri($my_url, '', -1, true, true); | ||||||
|  | 					$contact = dba::selectFirst('contact',$fields, $condition); | ||||||
| 				} | 				} | ||||||
|  | 
 | ||||||
|  | 				if (!DBM::is_result($contact)) { | ||||||
|  | 					logger('No contact record found for ' . $my_url, LOGGER_DEBUG); | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if (DBM::is_result($contact) && remote_user() && remote_user() === $contact['id']) { | ||||||
|  | 					// The visitor is already authenticated.
 | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				logger('Not authenticated. Invoking reverse magic-auth for ' . $my_url, LOGGER_DEBUG); | ||||||
|  | 
 | ||||||
|  | 				// Try to avoid recursion - but send them home to do a proper magic auth.
 | ||||||
|  | 				$query = str_replace(array('?zrl=', '&zid='), array('?rzrl=', '&rzrl='), $a->query_string); | ||||||
|  | 				// The other instance needs to know where to redirect.
 | ||||||
|  | 				$dest = urlencode(System::baseUrl() . "/" . $query); | ||||||
|  | 
 | ||||||
|  | 				// We need to extract the basebath from the profile url
 | ||||||
|  | 				// to redirect the visitors '/magic' module.
 | ||||||
|  | 				// Note: We should have the basepath of a contact also in the contact table.
 | ||||||
|  | 				$urlarr = explode("/profile/", $contact['url']); | ||||||
|  | 				$basepath = $urlarr[0]; | ||||||
|  | 
 | ||||||
|  | 				if ($basepath != System::baseUrl() && !strstr($dest, '/magic') && !strstr($dest, '/rmagic')) { | ||||||
|  | 					goaway($basepath . '/magic' . '?f=&owa=1&dest=' . $dest); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * OpenWebAuth authentication. | ||||||
|  | 	 * | ||||||
|  | 	 * @param string $token | ||||||
|  | 	 */ | ||||||
|  | 	public static function owtInit($token) | ||||||
|  | 	{ | ||||||
|  | 		$a = get_app(); | ||||||
|  | 
 | ||||||
|  | 		// Clean old verify entries.
 | ||||||
|  | 		Verify::purge('owt', '3 MINUTE'); | ||||||
|  | 
 | ||||||
|  | 		// Check if the token we got is the same one
 | ||||||
|  | 		// we have stored in the database.
 | ||||||
|  | 		$visitor_handle = Verify::getMeta('owt', 0, $token); | ||||||
|  | 
 | ||||||
|  | 		if($visitor_handle === false) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Try to find the public contact entry of the visitor.
 | ||||||
|  | 		$condition = ["uid" => 0, "addr" => $visitor_handle]; | ||||||
|  | 		$visitor = dba::selectFirst("contact", [], $condition); | ||||||
|  | 
 | ||||||
|  | 		if (!DBM::is_result($visitor)) { | ||||||
|  | 			Probe::uri($visitor_handle, '', -1, true, true); | ||||||
|  | 			$visitor = dba::selectFirst("contact", [], $condition); | ||||||
|  | 		} | ||||||
|  | 		if(!DBM::is_result($visitor)) { | ||||||
|  | 			logger('owt: unable to finger ' . $visitor_handle, LOGGER_DEBUG); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Authenticate the visitor.
 | ||||||
|  | 		$_SESSION['authenticated'] = 1; | ||||||
|  | 		$_SESSION['visitor_id'] = $visitor['id']; | ||||||
|  | 		$_SESSION['visitor_handle'] = $visitor['addr']; | ||||||
|  | 		$_SESSION['visitor_home'] = $visitor['url']; | ||||||
|  | 
 | ||||||
|  | 		$arr = [ | ||||||
|  | 			'visitor' => $visitor, | ||||||
|  | 			'url' => $a->query_string, | ||||||
|  | 			'session' => $_SESSION | ||||||
|  | 		]; | ||||||
|  | 		/** | ||||||
|  | 		 * @hooks magic_auth_success | ||||||
|  | 		 *   Called when a magic-auth was successful. | ||||||
|  | 		 *   * \e array \b visitor | ||||||
|  | 		 *   * \e string \b url | ||||||
|  | 		 *   * \e array \b session | ||||||
|  | 		 */ | ||||||
|  | 		Addon::callHooks('magic_auth_success', $arr); | ||||||
|  | 		$a->contact = $visitor; | ||||||
|  | 
 | ||||||
|  | 		info(L10n::t('OpenWebAuth: %1$s welcomes %2$s', $a->get_hostname(), $visitor['name'])); | ||||||
|  | 
 | ||||||
|  | 		logger('OpenWebAuth: auth success from ' . $visitor['addr'], LOGGER_DEBUG); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public static function zrl($s, $force = false) | 	public static function zrl($s, $force = false) | ||||||
|  | @ -1042,4 +1154,26 @@ class Profile | ||||||
| 
 | 
 | ||||||
| 		return $uid; | 		return $uid; | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	* Stip zrl parameter from a string. | ||||||
|  | 	*  | ||||||
|  | 	* @param string $s The input string. | ||||||
|  | 	* @return string The zrl. | ||||||
|  | 	*/ | ||||||
|  | 	public static function stripZrls($s) | ||||||
|  | 	{ | ||||||
|  | 		return preg_replace('/[\?&]zrl=(.*?)([\?&]|$)/is', '', $s); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	* Stip query parameter from a string. | ||||||
|  | 	*  | ||||||
|  | 	* @param string $s The input string. | ||||||
|  | 	* @return string The query parameter. | ||||||
|  | 	*/ | ||||||
|  | 	public static function stripQueryParam($s, $param) | ||||||
|  | 	{ | ||||||
|  | 		return preg_replace('/[\?&]' . $param . '=(.*?)(&|$)/ism', '$2', $s); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										73
									
								
								src/Model/Verify.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/Model/Verify.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @file src/Model/Verify.php | ||||||
|  |  */ | ||||||
|  | namespace Friendica\Model; | ||||||
|  | 
 | ||||||
|  | use Friendica\Database\DBM; | ||||||
|  | use Friendica\Util\DateTimeFormat; | ||||||
|  | use dba; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Methods to deal with entries of the 'verify' table. | ||||||
|  |  */ | ||||||
|  | class Verify | ||||||
|  | { | ||||||
|  | 	/** | ||||||
|  | 	 * Create an entry in the 'verify' table. | ||||||
|  | 	 *  | ||||||
|  | 	 * @param string $type   Verify type. | ||||||
|  | 	 * @param int    $uid    The user ID. | ||||||
|  | 	 * @param string $token | ||||||
|  | 	 * @param string $meta | ||||||
|  | 	 *  | ||||||
|  | 	 * @return boolean | ||||||
|  | 	 */ | ||||||
|  | 	public static function create($type, $uid, $token, $meta) | ||||||
|  | 	{ | ||||||
|  | 		$fields = [ | ||||||
|  | 			"type" => $type, | ||||||
|  | 			"uid" => $uid, | ||||||
|  | 			"token" => $token, | ||||||
|  | 			"meta" => $meta, | ||||||
|  | 			"created" => DateTimeFormat::utcNow() | ||||||
|  | 		]; | ||||||
|  | 		return dba::insert("verify", $fields); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Get the "meta" field of an entry in the verify table. | ||||||
|  | 	 *  | ||||||
|  | 	 * @param string $type   Verify type. | ||||||
|  | 	 * @param int    $uid    The user ID. | ||||||
|  | 	 * @param string $token | ||||||
|  | 	 *  | ||||||
|  | 	 * @return string|boolean The meta enry or false if not found. | ||||||
|  | 	 */ | ||||||
|  | 	public static function getMeta($type, $uid, $token) | ||||||
|  | 	{ | ||||||
|  | 		$condition = ["type" => $type, "uid" => $uid, "token" => $token]; | ||||||
|  | 
 | ||||||
|  | 		$entry = dba::selectFirst("verify", ["id", "meta"], $condition); | ||||||
|  | 		if (DBM::is_result($entry)) { | ||||||
|  | 			dba::delete("verify", ["id" => $entry["id"]]); | ||||||
|  | 
 | ||||||
|  | 			return $entry["meta"]; | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Purge entries of a verify-type older than interval. | ||||||
|  | 	 *  | ||||||
|  | 	 * @param string $type     Verify type. | ||||||
|  | 	 * @param string $interval SQL compatible time interval | ||||||
|  | 	 */ | ||||||
|  | 	public static function purge($type, $interval) | ||||||
|  | 	{ | ||||||
|  | 		$condition = ["`type` = ? AND `created` < ?", $type, DateTimeFormat::utcNow() . " - INTERVAL " . $interval]; | ||||||
|  | 		dba::delete("verify", $condition); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										121
									
								
								src/Module/Magic.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/Module/Magic.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,121 @@ | ||||||
|  | <?php | ||||||
|  | /** | ||||||
|  |  * @file src/Module/Magic.php | ||||||
|  |  */ | ||||||
|  | namespace Friendica\Module; | ||||||
|  | 
 | ||||||
|  | use Friendica\BaseModule; | ||||||
|  | use Friendica\Database\DBM; | ||||||
|  | use Friendica\Network\Probe; | ||||||
|  | use Friendica\Util\HTTPSig; | ||||||
|  | use Friendica\Util\Network; | ||||||
|  | 
 | ||||||
|  | use dba; | ||||||
|  | 
 | ||||||
|  | class Magic extends BaseModule | ||||||
|  | { | ||||||
|  | 	public static function init() | ||||||
|  | 	{ | ||||||
|  | 		$a = self::getApp(); | ||||||
|  | 		$ret = ['success' => false, 'url' => '', 'message' => '']; | ||||||
|  | 		logger('magic mdule: invoked', LOGGER_DEBUG); | ||||||
|  | 
 | ||||||
|  | 		logger('args: ' . print_r($_REQUEST, true), LOGGER_DATA); | ||||||
|  | 
 | ||||||
|  | 		$addr = ((x($_REQUEST, 'addr')) ? $_REQUEST['addr'] : ''); | ||||||
|  | 		$dest = ((x($_REQUEST, 'dest')) ? $_REQUEST['dest'] : ''); | ||||||
|  | 		$test = ((x($_REQUEST, 'test')) ? intval($_REQUEST['test']) : 0); | ||||||
|  | 		$owa  = ((x($_REQUEST, 'owa'))  ? intval($_REQUEST['owa'])  : 0); | ||||||
|  | 
 | ||||||
|  | 		// NOTE: I guess $dest isn't just the profile url (could be also 
 | ||||||
|  | 		// other profile pages e.g. photo). We need to find a solution
 | ||||||
|  | 		// to be able to redirct to other pages than the contact profile.
 | ||||||
|  | 		$fields = ["id", "nurl", "url"]; | ||||||
|  | 		$condition = ["nurl" => normalise_link($dest)]; | ||||||
|  | 
 | ||||||
|  | 		$contact = dba::selectFirst("contact", $fields, $condition); | ||||||
|  | 
 | ||||||
|  | 		if (!DBM::is_result($contact)) { | ||||||
|  | 			// If we don't have a contact record, try to probe it.
 | ||||||
|  | 			/// @todo: Also check against the $addr.
 | ||||||
|  | 			Probe::uri($dest, '', -1, true, true); | ||||||
|  | 			$contact = dba::selectFirst("contact", $fields, $condition); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (!DBM::is_result($contact)) { | ||||||
|  | 			logger("No contact record found: " . print_r($_REQUEST, true), LOGGER_DEBUG); | ||||||
|  | 			goaway($dest); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Redirect if the contact is already authenticated on this site.
 | ||||||
|  | 		if (array_key_exists("id", $a->contact) && strpos($contact['nurl'], normalise_link(self::getApp()->get_baseurl())) !== false) { | ||||||
|  | 			if($test) { | ||||||
|  | 				$ret['success'] = true; | ||||||
|  | 				$ret['message'] .= 'Local site - you are already authenticated.' . EOL; | ||||||
|  | 				return $ret; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			logger("Contact is already authenticated", LOGGER_DEBUG); | ||||||
|  | 			goaway($dest); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (local_user()) { | ||||||
|  | 			$user = $a->user; | ||||||
|  | 
 | ||||||
|  | 			// OpenWebAuth
 | ||||||
|  | 			if ($owa) { | ||||||
|  | 				// Extract the basepath
 | ||||||
|  | 				// NOTE: we need another solution because this does only work
 | ||||||
|  | 				// for friendica contacts :-/ . We should have the basepath
 | ||||||
|  | 				// of a contact also in the contact table.
 | ||||||
|  | 				$exp = explode("/profile/", $contact['url']); | ||||||
|  | 				$basepath = $exp[0]; | ||||||
|  | 
 | ||||||
|  | 				$headers = []; | ||||||
|  | 				$headers['Accept'] = 'application/x-dfrn+json'; | ||||||
|  | 				$headers['X-Open-Web-Auth'] = random_string(); | ||||||
|  | 
 | ||||||
|  | 				// Create a header that is signed with the local users private key.
 | ||||||
|  | 				$headers = HTTPSig::createSig( | ||||||
|  | 							'', | ||||||
|  | 							$headers, | ||||||
|  | 							$user['prvkey'], | ||||||
|  | 							'acct:' . $user['nickname'] . '@' . $a->get_hostname() . ($a->path ? '/' . $a->path : ''), | ||||||
|  | 							false, | ||||||
|  | 							true, | ||||||
|  | 							'sha512' | ||||||
|  | 				); | ||||||
|  | 
 | ||||||
|  | 				// Try to get an authentication token from the other instance.
 | ||||||
|  | 				$x = Network::curl($basepath . '/owa', false, $redirects, ['headers' => $headers]); | ||||||
|  | 
 | ||||||
|  | 				if ($x['success']) { | ||||||
|  | 					$j = json_decode($x['body'], true); | ||||||
|  | 
 | ||||||
|  | 					if ($j['success']) { | ||||||
|  | 						$token = ''; | ||||||
|  | 						if ($j['encrypted_token']) { | ||||||
|  | 							// The token is encrypted. If the local user is really the one the other instance
 | ||||||
|  | 							// thinks he/she is, the token can be decrypted with the local users public key.
 | ||||||
|  | 							openssl_private_decrypt(base64url_decode($j['encrypted_token']), $token, $user['prvkey']); | ||||||
|  | 						} else { | ||||||
|  | 							$token = $j['token']; | ||||||
|  | 						} | ||||||
|  | 						$x = strpbrk($dest, '?&'); | ||||||
|  | 						$args = (($x) ? '&owt=' . $token : '?f=&owt=' . $token); | ||||||
|  | 
 | ||||||
|  | 						goaway($dest . $args); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				goaway($dest); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if($test) { | ||||||
|  | 			$ret['message'] = 'Not authenticated or invalid arguments' . EOL; | ||||||
|  | 			return $ret; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		goaway($dest); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										94
									
								
								src/Module/Owa.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/Module/Owa.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | ||||||
|  | <?php | ||||||
|  | /** | ||||||
|  |  * @file src/Module/Owa.php | ||||||
|  |  */ | ||||||
|  | namespace Friendica\Module; | ||||||
|  | 
 | ||||||
|  | use Friendica\BaseModule; | ||||||
|  | use Friendica\Core\System; | ||||||
|  | use Friendica\Database\DBM; | ||||||
|  | use Friendica\Model\Verify; | ||||||
|  | use Friendica\Network\Probe; | ||||||
|  | use Friendica\Util\DateTimeFormat; | ||||||
|  | use Friendica\Util\HTTPSig; | ||||||
|  | 
 | ||||||
|  | use dba; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @brief OpenWebAuth verifier and token generator | ||||||
|  |  *  | ||||||
|  |  * See https://macgirvin.com/wiki/mike/OpenWebAuth/Home | ||||||
|  |  * Requests to this endpoint should be signed using HTTP Signatures | ||||||
|  |  * using the 'Authorization: Signature' authentication method | ||||||
|  |  * If the signature verifies a token is returned. | ||||||
|  |  * | ||||||
|  |  * This token may be exchanged for an authenticated cookie. | ||||||
|  |  */ | ||||||
|  | class Owa extends BaseModule | ||||||
|  | { | ||||||
|  | 	public static function init() | ||||||
|  | 	{ | ||||||
|  | 
 | ||||||
|  | 		$ret = [ 'success' => false ]; | ||||||
|  | 
 | ||||||
|  | 		foreach (['REDIRECT_REMOTE_USER', 'HTTP_AUTHORIZATION'] as $head) { | ||||||
|  | 			if (array_key_exists($head, $_SERVER) && substr(trim($_SERVER[$head]), 0, 9) === 'Signature') { | ||||||
|  | 				if ($head !== 'HTTP_AUTHORIZATION') { | ||||||
|  | 					$_SERVER['HTTP_AUTHORIZATION'] = $_SERVER[$head]; | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				$sigblock = HTTPSig::parseSigheader($_SERVER[$head]); | ||||||
|  | 				if ($sigblock) { | ||||||
|  | 					$keyId = $sigblock['keyId']; | ||||||
|  | 
 | ||||||
|  | 					if ($keyId) { | ||||||
|  | 						// Try to find the public contact entry of the handle.
 | ||||||
|  | 						$handle = str_replace("acct:", "", $keyId); | ||||||
|  | 						$fields = ["id", "url", "addr", "pubkey"]; | ||||||
|  | 						$condition = ["addr" => $handle, "uid" => 0]; | ||||||
|  | 
 | ||||||
|  | 						$contact = dba::selectFirst("contact", $fields, $condition); | ||||||
|  | 
 | ||||||
|  | 						// Not found? Try to probe with the handle.
 | ||||||
|  | 						if(!DBM::is_result($contact)) { | ||||||
|  | 							Probe::uri($handle, '', -1, true, true); | ||||||
|  | 							$contact = dba::selectFirst("contact", $fields, $condition); | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						if (DBM::is_result($contact)) { | ||||||
|  | 							// Try to verify the signed header with the public key of the contact record
 | ||||||
|  | 							// we have found.
 | ||||||
|  | 							$verified = HTTPSig::verify('', $contact['pubkey']); | ||||||
|  | 
 | ||||||
|  | 							if ($verified && $verified['header_signed'] && $verified['header_valid']) { | ||||||
|  | 								logger('OWA header: ' . print_r($verified, true), LOGGER_DATA); | ||||||
|  | 								logger('OWA success: ' . $contact['addr'], LOGGER_DATA); | ||||||
|  | 
 | ||||||
|  | 								$ret['success'] = true; | ||||||
|  | 								$token = random_string(32); | ||||||
|  | 
 | ||||||
|  | 								// Store the generated token in the databe.
 | ||||||
|  | 								Verify::create('owt', 0, $token, $contact['addr']); | ||||||
|  | 
 | ||||||
|  | 								$result = ''; | ||||||
|  | 
 | ||||||
|  | 								// Encrypt the token with the public contacts publik key.
 | ||||||
|  | 								// Only the specific public contact will be able to encrypt it.
 | ||||||
|  | 								// At a later time, we will compare weather the token we're getting
 | ||||||
|  | 								// is really the same token we have stored in the database.
 | ||||||
|  | 								openssl_public_encrypt($token, $result, $contact['pubkey']); | ||||||
|  | 								$ret['encrypted_token'] = base64url_encode($result); | ||||||
|  | 							} else { | ||||||
|  | 								logger('OWA fail: ' . $contact['id'] . ' ' . $contact['addr'] . ' ' . $contact['url'], LOGGER_DEBUG); | ||||||
|  | 							} | ||||||
|  | 						} else { | ||||||
|  | 							logger('Contact not found: ' . $handle, LOGGER_DEBUG); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		System::jsonExit($ret, 'application/x-dfrn+json'); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -311,10 +311,11 @@ class Probe | ||||||
| 	 * @param string  $network Test for this specific network | 	 * @param string  $network Test for this specific network | ||||||
| 	 * @param integer $uid     User ID for the probe (only used for mails) | 	 * @param integer $uid     User ID for the probe (only used for mails) | ||||||
| 	 * @param boolean $cache   Use cached values? | 	 * @param boolean $cache   Use cached values? | ||||||
|  | 	 * @param boolean $insert  Insert the contact into the contact table. | ||||||
| 	 * | 	 * | ||||||
| 	 * @return array uri data | 	 * @return array uri data | ||||||
| 	 */ | 	 */ | ||||||
| 	public static function uri($uri, $network = "", $uid = -1, $cache = true) | 	public static function uri($uri, $network = "", $uid = -1, $cache = true, $insert = false) | ||||||
| 	{ | 	{ | ||||||
| 		if ($cache) { | 		if ($cache) { | ||||||
| 			$result = Cache::get("Probe::uri:".$network.":".$uri); | 			$result = Cache::get("Probe::uri:".$network.":".$uri); | ||||||
|  | @ -463,11 +464,19 @@ class Probe | ||||||
| 				$condition = ['nurl' => normalise_link($data["url"]), 'self' => false, 'uid' => 0]; | 				$condition = ['nurl' => normalise_link($data["url"]), 'self' => false, 'uid' => 0]; | ||||||
| 
 | 
 | ||||||
| 				// "$old_fields" will return a "false" when the contact doesn't exist.
 | 				// "$old_fields" will return a "false" when the contact doesn't exist.
 | ||||||
| 				// This won't trigger an insert. This is intended, since we only need
 | 				// This won't trigger an insert except $insert is set to true.
 | ||||||
| 				// public contacts for everyone we store items from.
 | 				// This is intended, since we only need public contacts
 | ||||||
| 				// We don't need to store every contact on the planet.
 | 				// for everyone we store items from. We don't need to store
 | ||||||
|  | 				// every contact on the planet.
 | ||||||
| 				$old_fields = dba::selectFirst('contact', $fieldnames, $condition); | 				$old_fields = dba::selectFirst('contact', $fieldnames, $condition); | ||||||
| 
 | 
 | ||||||
|  | 				// When the contact doesn't exist, the value "true" will trigger an insert
 | ||||||
|  | 				if (!$old_fields && $insert) { | ||||||
|  | 					$old_fields = true; | ||||||
|  | 					$fields['blocked'] = false; | ||||||
|  | 					$fields['pending'] = false; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
| 				$fields['name-date'] = DateTimeFormat::utcNow(); | 				$fields['name-date'] = DateTimeFormat::utcNow(); | ||||||
| 				$fields['uri-date'] = DateTimeFormat::utcNow(); | 				$fields['uri-date'] = DateTimeFormat::utcNow(); | ||||||
| 				$fields['success_update'] = DateTimeFormat::utcNow(); | 				$fields['success_update'] = DateTimeFormat::utcNow(); | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
|  */ |  */ | ||||||
| namespace Friendica\Util; | namespace Friendica\Util; | ||||||
| 
 | 
 | ||||||
|  | use Friendica\Core\Addon; | ||||||
| use Friendica\Core\Config; | use Friendica\Core\Config; | ||||||
| use ASN_BASE; | use ASN_BASE; | ||||||
| use ASNValue; | use ASNValue; | ||||||
|  | @ -246,4 +247,221 @@ class Crypto | ||||||
| 
 | 
 | ||||||
| 		return $response; | 		return $response; | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Encrypt a string with 'aes-256-cbc' cipher method. | ||||||
|  | 	 *  | ||||||
|  | 	 * @param string $data | ||||||
|  | 	 * @param string $key   The key used for encryption. | ||||||
|  | 	 * @param string $iv    A non-NULL Initialization Vector. | ||||||
|  | 	 *  | ||||||
|  | 	 * @return string|boolean Encrypted string or false on failure. | ||||||
|  | 	 */ | ||||||
|  | 	private static function encryptAES256CBC($data, $key, $iv) | ||||||
|  | 	{ | ||||||
|  | 		return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Decrypt a string with 'aes-256-cbc' cipher method. | ||||||
|  | 	 *  | ||||||
|  | 	 * @param string $data | ||||||
|  | 	 * @param string $key   The key used for decryption. | ||||||
|  | 	 * @param string $iv    A non-NULL Initialization Vector. | ||||||
|  | 	 *  | ||||||
|  | 	 * @return string|boolean Decrypted string or false on failure. | ||||||
|  | 	 */ | ||||||
|  | 	private static function decryptAES256CBC($data, $key, $iv) | ||||||
|  | 	{ | ||||||
|  | 		return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Encrypt a string with 'aes-256-ctr' cipher method. | ||||||
|  | 	 *  | ||||||
|  | 	 * @param string $data | ||||||
|  | 	 * @param string $key   The key used for encryption. | ||||||
|  | 	 * @param string $iv    A non-NULL Initialization Vector. | ||||||
|  | 	 *  | ||||||
|  | 	 * @return string|boolean Encrypted string or false on failure. | ||||||
|  | 	 */ | ||||||
|  | 	private static function encryptAES256CTR($data, $key, $iv) | ||||||
|  | 	{ | ||||||
|  | 		$key = substr($key, 0, 32); | ||||||
|  | 		$iv = substr($iv, 0, 16); | ||||||
|  | 		return openssl_encrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Decrypt a string with 'aes-256-cbc' cipher method. | ||||||
|  | 	 *  | ||||||
|  | 	 * @param string $data | ||||||
|  | 	 * @param string $key   The key used for decryption. | ||||||
|  | 	 * @param string $iv    A non-NULL Initialization Vector. | ||||||
|  | 	 *  | ||||||
|  | 	 * @return string|boolean Decrypted string or false on failure. | ||||||
|  | 	 */ | ||||||
|  | 	private static function decryptAES256CTR($data, $key, $iv) | ||||||
|  | 	{ | ||||||
|  | 		$key = substr($key, 0, 32); | ||||||
|  | 		$iv = substr($iv, 0, 16); | ||||||
|  | 		return openssl_decrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 *  | ||||||
|  | 	 * @param string $data | ||||||
|  | 	 * @param string $pubkey The public key. | ||||||
|  | 	 * @param string $alg    The algorithm used for encryption. | ||||||
|  | 	 *  | ||||||
|  | 	 * @return array | ||||||
|  | 	 */ | ||||||
|  | 	public static function encapsulate($data, $pubkey, $alg = 'aes256cbc') | ||||||
|  | 	{ | ||||||
|  | 		if ($alg === 'aes256cbc') { | ||||||
|  | 			return self::encapsulateAes($data, $pubkey); | ||||||
|  | 		} | ||||||
|  | 		return self::encapsulateOther($data, $pubkey, $alg); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 *  | ||||||
|  | 	 * @param type $data | ||||||
|  | 	 * @param type $pubkey The public key. | ||||||
|  | 	 * @param type $alg    The algorithm used for encryption. | ||||||
|  | 	 *  | ||||||
|  | 	 * @return array | ||||||
|  | 	 */ | ||||||
|  | 	private static function encapsulateOther($data, $pubkey, $alg) | ||||||
|  | 	{ | ||||||
|  | 		if (!$pubkey) { | ||||||
|  | 			logger('no key. data: '.$data); | ||||||
|  | 		} | ||||||
|  | 		$fn = 'encrypt' . strtoupper($alg); | ||||||
|  | 		if (method_exists(__CLASS__, $fn)) { | ||||||
|  | 			// A bit hesitant to use openssl_random_pseudo_bytes() as we know
 | ||||||
|  | 			// it has been historically targeted by US agencies for 'weakening'.
 | ||||||
|  | 			// It is still arguably better than trying to come up with an
 | ||||||
|  | 			// alternative cryptographically secure random generator.
 | ||||||
|  | 			// There is little point in using the optional second arg to flag the
 | ||||||
|  | 			// assurance of security since it is meaningless if the source algorithms
 | ||||||
|  | 			// have been compromised. Also none of this matters if RSA has been
 | ||||||
|  | 			// compromised by state actors and evidence is mounting that this has
 | ||||||
|  | 			// already happened.
 | ||||||
|  | 			$result = ['encrypted' => true]; | ||||||
|  | 			$key = openssl_random_pseudo_bytes(256); | ||||||
|  | 			$iv  = openssl_random_pseudo_bytes(256); | ||||||
|  | 			$result['data'] = base64url_encode(self::$fn($data, $key, $iv), true); | ||||||
|  | 
 | ||||||
|  | 			// log the offending call so we can track it down
 | ||||||
|  | 			if (!openssl_public_encrypt($key, $k, $pubkey)) { | ||||||
|  | 				$x = debug_backtrace(); | ||||||
|  | 				logger('RSA failed. ' . print_r($x[0], true)); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			$result['alg'] = $alg; | ||||||
|  | 			$result['key'] = base64url_encode($k, true); | ||||||
|  | 			openssl_public_encrypt($iv, $i, $pubkey); | ||||||
|  | 			$result['iv'] = base64url_encode($i, true); | ||||||
|  | 
 | ||||||
|  | 			return $result; | ||||||
|  | 		} else { | ||||||
|  | 			$x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data]; | ||||||
|  | 			Addon::callHooks('other_encapsulate', $x); | ||||||
|  | 
 | ||||||
|  | 			return $x['result']; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 *  | ||||||
|  | 	 * @param string $data | ||||||
|  | 	 * @param string $pubkey | ||||||
|  | 	 *  | ||||||
|  | 	 * @return array | ||||||
|  | 	 */ | ||||||
|  | 	private static function encapsulateAes($data, $pubkey) | ||||||
|  | 	{ | ||||||
|  | 		if (!$pubkey) { | ||||||
|  | 			logger('aes_encapsulate: no key. data: ' . $data); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		$key = openssl_random_pseudo_bytes(32); | ||||||
|  | 		$iv  = openssl_random_pseudo_bytes(16); | ||||||
|  | 		$result = ['encrypted' => true]; | ||||||
|  | 		$result['data'] = base64url_encode(AES256CBC_encrypt($data, $key, $iv), true); | ||||||
|  | 
 | ||||||
|  | 		// log the offending call so we can track it down
 | ||||||
|  | 		if (!openssl_public_encrypt($key, $k, $pubkey)) { | ||||||
|  | 			$x = debug_backtrace(); | ||||||
|  | 			logger('aes_encapsulate: RSA failed. ' . print_r($x[0], true)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		$result['alg'] = 'aes256cbc'; | ||||||
|  | 		$result['key'] = base64url_encode($k, true); | ||||||
|  | 		openssl_public_encrypt($iv, $i, $pubkey); | ||||||
|  | 		$result['iv'] = base64url_encode($i, true); | ||||||
|  | 
 | ||||||
|  | 		return $result; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 *  | ||||||
|  | 	 * @param string $data | ||||||
|  | 	 * @param string $prvkey  The private key used for decryption. | ||||||
|  | 	 *  | ||||||
|  | 	 * @return string|boolean The decrypted string or false on failure. | ||||||
|  | 	 */ | ||||||
|  | 	public static function unencapsulate($data, $prvkey) | ||||||
|  | 	{ | ||||||
|  | 		if (!$data) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		$alg = ((array_key_exists('alg', $data)) ? $data['alg'] : 'aes256cbc'); | ||||||
|  | 		if ($alg === 'aes256cbc') { | ||||||
|  | 			return self::encapsulateAes($data, $prvkey); | ||||||
|  | 		} | ||||||
|  | 		return self::encapsulateOther($data, $prvkey, $alg); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 *  | ||||||
|  | 	 * @param string $data | ||||||
|  | 	 * @param string $prvkey  The private key used for decryption. | ||||||
|  | 	 * @param string $alg | ||||||
|  | 	 *  | ||||||
|  | 	 * @return string|boolean The decrypted string or false on failure. | ||||||
|  | 	 */ | ||||||
|  | 	private static function unencapsulateOther($data, $prvkey, $alg) | ||||||
|  | 	{ | ||||||
|  | 		$fn = 'decrypt' . strtoupper($alg); | ||||||
|  | 
 | ||||||
|  | 		if (method_exists(__CLASS__, $fn)) { | ||||||
|  | 			openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey); | ||||||
|  | 			openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey); | ||||||
|  | 
 | ||||||
|  | 			return self::$fn(base64url_decode($data['data']), $k, $i); | ||||||
|  | 		} else { | ||||||
|  | 			$x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data]; | ||||||
|  | 			Addon::callHooks('other_unencapsulate', $x); | ||||||
|  | 
 | ||||||
|  | 			return $x['result']; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 *  | ||||||
|  | 	 * @param array  $data | ||||||
|  | 	 * @param string $prvkey  The private key used for decryption. | ||||||
|  | 	 *  | ||||||
|  | 	 * @return string|boolean The decrypted string or false on failure. | ||||||
|  | 	 */ | ||||||
|  | 	private static function unencapsulateAes($data, $prvkey) | ||||||
|  | 	{ | ||||||
|  | 		openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey); | ||||||
|  | 		openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey); | ||||||
|  | 
 | ||||||
|  | 		return self::decryptAES256CBC(base64url_decode($data['data']), $k, $i); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										59
									
								
								src/Util/HTTPHeaders.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/Util/HTTPHeaders.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | ||||||
|  | <?php | ||||||
|  | /** | ||||||
|  |  * @file src/Util/HTTPHeaders.php | ||||||
|  |  */ | ||||||
|  | namespace Friendica\Util; | ||||||
|  | 
 | ||||||
|  | class HTTPHeaders | ||||||
|  | { | ||||||
|  | 	private $in_progress = []; | ||||||
|  | 	private $parsed = []; | ||||||
|  | 
 | ||||||
|  | 	function __construct($headers) | ||||||
|  | 	{ | ||||||
|  | 		$lines = explode("\n", str_replace("\r", '', $headers)); | ||||||
|  | 
 | ||||||
|  | 		if ($lines) { | ||||||
|  | 			foreach ($lines as $line) { | ||||||
|  | 				if (preg_match('/^\s+/', $line, $matches) && trim($line)) { | ||||||
|  | 					if ($this->in_progress['k']) { | ||||||
|  | 						$this->in_progress['v'] .= ' ' . ltrim($line); | ||||||
|  | 						continue; | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					if ($this->in_progress['k']) { | ||||||
|  | 						$this->parsed[] = [$this->in_progress['k'] => $this->in_progress['v']]; | ||||||
|  | 						$this->in_progress = []; | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					$this->in_progress['k'] = strtolower(substr($line, 0, strpos($line, ':'))); | ||||||
|  | 					$this->in_progress['v'] = ltrim(substr($line, strpos($line, ':') + 1)); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if ($this->in_progress['k']) { | ||||||
|  | 				$this->parsed[] = [$this->in_progress['k'] => $this->in_progress['v']]; | ||||||
|  | 				$this->in_progress = []; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function fetch() | ||||||
|  | 	{ | ||||||
|  | 		return $this->parsed; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function fetcharr() | ||||||
|  | 	{ | ||||||
|  | 		$ret = []; | ||||||
|  | 
 | ||||||
|  | 		if ($this->parsed) { | ||||||
|  | 			foreach ($this->parsed as $x) { | ||||||
|  | 				foreach ($x as $y => $z) { | ||||||
|  | 					$ret[$y] = $z; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return $ret; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										352
									
								
								src/Util/HTTPSig.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										352
									
								
								src/Util/HTTPSig.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,352 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @file src/Util/HTTPSig.php | ||||||
|  |  */ | ||||||
|  | namespace Friendica\Util; | ||||||
|  | 
 | ||||||
|  | use Friendica\Core\Config; | ||||||
|  | use Friendica\Util\Crypto; | ||||||
|  | use Friendica\Util\HTTPHeaders; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @brief Implements HTTP Signatures per draft-cavage-http-signatures-07. | ||||||
|  |  * | ||||||
|  |  * @see https://tools.ietf.org/html/draft-cavage-http-signatures-07 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | class HTTPSig | ||||||
|  | { | ||||||
|  | 	/** | ||||||
|  | 	 * @brief RFC5843 | ||||||
|  | 	 * | ||||||
|  | 	 * @see https://tools.ietf.org/html/rfc5843 | ||||||
|  | 	 * | ||||||
|  | 	 * @param string $body The value to create the digest for | ||||||
|  | 	 * @param boolean $set (optional, default true) | ||||||
|  | 	 *   If set send a Digest HTTP header | ||||||
|  | 	 * @return string The generated digest of $body | ||||||
|  | 	 */ | ||||||
|  | 	public static function generateDigest($body, $set = true) | ||||||
|  | 	{ | ||||||
|  | 		$digest = base64_encode(hash('sha256', $body, true)); | ||||||
|  | 
 | ||||||
|  | 		if($set) { | ||||||
|  | 			header('Digest: SHA-256=' . $digest); | ||||||
|  | 		} | ||||||
|  | 		return $digest; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// See draft-cavage-http-signatures-08
 | ||||||
|  | 	public static function verify($data, $key = '') | ||||||
|  | 	{ | ||||||
|  | 		$body      = $data; | ||||||
|  | 		$headers   = null; | ||||||
|  | 		$spoofable = false; | ||||||
|  | 		$result = [ | ||||||
|  | 			'signer'         => '', | ||||||
|  | 			'header_signed'  => false, | ||||||
|  | 			'header_valid'   => false, | ||||||
|  | 			'content_signed' => false, | ||||||
|  | 			'content_valid'  => false | ||||||
|  | 		]; | ||||||
|  | 
 | ||||||
|  | 		// Decide if $data arrived via controller submission or curl.
 | ||||||
|  | 		if (is_array($data) && $data['header']) { | ||||||
|  | 			if (!$data['success']) { | ||||||
|  | 				return $result; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			$h = new HTTPHeaders($data['header']); | ||||||
|  | 			$headers = $h->fetcharr(); | ||||||
|  | 			$body = $data['body']; | ||||||
|  | 		} else { | ||||||
|  | 			$headers = []; | ||||||
|  | 			$headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']).' '.$_SERVER['REQUEST_URI']; | ||||||
|  | 
 | ||||||
|  | 			foreach ($_SERVER as $k => $v) { | ||||||
|  | 				if (strpos($k, 'HTTP_') === 0) { | ||||||
|  | 					$field = str_replace('_', '-', strtolower(substr($k, 5))); | ||||||
|  | 					$headers[$field] = $v; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		$sig_block = null; | ||||||
|  | 
 | ||||||
|  | 		if (array_key_exists('signature', $headers)) { | ||||||
|  | 			$sig_block = self::parseSigheader($headers['signature']); | ||||||
|  | 		} elseif (array_key_exists('authorization', $headers)) { | ||||||
|  | 			$sig_block = self::parseSigheader($headers['authorization']); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (!$sig_block) { | ||||||
|  | 			logger('no signature provided.'); | ||||||
|  | 			return $result; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Warning: This log statement includes binary data
 | ||||||
|  | 		// logger('sig_block: ' . print_r($sig_block,true), LOGGER_DATA);
 | ||||||
|  | 
 | ||||||
|  | 		$result['header_signed'] = true; | ||||||
|  | 
 | ||||||
|  | 		$signed_headers = $sig_block['headers']; | ||||||
|  | 		if (!$signed_headers) { | ||||||
|  | 			$signed_headers = ['date']; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		$signed_data = ''; | ||||||
|  | 		foreach ($signed_headers as $h) { | ||||||
|  | 			if (array_key_exists($h, $headers)) { | ||||||
|  | 				$signed_data .= $h . ': ' . $headers[$h] . "\n"; | ||||||
|  | 			} | ||||||
|  | 			if (strpos($h, '.')) { | ||||||
|  | 				$spoofable = true; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		$signed_data = rtrim($signed_data, "\n"); | ||||||
|  | 
 | ||||||
|  | 		$algorithm = null; | ||||||
|  | 		if ($sig_block['algorithm'] === 'rsa-sha256') { | ||||||
|  | 			$algorithm = 'sha256'; | ||||||
|  | 		} | ||||||
|  | 		if ($sig_block['algorithm'] === 'rsa-sha512') { | ||||||
|  | 			$algorithm = 'sha512'; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ($key && function_exists($key)) { /// @todo What function do we check for - maybe we check now for a method !!!
 | ||||||
|  | 			$result['signer'] = $sig_block['keyId']; | ||||||
|  | 			$key = $key($sig_block['keyId']); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (!$key) { | ||||||
|  | 			return $result; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		$x = Crypto::rsaVerify($signed_data, $sig_block['signature'], $key, $algorithm); | ||||||
|  | 
 | ||||||
|  | 		logger('verified: ' . $x, LOGGER_DEBUG); | ||||||
|  | 
 | ||||||
|  | 		if (!$x) { | ||||||
|  | 			return $result; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (!$spoofable) { | ||||||
|  | 			$result['header_valid'] = true; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (in_array('digest', $signed_headers)) { | ||||||
|  | 			$result['content_signed'] = true; | ||||||
|  | 			$digest = explode('=', $headers['digest']); | ||||||
|  | 
 | ||||||
|  | 			if ($digest[0] === 'SHA-256') { | ||||||
|  | 				$hashalg = 'sha256'; | ||||||
|  | 			} | ||||||
|  | 			if ($digest[0] === 'SHA-512') { | ||||||
|  | 				$hashalg = 'sha512'; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// The explode operation will have stripped the '=' padding, so compare against unpadded base64.
 | ||||||
|  | 			if (rtrim(base64_encode(hash($hashalg, $body, true)), '=') === $digest[1]) { | ||||||
|  | 				$result['content_valid'] = true; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		logger('Content_Valid: ' . $result['content_valid']); | ||||||
|  | 
 | ||||||
|  | 		return $result; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * @brief | ||||||
|  | 	 * | ||||||
|  | 	 * @param string  $request | ||||||
|  | 	 * @param array   $head | ||||||
|  | 	 * @param string  $prvkey | ||||||
|  | 	 * @param string  $keyid (optional, default 'Key') | ||||||
|  | 	 * @param boolean $send_headers (optional, default false) | ||||||
|  | 	 *   If set send a HTTP header | ||||||
|  | 	 * @param boolean $auth (optional, default false) | ||||||
|  | 	 * @param string  $alg (optional, default 'sha256') | ||||||
|  | 	 * @param string  $crypt_key (optional, default null) | ||||||
|  | 	 * @param string  $crypt_algo (optional, default 'aes256ctr') | ||||||
|  | 	 *  | ||||||
|  | 	 * @return array | ||||||
|  | 	 */ | ||||||
|  | 	public static function createSig($request, $head, $prvkey, $keyid = 'Key', $send_headers = false, $auth = false, $alg = 'sha256', $crypt_key = null, $crypt_algo = 'aes256ctr') | ||||||
|  | 	{ | ||||||
|  | 		$return_headers = []; | ||||||
|  | 
 | ||||||
|  | 		if ($alg === 'sha256') { | ||||||
|  | 			$algorithm = 'rsa-sha256'; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ($alg === 'sha512') { | ||||||
|  | 			$algorithm = 'rsa-sha512'; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		$x = self::sign($request, $head, $prvkey, $alg); | ||||||
|  | 
 | ||||||
|  | 		$headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm | ||||||
|  | 			. '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; | ||||||
|  | 
 | ||||||
|  | 		if ($crypt_key) { | ||||||
|  | 			$x = Crypto::encapsulate($headerval, $crypt_key, $crypt_algo); | ||||||
|  | 			$headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"'; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ($auth) { | ||||||
|  | 			$sighead = 'Authorization: Signature ' . $headerval; | ||||||
|  | 		} else { | ||||||
|  | 			$sighead = 'Signature: ' . $headerval; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ($head) { | ||||||
|  | 			foreach ($head as $k => $v) { | ||||||
|  | 				if ($send_headers) { | ||||||
|  | 					header($k . ': ' . $v); | ||||||
|  | 				} else { | ||||||
|  | 					$return_headers[] = $k . ': ' . $v; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ($send_headers) { | ||||||
|  | 			header($sighead); | ||||||
|  | 		} else { | ||||||
|  | 			$return_headers[] = $sighead; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return $return_headers; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * @brief | ||||||
|  | 	 * | ||||||
|  | 	 * @param string $request | ||||||
|  | 	 * @param array  $head | ||||||
|  | 	 * @param string $prvkey | ||||||
|  | 	 * @param string $alg (optional) default 'sha256' | ||||||
|  | 	 *  | ||||||
|  | 	 * @return array | ||||||
|  | 	 */ | ||||||
|  | 	private static function sign($request, $head, $prvkey, $alg = 'sha256') | ||||||
|  | 	{ | ||||||
|  | 		$ret = []; | ||||||
|  | 		$headers = ''; | ||||||
|  | 		$fields  = ''; | ||||||
|  | 
 | ||||||
|  | 		if ($request) { | ||||||
|  | 			$headers = '(request-target)' . ': ' . trim($request) . "\n"; | ||||||
|  | 			$fields = '(request-target)'; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ($head) { | ||||||
|  | 			foreach ($head as $k => $v) { | ||||||
|  | 				$headers .= strtolower($k) . ': ' . trim($v) . "\n"; | ||||||
|  | 				if ($fields) { | ||||||
|  | 					$fields .= ' '; | ||||||
|  | 				} | ||||||
|  | 				$fields .= strtolower($k); | ||||||
|  | 			} | ||||||
|  | 			// strip the trailing linefeed
 | ||||||
|  | 			$headers = rtrim($headers, "\n"); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		$sig = base64_encode(Crypto::rsaSign($headers, $prvkey, $alg)); | ||||||
|  | 
 | ||||||
|  | 		$ret['headers']   = $fields; | ||||||
|  | 		$ret['signature'] = $sig; | ||||||
|  | 	 | ||||||
|  | 		return $ret; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * @brief | ||||||
|  | 	 * | ||||||
|  | 	 * @param string $header | ||||||
|  | 	 * @return array associate array with | ||||||
|  | 	 *   - \e string \b keyID | ||||||
|  | 	 *   - \e string \b algorithm | ||||||
|  | 	 *   - \e array  \b headers | ||||||
|  | 	 *   - \e string \b signature | ||||||
|  | 	 */ | ||||||
|  | 	public static function parseSigheader($header) | ||||||
|  | 	{ | ||||||
|  | 		$ret = []; | ||||||
|  | 		$matches = []; | ||||||
|  | 
 | ||||||
|  | 		// if the header is encrypted, decrypt with (default) site private key and continue
 | ||||||
|  | 		if (preg_match('/iv="(.*?)"/ism', $header, $matches)) { | ||||||
|  | 			$header = self::decryptSigheader($header); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (preg_match('/keyId="(.*?)"/ism', $header, $matches)) { | ||||||
|  | 			$ret['keyId'] = $matches[1]; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (preg_match('/algorithm="(.*?)"/ism', $header, $matches)) { | ||||||
|  | 			$ret['algorithm'] = $matches[1]; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (preg_match('/headers="(.*?)"/ism', $header, $matches)) { | ||||||
|  | 			$ret['headers'] = explode(' ', $matches[1]); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (preg_match('/signature="(.*?)"/ism', $header, $matches)) { | ||||||
|  | 			$ret['signature'] = base64_decode(preg_replace('/\s+/', '', $matches[1])); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (($ret['signature']) && ($ret['algorithm']) && (!$ret['headers'])) { | ||||||
|  | 			$ret['headers'] = ['date']; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return $ret; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * @brief | ||||||
|  | 	 * | ||||||
|  | 	 * @param string $header | ||||||
|  | 	 * @param string $prvkey (optional), if not set use site private key | ||||||
|  | 	 *  | ||||||
|  | 	 * @return array|string associative array, empty string if failue | ||||||
|  | 	 *   - \e string \b iv | ||||||
|  | 	 *   - \e string \b key | ||||||
|  | 	 *   - \e string \b alg | ||||||
|  | 	 *   - \e string \b data | ||||||
|  | 	 */ | ||||||
|  | 	private static function decryptSigheader($header, $prvkey = null) | ||||||
|  | 	{ | ||||||
|  | 		$iv = $key = $alg = $data = null; | ||||||
|  | 
 | ||||||
|  | 		if (!$prvkey) { | ||||||
|  | 			$prvkey = Config::get('system', 'prvkey'); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		$matches = []; | ||||||
|  | 
 | ||||||
|  | 		if (preg_match('/iv="(.*?)"/ism', $header, $matches)) { | ||||||
|  | 			$iv = $matches[1]; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (preg_match('/key="(.*?)"/ism', $header, $matches)) { | ||||||
|  | 			$key = $matches[1]; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (preg_match('/alg="(.*?)"/ism', $header, $matches)) { | ||||||
|  | 			$alg = $matches[1]; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (preg_match('/data="(.*?)"/ism', $header, $matches)) { | ||||||
|  | 			$data = $matches[1]; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ($iv && $key && $alg && $data) { | ||||||
|  | 			return Crypto::unencapsulate(['iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data], $prvkey); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return ''; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -33,4 +33,7 @@ | ||||||
|           template="{{$subscribe}}" /> |           template="{{$subscribe}}" /> | ||||||
|     <Link rel="magic-public-key"  |     <Link rel="magic-public-key"  | ||||||
|           href="{{$modexp}}" /> |           href="{{$modexp}}" /> | ||||||
|  |     <Link rel="http://purl.org/openwebauth/v1" | ||||||
|  |           type="application/x-dfrn+json" | ||||||
|  |           href="{{$openwebauth}}" /> | ||||||
| </XRD> | </XRD> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue