diff options
-rw-r--r-- | Zotlabs/Daemon/Notifier.php | 25 | ||||
-rw-r--r-- | Zotlabs/Storage/File.php | 2 | ||||
-rw-r--r-- | Zotlabs/Zot/IHandler.php | 2 | ||||
-rw-r--r-- | Zotlabs/Zot/Receiver.php | 4 | ||||
-rw-r--r-- | Zotlabs/Zot/ZotHandler.php | 4 | ||||
-rwxr-xr-x | boot.php | 2 | ||||
-rw-r--r-- | include/channel.php | 102 | ||||
-rw-r--r-- | include/crypto.php | 10 | ||||
-rw-r--r-- | include/feedutils.php | 197 | ||||
-rw-r--r-- | include/markdown.php | 69 | ||||
-rw-r--r-- | include/xchan.php | 80 | ||||
-rw-r--r-- | include/zot.php | 235 |
12 files changed, 512 insertions, 220 deletions
diff --git a/Zotlabs/Daemon/Notifier.php b/Zotlabs/Daemon/Notifier.php index 20dd96ddd..f64e6748c 100644 --- a/Zotlabs/Daemon/Notifier.php +++ b/Zotlabs/Daemon/Notifier.php @@ -67,6 +67,7 @@ require_once('include/bbcode.php'); * location channel_id * request channel_id xchan_hash message_id * rating xlink_id + * keychange channel_id * */ @@ -144,6 +145,20 @@ class Notifier { $packet_type = 'request'; $normal_mode = false; } + elseif($cmd === 'keychange') { + $channel = channelx_by_n($item_id); + $r = q("select abook_xchan from abook where abook_channel = %d", + intval($item_id) + ); + if($r) { + foreach($r as $rr) { + $recipients[] = $rr['abook_xchan']; + } + } + $private = false; + $packet_type = 'keychange'; + $normal_mode = false; + } elseif($cmd == 'permission_update' || $cmd == 'permission_create') { // Get the (single) recipient $r = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_id = %d and abook_self = 0", @@ -570,12 +585,17 @@ class Notifier { } - $hash = random_string(); + $hash = random_string(); $packet = null; + $pmsg = ''; if($packet_type === 'refresh' || $packet_type === 'purge') { $packet = zot_build_packet($channel,$packet_type,(($packet_recips) ? $packet_recips : null)); } + if($packet_type === 'keychange') { + $packet = zot_build_packet($channel,$packet_type,(($packet_recips) ? $packet_recips : null)); + $pmsg = get_pconfig($channel['channel_id'],'system','keychange'); + } elseif($packet_type === 'request') { $env = (($hub_env && $hub_env[$hub['hubloc_host'] . $hub['hubloc_sitekey']]) ? $hub_env[$hub['hubloc_host'] . $hub['hubloc_sitekey']] : ''); $packet = zot_build_packet($channel,$packet_type,$env,$hub['hubloc_sitekey'],$hub['site_crypto'], @@ -589,7 +609,8 @@ class Notifier { 'account_id' => $channel['channel_account_id'], 'channel_id' => $channel['channel_id'], 'posturl' => $hub['hubloc_callback'], - 'notify' => $packet + 'notify' => $packet, + 'msg' => (($pmsg) ? json_encode($pmsg) : '') )); } else { diff --git a/Zotlabs/Storage/File.php b/Zotlabs/Storage/File.php index 1475241ab..7a102134f 100644 --- a/Zotlabs/Storage/File.php +++ b/Zotlabs/Storage/File.php @@ -265,7 +265,7 @@ class File extends DAV\Node implements DAV\IFile { $f = 'store/' . $this->auth->owner_nick . '/' . (($this->os_path) ? $this->os_path . '/' : '') . $x; else $f = $x; - return fopen($f, 'rb'); + return @fopen($f, 'rb'); } return dbunescbin($r[0]['content']); } diff --git a/Zotlabs/Zot/IHandler.php b/Zotlabs/Zot/IHandler.php index eeca1555c..dd82f5be6 100644 --- a/Zotlabs/Zot/IHandler.php +++ b/Zotlabs/Zot/IHandler.php @@ -12,6 +12,8 @@ interface IHandler { function Request($data); + function Rekey($sender,$data); + function AuthCheck($data,$encrypted); function Purge($sender,$recipients); diff --git a/Zotlabs/Zot/Receiver.php b/Zotlabs/Zot/Receiver.php index 0050a2559..c521c9d64 100644 --- a/Zotlabs/Zot/Receiver.php +++ b/Zotlabs/Zot/Receiver.php @@ -120,6 +120,10 @@ class Receiver { $this->handler->Notify($this->data); break; + case 'rekey': + $this->handler->Rekey($this->sender, $this->data); + break; + default: $this->response['message'] = 'Not implemented'; json_return_and_die($this->response); diff --git a/Zotlabs/Zot/ZotHandler.php b/Zotlabs/Zot/ZotHandler.php index aab336545..ab8815b3d 100644 --- a/Zotlabs/Zot/ZotHandler.php +++ b/Zotlabs/Zot/ZotHandler.php @@ -20,6 +20,10 @@ class ZotHandler implements IHandler { zot_reply_message_request($data); } + function Rekey($sender,$data) { + zot_rekey_request($sender,$data); + } + function AuthCheck($data,$encrypted) { zot_reply_auth_check($data,$encrypted); } @@ -50,7 +50,7 @@ require_once('include/attach.php'); define ( 'PLATFORM_NAME', 'hubzilla' ); define ( 'STD_VERSION', '2.5.10' ); -define ( 'ZOT_REVISION', '1.2' ); +define ( 'ZOT_REVISION', '1.3' ); define ( 'DB_UPDATE_VERSION', 1192 ); diff --git a/include/channel.php b/include/channel.php index 49da57fd6..f6252f094 100644 --- a/include/channel.php +++ b/include/channel.php @@ -454,6 +454,108 @@ function create_identity($arr) { return $ret; } + +function change_channel_keys($channel) { + + $ret = array('success' => false); + + $stored = []; + + $key = new_keypair(4096); + + $sig = base64url_encode(rsa_sign($channel['channel_guid'],$key['prvkey'])); + $hash = make_xchan_hash($channel['channel_guid'],$sig); + + $stored['old_guid'] = $channel['channel_guid']; + $stored['old_guid_sig'] = $channel['channel_guid_sig']; + $stored['old_key'] = $channel['channel_pubkey']; + $stored['old_hash'] = $channel['channel_hash']; + + $stored['new_key'] = $key['pubkey']; + $stored['new_sig'] = base64url_encode(rsa_sign($key['pubkey'],$channel['channel_prvkey'])); + + // Save this info for the notifier to collect + + set_pconfig($channel['channel_id'],'system','keychange',$stored); + + $r = q("update channel set channel_prvkey = '%s', channel_pubkey = '%s', channel_guid_sig = '%s', channel_hash = '%s' where channel_id = %d", + dbesc($key['prvkey']), + dbesc($key['pubkey']), + dbesc($sig), + dbesc($hash), + intval($channel['channel_id']) + ); + if(! $r) { + return $ret; + } + + $r = q("select * from channel where channel_id = %d", + intval($channel['channel_id']) + ); + + if(! $r) { + $ret['message'] = t('Unable to retrieve modified identity'); + return $ret; + } + + $modified = $r[0]; + + $h = q("select * from hubloc where hubloc_hash = '%s' and hubloc_url = '%s' ", + dbesc($stored['old_hash']), + dbesc(z_root()) + ); + + if($h) { + foreach($h as $hv) { + $hv['hubloc_guid_sig'] = $sig; + $hv['hubloc_hash'] = $hash; + $hv['hubloc_url_sig'] = base64url_encode(rsa_sign(z_root(),$modifed['channel_prvkey'])); + hubloc_store_lowlevel($hv); + } + } + + $x = q("select * from xchan where xchan_hash = '%s' ", + dbesc($stored['old_hash']) + ); + + $check = q("select * from xchan where xchan_hash = '%s'", + dbesc($hash) + ); + + if(($x) && (! $check)) { + $oldxchan = $x[0]; + foreach($x as $xv) { + $xv['xchan_guid_sig'] = $sig; + $xv['xchan_hash'] = $hash; + $xv['xchan_pubkey'] = $key['pubkey']; + xchan_store_lowlevel($xv); + $newxchan = $xv; + } + } + + build_sync_packet($channel['channel_id'], [ 'keychange' => $stored ]); + + $a = q("select * from abook where abook_xchan = '%s' and abook_self = 1", + dbesc($stored['old_hash']) + ); + + if($a) { + q("update abook set abook_xchan = '%s' where abook_id = %d", + dbesc($hash), + intval($a[0]['abook_id']) + ); + } + + xchan_change_key($oldxchan,$newxchan,$stored); + + Zotlabs\Daemon\Master::Summon(array('Notifier', 'keychange', $channel['channel_id'])); + + $ret['success'] = true; + return $ret; +} + + + /** * @brief Set default channel to be used on login. * diff --git a/include/crypto.php b/include/crypto.php index 2c5545e9b..6ac3fd732 100644 --- a/include/crypto.php +++ b/include/crypto.php @@ -185,6 +185,16 @@ function crypto_methods() { } +function signing_methods() { + + + $r = [ 'sha256' ]; + call_hooks('signing_methods',$r); + return $r; + +} + + function aes_encapsulate($data,$pubkey) { if(! $pubkey) logger('aes_encapsulate: no key. data: ' . $data); diff --git a/include/feedutils.php b/include/feedutils.php index 4f68fdeef..eb24f9032 100644 --- a/include/feedutils.php +++ b/include/feedutils.php @@ -999,6 +999,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { foreach($items as $item) { $is_reply = false; + $send_downstream = false; $parent_link = ''; logger('processing ' . $item->get_id(), LOGGER_DEBUG); @@ -1200,6 +1201,15 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { $status = 202; continue; } + + // The salmon endpoint sets this to indicate that we should send comments from + // interactive feeds (such as OStatus) downstream to our followers + // We do not want to set it for non-interactive feeds or conversations we do not own + + if(array_key_exists('send_downstream',$importer) && intval($importer['send_downstream']) + && ($parent_item['owner_xchan'] == $importer['channel_hash'])) { + $send_downstream = true; + } } else { if((! perm_is_allowed($importer['channel_id'],$contact['xchan_hash'],'send_stream')) && (! $importer['system'])) { @@ -1229,6 +1239,11 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { $xx = item_store($datarray); $r = $xx['item_id']; + + if($send_downstream) { + \Zotlabs\Daemon\Master::Summon(array('Notifier', 'comment', $r)); + } + continue; } else { @@ -1868,185 +1883,3 @@ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0, $ return $x['entry']; } -/** - * @brief - * - * @param array $items - * @return array - */ -function gen_asld($items) { - $ret = array(); - if(! $items) - return $ret; - - foreach($items as $item) { - $ret[] = i2asld($item); - } - - return $ret; -} - -/** - * @brief - * - * @param array $i - * @return array - */ -function i2asld($i) { - - if(! $i) - return array(); - - $ret = array(); - - $ret['@context'] = array( 'https://www.w3.org/ns/activitystreams', 'zot' => 'http://purl.org/zot/protocol'); - - if($i['verb']) { - if(strpos(dirname($i['verb'],'activitystrea.ms/schema/1.0'))) { - $ret['type'] = ucfirst(basename($i['verb'])); - } - elseif(strpos(dirname($i['verb'],'purl.org/zot'))) { - $ret['type'] = 'zot:' . ucfirst(basename($i['verb'])); - } - } - $ret['id'] = $i['plink']; - - $ret['published'] = datetime_convert('UTC','UTC',$i['created'],ATOM_TIME); - - // we need to pass the parent into this -// if($i['id'] != $i['parent'] && $i['obj_type'] === ACTIVITY_OBJ_NOTE) { -// $ret['inReplyTo'] = asencode_note -// } - - if($i['obj_type'] === ACTIVITY_OBJ_NOTE) - $ret['object'] = asencode_note($i); - - $ret['actor'] = asencode_person($i['author']); - - return $ret; -} - -function asencode_note($i) { - - $ret = array(); - - $ret['@type'] = 'Note'; - $ret['id'] = $i['plink']; - if($i['title']) - $ret['title'] = bbcode($i['title']); - - $ret['content'] = bbcode($i['body']); - $ret['zot:owner'] = asencode_person($i['owner']); - $ret['published'] = datetime_convert('UTC','UTC',$i['created'],ATOM_TIME); - if($i['created'] !== $i['edited']) - $ret['updated'] = datetime_convert('UTC','UTC',$i['edited'],ATOM_TIME); - - return $ret; -} - - -function asencode_person($p) { - $ret = []; - $ret['type'] = 'Person'; - $ret['id'] = $p['xchan_url']; - $ret['name'] = $p['xchan_name']; - $ret['icon'] = [ - [ - 'type' => 'Image', - 'mediaType' => $p['xchan_photo_mimetype'], - 'url' => $p['xchan_photo_l'], - 'height' => 300, - 'width' => 300, - ], - [ - 'type' => 'Image', - 'mediaType' => $p['xchan_photo_mimetype'], - 'url' => $p['xchan_photo_m'], - 'height' => 80, - 'width' => 80, - ], - [ - 'type' => 'Image', - 'mediaType' => $p['xchan_photo_mimetype'], - 'url' => $p['xchan_photo_l'], - 'height' => 48, - 'width' => 48, - ] - ]; - $ret['url'] = [ - 'type' => 'Link', - 'mediaType' => 'text/html', - 'href' => $p['xchan_url'] - ]; - - if(array_key_exists('channel_id',$p)) { - $ret['inbox'] = z_root() . '/inbox/' . $p['channel_address']; - $ret['outbox'] = z_root() . '/outbox/' . $p['channel_address']; - $ret['me:magic_keys'] = [ - [ - 'value' => salmon_key($p['channel_pubkey']), - 'key_id' => base64url_encode(hash('sha256',salmon_key($p['channel_pubkey'])),true) - ] - ]; - - - } - else { - $collections = get_xconfig($p['xchan_hash'],'activitystreams','collections',[]); - if($collections) { - $ret = array_merge($ret,$collections); - } - } - - return $ret; -} - - -function activity_mapper($verb) { - - $acts = [ - 'http://activitystrea.ms/schema/1.0/post' => 'Create', - 'http://activitystrea.ms/schema/1.0/update' => 'Update', - 'http://activitystrea.ms/schema/1.0/like' => 'Like', - 'http://activitystrea.ms/schema/1.0/favorite' => 'Like', - 'http://purl.org/zot/activity/dislike' => 'Dislike', - 'http://activitystrea.ms/schema/1.0/tag' => 'Add', - 'http://activitystrea.ms/schema/1.0/follow' => 'Follow', - 'http://activitystrea.ms/schema/1.0/unfollow' => 'Unfollow', - ]; - - - if(array_key_exists($acts[$verb])) { - return $acts[$verb]; - } - return false; -} - - -function activity_obj_mapper($obj,$reverse = false) { - - $objs = [ - 'http://activitystrea.ms/schema/1.0/note' => 'Note', - 'http://activitystrea.ms/schema/1.0/comment' => 'Note', - 'http://activitystrea.ms/schema/1.0/person' => 'Person', - 'http://purl.org/zot/activity/profile' => 'Profile', - 'http://activitystrea.ms/schema/1.0/photo' => 'Image', - 'http://activitystrea.ms/schema/1.0/profile-photo' => 'Icon', - 'http://activitystrea.ms/schema/1.0/event' => 'Event', - 'http://activitystrea.ms/schema/1.0/wiki' => 'Document', - 'http://purl.org/zot/activity/location' => 'Place', - 'http://purl.org/zot/activity/chessgame' => 'Game', - 'http://purl.org/zot/activity/tagterm' => 'zot:Tag', - 'http://purl.org/zot/activity/thing' => 'zot:Thing', - 'http://purl.org/zot/activity/file' => 'zot:File', - 'http://purl.org/zot/activity/poke' => 'zot:Action', - 'http://purl.org/zot/activity/react' => 'zot:Reaction', - 'http://purl.org/zot/activity/mood' => 'zot:Mood', - - ]; - - if(array_key_exists($objs[$verb])) { - return $objs[$verb]; - } - return false; -} diff --git a/include/markdown.php b/include/markdown.php index 5d3c4c7df..ccd108c1b 100644 --- a/include/markdown.php +++ b/include/markdown.php @@ -84,6 +84,72 @@ function markdown_to_bb($s, $use_zrl = false, $options = []) { +function bb_to_markdown_share($match) { + + $matches = array(); + $attributes = $match[1]; + + $author = ""; + preg_match("/author='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $author = urldecode($matches[1]); + + $link = ""; + preg_match("/link='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + $avatar = ""; + preg_match("/avatar='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $avatar = $matches[1]; + + $profile = ""; + preg_match("/profile='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + $posted = ""; + preg_match("/posted='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $posted = $matches[1]; + + // message_id is never used, do we still need it? + $message_id = ""; + preg_match("/message_id='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $message_id = $matches[1]; + + if(! $message_id) { + preg_match("/guid='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $message_id = $matches[1]; + } + + + $reldate = datetime_convert('UTC', date_default_timezone_get(), $posted, 'r'); + + $headline = ''; + + if ($avatar != "") + $headline .= '[url=' . zid($profile) . '][img]' . $avatar . '[/img][/url]'; + + // Bob Smith wrote the following post 2 hours ago + + $fmt = sprintf( t('%1$s wrote the following %2$s %3$s'), + '[url=' . zid($profile) . ']' . $author . '[/url]', + '[url=' . zid($link) . ']' . t('post') . '[/url]', + $reldate + ); + + $headline .= $fmt . "\n\n"; + + $text = $headline . trim($match[2]); + + return $text; +} + + function bb_to_markdown($Text) { @@ -100,9 +166,12 @@ function bb_to_markdown($Text) { // Converting images with size parameters to simple images. Markdown doesn't know it. $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $Text); + $Text = preg_replace_callback("/\[share(.*?)\](.*?)\[\/share\]/ism", 'bb_to_markdown_share', $Text); + call_hooks('bb_to_markdown_bb',$Text); + // Convert it to HTML - don't try oembed $Text = bbcode($Text, $preserve_nl, false); diff --git a/include/xchan.php b/include/xchan.php index 12eb674fa..8c9c09c72 100644 --- a/include/xchan.php +++ b/include/xchan.php @@ -137,3 +137,83 @@ function xchan_fetch($arr) { } +function xchan_keychange_table($table,$column,$oldxchan,$newxchan) { + $r = q("update $table set $column = '%s' where $column = '%s'", + dbesc($newxchan['xchan_hash']), + dbesc($oldxchan['xchan_hash']) + ); + return $r; +} + +function xchan_keychange_acl($table,$column,$oldxchan,$newxchan) { + + $allow = (($table === 'channel') ? 'channel_allow_cid' : 'allow_cid'); + $deny = (($table === 'channel') ? 'channel_deny_cid' : 'deny_cid'); + + + $r = q("select $column, $allow, $deny from $table where ($allow like '%s' or $deny like '%s') ", + dbesc('<' . $oldxchan['xchan_hash'] . '>'), + dbesc('<' . $oldxchan['xchan_hash'] . '>') + ); + + if($r) { + foreach($r as $rv) { + $z = q("update $table set $allow = '%s', $deny = '%s' where $column = %d", + dbesc(str_replace('<' . $oldxchan['xchan_hash'] . '>', '<' . $newxchan['xchan_hash'] . '>', + $rv[$allow])), + dbesc(str_replace('<' . $oldxchan['xchan_hash'] . '>', '<' . $newxchan['xchan_hash'] . '>', + $rv[$deny])), + intval($rv[$column]) + ); + } + } + return $z; +} + + +function xchan_change_key($oldx,$newx,$data) { + + $tables = [ + 'abook' => 'abook_xchan', + 'abconfig' => 'xchan', + 'group_member' => 'xchan', + 'chat' => 'chat_xchan', + 'chatpresence' => 'cp_xchan', + 'event' => 'event_xchan', + 'item' => 'owner_xchan', + 'item' => 'author_xchan', + 'item' => 'source_xchan', + 'mail' => 'from_xchan', + 'mail' => 'to_xchan', + 'shares' => 'share_xchan', + 'source' => 'src_channel_xchan', + 'source' => 'src_xchan', + 'xchat' => 'xchat_xchan', + 'xconfig' => 'xchan', + 'xign' => 'xchan', + 'xlink' => 'xlink_xchan', + 'xprof' => 'xprof_hash', + 'xtag' => 'xtag_hash' + ]; + + + $acls = [ + 'channel' => 'channel_id', + 'attach' => 'id', + 'chatroom' => 'cr_id', + 'event' => 'id', + 'item' => 'id', + 'menu_item' => 'mitem_id', + 'obj' => 'obj_id', + 'photo' => 'id' + ]; + + + foreach($tables as $k => $v) { + xchan_keychange_table($k,$v,$oldx,$newx); + } + + foreach($acls as $k => $v) { + xchan_keychange_acl($k,$v,$oldx,$newx); + } +}
\ No newline at end of file diff --git a/include/zot.php b/include/zot.php index 8bbc4a969..75c37836d 100644 --- a/include/zot.php +++ b/include/zot.php @@ -31,9 +31,9 @@ require_once('include/perm_upgrade.php'); * @param string $channel_nick a unique nickname of controlling entity * @returns string */ + function zot_new_uid($channel_nick) { $rawstr = z_root() . '/' . $channel_nick . '.' . mt_rand(); - return(base64url_encode(hash('whirlpool', $rawstr, true), true)); } @@ -49,6 +49,7 @@ function zot_new_uid($channel_nick) { * @param string $guid * @param string $guid_sig */ + function make_xchan_hash($guid, $guid_sig) { return base64url_encode(hash('whirlpool', $guid . $guid_sig, true)); } @@ -62,17 +63,17 @@ function make_xchan_hash($guid, $guid_sig) { * @param string $hash - xchan_hash * @returns array of hubloc (hub location structures) * * \b hubloc_id int - * * \b hubloc_guid char(255) + * * \b hubloc_guid char(191) * * \b hubloc_guid_sig text - * * \b hubloc_hash char(255) - * * \b hubloc_addr char(255) + * * \b hubloc_hash char(191) + * * \b hubloc_addr char(191) * * \b hubloc_flags int * * \b hubloc_status int - * * \b hubloc_url char(255) + * * \b hubloc_url char(191) * * \b hubloc_url_sig text - * * \b hubloc_host char(255) - * * \b hubloc_callback char(255) - * * \b hubloc_connect char(255) + * * \b hubloc_host char(191) + * * \b hubloc_callback char(191) + * * \b hubloc_connect char(191) * * \b hubloc_sitekey text * * \b hubloc_updated datetime * * \b hubloc_connected datetime @@ -97,7 +98,7 @@ function zot_get_hublocs($hash) { * @param array $channel * sender channel structure * @param string $type - * packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'force_refresh', 'notify', 'auth_check' + * 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 @@ -111,18 +112,21 @@ function zot_get_hublocs($hash) { */ function zot_build_packet($channel, $type = 'notify', $recipients = null, $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'])), + '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'])), + 'url_sig' => base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey'],$sig_method)), 'sitekey' => get_config('system','pubkey') ], 'callback' => '/post', 'version' => ZOT_REVISION, - 'encryption' => crypto_methods() + 'encryption' => crypto_methods(), + 'signing' => signing_methods() ]; if ($recipients) { @@ -134,7 +138,7 @@ function zot_build_packet($channel, $type = 'notify', $recipients = null, $remot if ($secret) { $data['secret'] = $secret; - $data['secret_sig'] = base64url_encode(rsa_sign($secret,$channel['channel_prvkey'])); + $data['secret_sig'] = base64url_encode(rsa_sign($secret,$channel['channel_prvkey'],$sig_method)); } if ($extra) { @@ -529,7 +533,7 @@ function zot_gethub($arr, $multiple = false) { } $limit = (($multiple) ? '' : ' limit 1 '); - $sitekey = ((array_key_exists('sitekey',$arr) && $arr['sitekey']) ? " and hubloc_sitekey = '" . protect_sprintf($arr['sitekey']) . "' " : ''); + $sitekey = ((array_key_exists('sitekey',$arr) && $arr['sitekey']) ? " and hubloc_sitekey = '" . dbesc(protect_sprintf($arr['sitekey'])) . "' " : ''); $r = q("select hubloc.*, site.site_crypto from hubloc left join site on hubloc_url = site_url where hubloc_guid = '%s' and hubloc_guid_sig = '%s' @@ -575,6 +579,8 @@ function zot_register_hub($arr) { if($arr['url'] && $arr['url_sig'] && $arr['guid'] && $arr['guid_sig']) { + $sig_methods = ((array_key_exists('signing',$arr) && is_array($arr['signing'])) ? $arr['signing'] : [ 'sha256' ]); + $guid_hash = make_xchan_hash($arr['guid'],$arr['guid_sig']); $url = $arr['url'] . '/.well-known/zot-info/?f=&guid_hash=' . $guid_hash; @@ -594,17 +600,18 @@ function zot_register_hub($arr) { * our current communication. */ - if((rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$record['key'])) - && (rsa_verify($arr['url'],base64url_decode($arr['url_sig']),$record['key'])) + foreach($sig_methods as $method) { + if((rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$record['key'],$method)) + && (rsa_verify($arr['url'],base64url_decode($arr['url_sig']),$record['key'],$method)) && ($arr['guid'] === $record['guid']) && ($arr['guid_sig'] === $record['guid_sig'])) { - - $c = import_xchan($record); - if($c['success']) - $result['success'] = true; - } - else { - logger('zot_register_hub: failure to verify returned packet.'); + $c = import_xchan($record); + if($c['success']) + $result['success'] = true; + } + else { + logger('zot_register_hub: failure to verify returned packet using ' . $method); + } } } } @@ -657,8 +664,19 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { $import_photos = false; - if(! rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$arr['key'])) { - logger('import_xchan: Unable to verify channel signature for ' . $arr['address']); + $sig_methods = ((array_key_exists('signing',$arr) && is_array($arr['signing'])) ? $arr['signing'] : [ 'sha256' ]); + $verified = false; + + foreach($sig_methods as $method) { + if(! rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$arr['key'],$method)) { + logger('import_xchan: Unable to verify channel signature for ' . $arr['address'] . ' using ' . $method); + continue; + } + else { + $verified = true; + } + } + if(! $verified) { $ret['message'] = t('Unable to verify channel signature'); return $ret; } @@ -917,7 +935,7 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { } elseif(! $ud_flags) { // nothing changed but we still need to update the updates record - q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d)>0 ", + q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d) > 0 ", intval(UPDATE_FLAGS_UPDATED), dbesc($address), intval(UPDATE_FLAGS_UPDATED) @@ -2948,6 +2966,11 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { if($packet) logger('packet: ' . print_r($packet, true),LOGGER_DATA, LOG_DEBUG); + $keychange = (($packet && array_key_exists('keychange',$packet)) ? true : false); + if($keychange) { + logger('keychange sync'); + } + if(! $uid) $uid = local_channel(); @@ -2961,6 +2984,7 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { return; $channel = $r[0]; + unset($channel['channel_password']); unset($channel['channel_salt']); @@ -2971,12 +2995,11 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { } } - if(intval($channel['channel_removed'])) return; $h = q("select hubloc.*, site.site_crypto from hubloc left join site on site_url = hubloc_url where hubloc_hash = '%s' and hubloc_deleted = 0", - dbesc($channel['channel_hash']) + dbesc(($keychange) ? $packet['keychange']['old_hash'] : $channel['channel_hash']) ); if(! $h) @@ -3031,7 +3054,15 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { // don't pass these elements, they should not be synchronised - $disallowed = array('channel_id','channel_account_id','channel_primary','channel_prvkey','channel_address','channel_deleted','channel_removed','channel_system'); + + $disallowed = [ + 'channel_id','channel_account_id','channel_primary','channel_address', + 'channel_deleted','channel_removed','channel_system' + ]; + + if(! $keychange) { + $disallowed[] = 'channel_prvkey'; + } if(in_array($k,$disallowed)) continue; @@ -3091,17 +3122,18 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { function process_channel_sync_delivery($sender, $arr, $deliveries) { - require_once('include/import.php'); /** @FIXME this will sync red structures (channel, pconfig and abook). Eventually we need to make this application agnostic. */ - $result = array(); + $result = []; + + $keychange = ((array_key_exists('keychange',$arr)) ? true : false); foreach ($deliveries as $d) { $r = q("select * from channel where channel_hash = '%s' limit 1", - dbesc($d['hash']) + dbesc(($keychange) ? $arr['keychange']['old_hash'] : $d['hash']) ); if (! $r) { @@ -3120,6 +3152,94 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { continue; } + if($keychange) { + // verify the keychange operation + if(! rsa_verify($arr['channel']['channel_pubkey'],base64url_decode($arr['keychange']['new_sig']),$channel['channel_prvkey'])) { + logger('sync keychange: verification failed'); + continue; + } + + $sig = base64url_encode(rsa_sign($channel['channel_guid'],$arr['channel']['channel_prvkey'])); + $hash = make_xchan_hash($channel['channel_guid'],$sig); + + + $r = q("update channel set channel_prvkey = '%s', channel_pubkey = '%s', channel_guid_sig = '%s', + channel_hash = '%s' where channel_id = %d", + dbesc($arr['channel']['channel_prvkey']), + dbesc($arr['channel']['channel_pubkey']), + dbesc($sig), + dbesc($hash), + intval($channel['channel_id']) + ); + if(! $r) { + logger('keychange sync: channel update failed'); + continue; + } + + $r = q("select * from channel where channel_id = %d", + intval($channel['channel_id']) + ); + + if(! $r) { + logger('keychange sync: channel retrieve failed'); + continue; + } + + $channel = $r[0]; + + $h = q("select * from hubloc where hubloc_hash = '%s' and hubloc_url = '%s' ", + dbesc($arr['keychange']['old_hash']), + dbesc(z_root()) + ); + + if($h) { + foreach($h as $hv) { + $hv['hubloc_guid_sig'] = $sig; + $hv['hubloc_hash'] = $hash; + $hv['hubloc_url_sig'] = base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey'])); + hubloc_store_lowlevel($hv); + } + } + + $x = q("select * from xchan where xchan_hash = '%s' ", + dbesc($arr['keychange']['old_hash']) + ); + + $check = q("select * from xchan where xchan_hash = '%s'", + dbesc($hash) + ); + + if(($x) && (! $check)) { + $oldxchan = $x[0]; + foreach($x as $xv) { + $xv['xchan_guid_sig'] = $sig; + $xv['xchan_hash'] = $hash; + $xv['xchan_pubkey'] = $channel['channel_pubkey']; + xchan_store_lowlevel($xv); + $newxchan = $xv; + } + } + + $a = q("select * from abook where abook_xchan = '%s' and abook_self = 1", + dbesc($arr['keychange']['old_hash']) + ); + + if($a) { + q("update abook set abook_xchan = '%s' where abook_id = %d", + dbesc($hash), + intval($a[0]['abook_id']) + ); + } + + xchan_change_key($oldxchan,$newxchan,$arr['keychange']); + + // keychange operations can end up in a confused state if you try and sync anything else + // besides the channel keys, so ignore any other packets. + + continue; + } + + if(array_key_exists('config',$arr) && is_array($arr['config']) && count($arr['config'])) { foreach($arr['config'] as $cat => $k) { foreach($arr['config'][$cat] as $k => $v) @@ -3757,11 +3877,57 @@ function zot_reply_message_request($data) { json_return_and_die($ret); } +function zot_rekey_request($sender,$data) { + + $ret = array('success' => false); + + // newsig is newkey signed with oldkey + + // The original xchan will remain. In Zot/Receiver we will have imported the new xchan and hubloc to verify + // the packet authenticity. What we will do now is verify that the keychange operation was signed by the + // oldkey, and if so change all the abook, abconfig, group, and permission elements which reference the + // old xchan_hash. + + if((! $data['old_key']) && (! $data['new_key']) && (! $data['new_sig'])) + json_return_and_die($ret); + + $oldhash = make_xchan_hash($data['old_guid'],$data['old_guid_sig']); + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($oldhash) + ); + + if(! $r) { + json_return_and_die($ret); + } + + $xchan = $r[0]; + + if(! rsa_verify($data['new_key'],base64url_decode($data['new_sig']),$xchan['xchan_pubkey'])) { + json_return_and_die($ret); + } + + $newhash = make_xchan_hash($sender['guid'],$sender['guid_sig']); + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($newhash) + ); + + $newxchan = $r[0]; + + xchan_change_key($xchan,$newxchan,$data); + + $ret['success'] = true; + json_return_and_die($ret); +} + function zotinfo($arr) { $ret = array('success' => false); + $sig_method = get_config('system','signature_algorithm','sha256'); + $zhash = ((x($arr,'guid_hash')) ? $arr['guid_hash'] : ''); $zguid = ((x($arr,'guid')) ? $arr['guid'] : ''); $zguid_sig = ((x($arr,'guid_sig')) ? $arr['guid_sig'] : ''); @@ -3925,7 +4091,7 @@ function zotinfo($arr) { // Communication details if($token) - $ret['signed_token'] = base64url_encode(rsa_sign('token.' . $token,$e['channel_prvkey'])); + $ret['signed_token'] = base64url_encode(rsa_sign('token.' . $token,$e['channel_prvkey'],$sig_method)); $ret['guid'] = $e['xchan_guid']; @@ -3994,7 +4160,7 @@ function zotinfo($arr) { $ret['site'] = array(); $ret['site']['url'] = z_root(); - $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(),$e['channel_prvkey'])); + $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(),$e['channel_prvkey'],$sig_method)); $ret['site']['zot_auth'] = z_root() . '/magic'; $dirmode = get_config('system','directory_mode'); @@ -4012,6 +4178,7 @@ function zotinfo($arr) { $ret['site']['encryption'] = crypto_methods(); + $ret['site']['signing'] = signing_methods(); // hide detailed site information if you're off the grid |