diff options
Diffstat (limited to 'include/zot.php')
-rw-r--r-- | include/zot.php | 199 |
1 files changed, 176 insertions, 23 deletions
diff --git a/include/zot.php b/include/zot.php index 8e3d03ad8..d28e584a1 100644 --- a/include/zot.php +++ b/include/zot.php @@ -158,6 +158,85 @@ function zot_build_packet($channel, $type = 'notify', $recipients = null, $remot return json_encode($data); } + +/** + * @brief Builds a zot6 notification packet. + * + * Builds a zot6 notification packet that you can either store in the queue with + * a message array or call zot_zot to immediately zot it to the other side. + * + * @param array $channel + * sender channel structure + * @param string $type + * packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'keychange', 'force_refresh', 'notify', 'auth_check' + * @param array $recipients + * envelope information, array ( 'guid' => string, 'guid_sig' => string ); empty for public posts + * @param string $remote_key + * optional public site key of target hub used to encrypt entire packet + * NOTE: remote_key and encrypted packets are required for 'auth_check' packets, optional for all others + * @param string $methods + * optional comma separated list of encryption methods @ref zot_best_algorithm() + * @param string $secret + * random string, required for packets which require verification/callback + * e.g. 'pickup', 'purge', 'notify', 'auth_check'. Packet types 'ping', 'force_refresh', and 'refresh' do not require verification + * @param string $extra + * @returns string json encoded zot packet + */ +function zot6_build_packet($channel, $type = 'notify', $recipients = null, $msg = '', $remote_key = null, $methods = '', $secret = null, $extra = null) { + + $sig_method = get_config('system','signature_algorithm','sha256'); + + $data = [ + 'type' => $type, + 'sender' => [ + 'guid' => $channel['channel_guid'], + 'guid_sig' => base64url_encode(rsa_sign($channel['channel_guid'],$channel['channel_prvkey'],$sig_method)), + 'url' => z_root(), + 'url_sig' => base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey'],$sig_method)), + 'sitekey' => get_config('system','pubkey') + ], + 'callback' => '/post', + 'version' => Zotlabs\Lib\System::get_zot_revision(), + 'encryption' => crypto_methods(), + 'signing' => signing_methods() + ]; + + if ($recipients) { + for ($x = 0; $x < count($recipients); $x ++) + unset($recipients[$x]['hash']); + + $data['recipients'] = $recipients; + } + + if($msg) { + $data['msg'] = $msg; + } + + if ($secret) { + $data['secret'] = preg_replace('/[^0-9a-fA-F]/','',$secret); + $data['secret_sig'] = base64url_encode(rsa_sign($secret,$channel['channel_prvkey'],$sig_method)); + } + + if ($extra) { + foreach ($extra as $k => $v) + $data[$k] = $v; + } + + logger('zot6_build_packet: ' . print_r($data,true), LOGGER_DATA, LOG_DEBUG); + + // Hush-hush ultra top-secret mode + + if($remote_key) { + $algorithm = zot_best_algorithm($methods); + $data = crypto_encapsulate(json_encode($data),$remote_key, $algorithm); + } + + return json_encode($data); +} + + + + /** * @brief Choose best encryption function from those available on both sites. * @@ -209,10 +288,23 @@ function zot_best_algorithm($methods) { * * @param string $url * @param array $data + * @param array $channel (optional if using zot6 delivery) + * @param array $crypto (optional if encrypted httpsig, requires hubloc_sitekey and site_crypto elements) * @return array see z_post_url() for returned data format */ -function zot_zot($url, $data) { - return z_post_url($url, array('data' => $data)); +function zot_zot($url, $data, $channel = null,$crypto = null) { + + $headers = []; + + if($channel) { + $headers['X-Zot-Token'] = random_string(); + $hash = \Zotlabs\Web\HTTPSig::generate_digest($data,false); + $headers['X-Zot-Digest'] = 'SHA-256=' . $hash; + $h = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'],'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,false,'sha512',(($crypto) ? $crypto['hubloc_sitekey'] : ''), (($crypto) ? zot_best_algorithm($crypto['site_crypto']) : '')); + } + + $redirects = 0; + return z_post_url($url, array('data' => $data),$redirects,((empty($h)) ? [] : [ 'headers' => $h ])); } /** @@ -1060,7 +1152,12 @@ function zot_process_response($hub, $arr, $outq) { * @brief * * We received a notification packet (in mod_post) that a message is waiting for us, and we've verified the sender. - * Now send back a pickup message, using our message tracking ID ($arr['secret']), which we will sign with our site + * Check if the site is using zot6 delivery and includes a verified HTTP Signature, signed content, and a 'msg' field, + * and also that the signer and the sender match. + * If that happens, we do not need to fetch/pickup the message - we have it already and it is verified. + * Translate it into the form we need for zot_import() and import it. + * + * Otherwise send back a pickup message, using our message tracking ID ($arr['secret']), which we will sign with our site * private key. * The entire pickup message is encrypted with the remote site's public key. * If everything checks out on the remote end, we will receive back a packet containing one or more messages, @@ -1078,38 +1175,61 @@ function zot_fetch($arr) { $url = $arr['sender']['url'] . $arr['callback']; - // set $multiple param on zot_gethub() to return all matching hubs - // This allows us to recover from re-installs when a redundant (but invalid) hubloc for - // this identity is widely dispersed throughout the network. + $import = null; + $hubs = null; + + $zret = zot6_check_sig(); - $ret_hubs = zot_gethub($arr['sender'],true); - if(! $ret_hubs) { + if($zret['success'] && $zret['hubloc'] && $zret['hubloc']['hubloc_guid'] === $data['sender']['guid'] && $data['msg']) { + logger('zot6_delivery',LOGGER_DEBUG); + logger('zot6_data: ' . print_r($data,true),LOGGER_DATA); + + $ret['collected'] = true; + + $import = [ 'success' => true, 'body' => json_encode( [ 'success' => true, 'pickup' => [ [ 'notify' => $data, 'message' => json_decode($data['msg'],true) ] ] ] ) ]; + $hubs = [ $zret['hubloc'] ] ; + } + + if(! $hubs) { + // set $multiple param on zot_gethub() to return all matching hubs + // This allows us to recover from re-installs when a redundant (but invalid) hubloc for + // this identity is widely dispersed throughout the network. + + $hubs = zot_gethub($arr['sender'],true); + } + + if(! $hubs) { logger('No hub: ' . print_r($arr['sender'],true)); return; } - foreach($ret_hubs as $ret_hub) { + foreach($hubs as $hub) { - $secret = substr(preg_replace('/[^0-9a-fA-F]/','',$arr['secret']),0,64); + if(! $import) { + $secret = substr(preg_replace('/[^0-9a-fA-F]/','',$arr['secret']),0,64); - $data = [ - 'type' => 'pickup', - 'url' => z_root(), - 'callback_sig' => base64url_encode(rsa_sign(z_root() . '/post', get_config('system','prvkey'))), - 'callback' => z_root() . '/post', - 'secret' => $secret, - 'secret_sig' => base64url_encode(rsa_sign($secret, get_config('system','prvkey'))) - ]; + $data = [ + 'type' => 'pickup', + 'url' => z_root(), + 'callback_sig' => base64url_encode(rsa_sign(z_root() . '/post', get_config('system','prvkey'))), + 'callback' => z_root() . '/post', + 'secret' => $secret, + 'secret_sig' => base64url_encode(rsa_sign($secret, get_config('system','prvkey'))) + ]; - $algorithm = zot_best_algorithm($ret_hub['site_crypto']); - $datatosend = json_encode(crypto_encapsulate(json_encode($data),$ret_hub['hubloc_sitekey'], $algorithm)); + $algorithm = zot_best_algorithm($hub['site_crypto']); + $datatosend = json_encode(crypto_encapsulate(json_encode($data),$hub['hubloc_sitekey'], $algorithm)); - $fetch = zot_zot($url,$datatosend); + $import = zot_zot($url,$datatosend); + } + else { + $algorithm = zot_best_algorithm($hub['site_crypto']); + } - $result = zot_import($fetch, $arr['sender']['url']); + $result = zot_import($import, $arr['sender']['url']); if($result) { - $result = crypto_encapsulate(json_encode($result),$ret_hub['hubloc_sitekey'], $algorithm); + $result = crypto_encapsulate(json_encode($result),$hub['hubloc_sitekey'], $algorithm); return $result; } @@ -4967,6 +5087,39 @@ function zot_reply_refresh($sender, $recipients) { } +function zot6_check_sig() { + + $ret = [ 'success' => false ]; + + logger('server: ' . print_r($_SERVER,true), LOGGER_DATA); + + if(array_key_exists('HTTP_SIGNATURE',$_SERVER)) { + $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER['HTTP_SIGNATURE']); + if($sigblock) { + $keyId = $sigblock['keyId']; + + if($keyId) { + $r = q("select hubloc.*, site_crypto from hubloc left join site on hubloc_url = site_url + where hubloc_addr = '%s' ", + dbesc(str_replace('acct:','',$keyId)) + ); + if($r) { + foreach($r as $hubloc) { + $verified = \Zotlabs\Web\HTTPSig::verify('',$hubloc['xchan_pubkey']); + if($verified && $verified['header_signed'] && $verified['header_valid'] && $verified['content_signed'] && $verified['content_valid']) { + $ret['hubloc'] = $hubloc; + $ret['success'] = true; + return $ret; + } + } + } + } + } + } + + return $ret; +} + function zot_reply_notify($data) { $ret = array('success' => false); |