diff options
author | friendica <info@friendica.com> | 2013-12-02 17:35:44 -0800 |
---|---|---|
committer | friendica <info@friendica.com> | 2013-12-02 17:35:44 -0800 |
commit | e0f4a76856bc891f7195ba0044518c24fbfac5a9 (patch) | |
tree | dc75e6f7f8cf0f2e80b98f4a32fa713ba4d15026 | |
parent | d9f67876dce5da9ed056726f792e087d142699cb (diff) | |
download | volse-hubzilla-e0f4a76856bc891f7195ba0044518c24fbfac5a9.tar.gz volse-hubzilla-e0f4a76856bc891f7195ba0044518c24fbfac5a9.tar.bz2 volse-hubzilla-e0f4a76856bc891f7195ba0044518c24fbfac5a9.zip |
magic-auth re-factor
-rw-r--r-- | mod/magic.php | 78 | ||||
-rw-r--r-- | mod/post.php | 217 |
2 files changed, 122 insertions, 173 deletions
diff --git a/mod/magic.php b/mod/magic.php index e3b14db62..29d16eec5 100644 --- a/mod/magic.php +++ b/mod/magic.php @@ -13,64 +13,20 @@ function magic_init(&$a) { $dest = ((x($_REQUEST,'dest')) ? $_REQUEST['dest'] : ''); $rev = ((x($_REQUEST,'rev')) ? intval($_REQUEST['rev']) : 0); - if($hash) { - $x = q("select xchan.xchan_url, hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash - where hubloc_hash = '%s' and (hubloc_flags & %d) order by hubloc_id desc limit 1", - dbesc($hash), - intval(HUBLOC_FLAGS_PRIMARY) - ); - } - elseif($addr) { - $x = q("select hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash - where xchan_addr = '%s' and (hubloc_flags & %d) order by hubloc_id desc limit 1", - dbesc($addr), - intval(HUBLOC_FLAGS_PRIMARY) - ); - } - else { - - // See if we know anybody at the dest site that will unlock the door for us - // This is the equivalent of buzzing every apartment in an apartment block - // to get inside the front gate. The thing about magic auth is that we're - // authenticating to the other site. Permissions provided by various - // channels will still affect what we can do once authenticated. - - $b = explode('/',$dest); - - if(count($b) >= 2) { - $u = $b[0] . '//' . $b[2]; - - if(local_user()) { - // first look for a connection or anybody who knows us - $x = q("select xchan.xchan_url, hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash - left join abook on abook_xchan = hubloc_hash - where abook_channel = %d and hubloc_url = '%s' order by hubloc_id desc limit 5", - intval(local_user()), - dbesc($u) - ); - } - if(! $x) { - // no luck - ok anybody will do - $x = q("select xchan.xchan_url, hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash - where hubloc_url = '%s' order by hubloc_id desc limit 5", - dbesc($u) - ); - } - - if($x) { - // They must have a valid hubloc_addr - while(! strpos($x[0]['hubloc_addr'],'@')) { - array_shift($x); - } - } + $parsed = parse_url($dest); + if(! $parsed) + goaway($dest); - } - } + $basepath = $parsed['scheme'] . '://' . $parsed['host'] . (($parsed['port']) ? ':' . $parsed['port'] : ''); + $x = q("select * from hubloc where hubloc_url = '%s' order by hubloc_connected desc limit 1" + dbesc($basepath) + ); + if(! $x) { - // Finger them if they've never been seen here before + // Somebody new? Finger them if they've never been seen here before if($addr) { $ret = zot_finger($addr,null); @@ -78,10 +34,11 @@ function magic_init(&$a) { $j = json_decode($ret['body'],true); if($j) import_xchan($j); - $x = q("select hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash - where xchan_addr = '%s' and (hubloc_flags & %d) order by hubloc_id desc limit 1", - dbesc($addr), - intval(HUBLOC_FLAGS_PRIMARY) + + // Now try again + + $x = q("select * from hubloc where hubloc_url = '%s' order by hubloc_connected desc limit 1" + dbesc($basepath) ); } } @@ -112,7 +69,7 @@ function magic_init(&$a) { if(! $arr['proceed']) goaway($dest); - if($x[0]['hubloc_url'] === z_root()) { + if((get_observer_hash()) && ($x[0]['hubloc_url'] === z_root())) { // We are already authenticated on this site and a registered observer. // Just redirect. goaway($dest); @@ -143,7 +100,6 @@ function magic_init(&$a) { . '&sec=' . $token . '&dest=' . urlencode($dest) . '&version=' . ZOT_REVISION); } - if(strpos($dest,'/')) - goaway($dest); - goaway(z_root()); + goaway($dest); + } diff --git a/mod/post.php b/mod/post.php index 95821307b..a0628bbfa 100644 --- a/mod/post.php +++ b/mod/post.php @@ -18,27 +18,26 @@ function post_init(&$a) { * Magic Auth * ========== * - * So-called "magic auth" takes place by a special exchange. On the remote computer, a redirection is made to the zot endpoint with special GET parameters. + * So-called "magic auth" takes place by a special exchange. On the site where the "channel to be authenticated" lives (e.g. $mysite), + * a redirection is made via $mysite/magic to the zot endpoint of the remote site ($remotesite) with special GET parameters. * - * Endpoint: https://example.com/post/name (name is now optional - we are authenticating to a site, not a channel) + * The endpoint is typically https://$remotesite/post - or whatever was specified as the callback url in prior communications + * (we will bootstrap an address and fetch a zot info packet if possible where no prior communications exist) * - * where 'name' is the left hand side of the channel webbie, for instance 'mike' where the webbie is 'mike@zothub.com' + * Four GET parameters are supplied: * - * Additionally four GET parameters are supplied: - * - ** auth => the webbie of the person requesting access + ** auth => the urlencoded webbie (channel@host.domain) of the channel requesting access ** dest => the desired destination URL (urlencoded) - ** sec => a random string which is also stored locally for use during the verification phase. + ** sec => a random string which is also stored on the remote site for use during the verification phase. ** version => the zot revision * - * When this packet is received, a zot message is sent to the site hosting the request auth identity. + * When this packet is received, an "auth-check" zot message is sent to $mysite. * (e.g. if $_GET['auth'] is foobar@podunk.edu, a zot packet is sent to the podunk.edu zot endpoint, which is typically /post) * If no information has been recorded about the requesting identity a zot information packet will be retrieved before * continuing. * - * The sender of this packet is the name attached to the request endpoint. e.g. 'mike' in this example. If this channel - * cannot be located, we will choose any local channel as the sender. The recipients will be a single recipient corresponding - * to the guid and guid_sig we have associated with the auth identity + * The sender of this packet is a random site user. The recipients will be a single recipient corresponding + * to the guid and guid_sig we have associated with the requesting auth identity * * * { @@ -72,19 +71,13 @@ function post_init(&$a) { * } * * 'confirm' in this case is the base64url encoded RSA signature of the concatenation of 'secret' with the - * base64url encoded whirlpool hash of the source guid and guid_sig; signed with the source channel private key. + * base64url encoded whirlpool hash of the requestor's guid and guid_sig; signed with the source channel private key. * This prevents a man-in-the-middle from inserting a rogue success packet. Upon receipt and successful * verification of this packet, the destination site will redirect to the original destination URL and indicate a successful remote login. * * * */ - - if(argc() > 1) { - $webbie = argv(1); - } - else - $webbie = ''; if(array_key_exists('auth',$_REQUEST)) { logger('mod_zot: auth request received.'); @@ -94,28 +87,19 @@ function post_init(&$a) { $version = $_REQUEST['version']; - if($webbie) { - $c = q("select * from channel where channel_address = '%s' limit 1", - dbesc($webbie) - ); - } - if(! $c) { - - // They are authenticating ultimately to the site and not to a particular channel. - // Any channel will do, providing it's currently active. We just need to have an - // identity to attach to the packet we send back. So find one. - - $c = q("select * from channel where not ( channel_pageflags & %d ) limit 1", - intval(PAGE_REMOVED) - ); + // They are authenticating ultimately to the site and not to a particular channel. + // Any channel will do, providing it's currently active. We just need to have an + // identity to attach to the packet we send back. So find one. - if(! $c) { + $c = q("select * from channel where not ( channel_pageflags & %d ) limit 1", + intval(PAGE_REMOVED) + ); - // nobody here + if(! $c) { + // nobody here - logger('mod_zot: auth: unable to find channel ' . $webbie); - goaway($desturl); - } + logger('mod_zot: auth: unable to find a response channel'); + goaway($desturl); } // Try and find a hubloc for the person attempting to auth @@ -140,7 +124,7 @@ function post_init(&$a) { goaway($desturl); } - logger('mod_zot: auth request received from ' . $x[0]['xchan_addr'] . ' for ' . (($webbie) ? $webbie : 'undefined')); + logger('mod_zot: auth request received from ' . $x[0]['xchan_addr'] ); // check credentials and access @@ -153,10 +137,12 @@ function post_init(&$a) { $already_authed = ((($remote) && ($x[0]['hubloc_hash'] == $remote)) ? true : false); if(! $already_authed) { - // Auth packets MUST use ultra top-secret hush-hush mode - $p = zot_build_packet($c[0],$type = 'auth_check', - array(array('guid' => $x[0]['hubloc_guid'],'guid_sig' => $x[0]['hubloc_guid_sig'])), - $x[0]['hubloc_sitekey'], $sec); + + // Auth packets MUST use ultra top-secret hush-hush mode - e.g. the entire packet is encrypted using the site private key + // The actual channel sending the packet ($c[0]) is not important, but this provides a generic zot packet with a sender + // which can be verified + + $p = zot_build_packet($c[0],$type = 'auth_check', array(array('guid' => $x[0]['hubloc_guid'],'guid_sig' => $x[0]['hubloc_guid_sig'])), $x[0]['hubloc_sitekey'], $sec); $result = zot_zot($x[0]['hubloc_callback'],$p); if(! $result['success']) { logger('mod_zot: auth_check callback failed.'); @@ -198,7 +184,7 @@ function post_init(&$a) { logger('mod_zot: auth success from ' . $x[0]['xchan_addr'] . ' for ' . $webbie); } else { - logger('mod_zot: still not authenticated: ' . $x[0]['xchan_addr']); + logger('mod_zot: magic-auth failure - not authenticated: ' . $x[0]['xchan_addr']); q("update hubloc set hubloc_status = (hubloc_status | %d ) where hubloc_addr = '%s'", intval(HUBLOC_RECEIVE_ERROR), dbesc($x[0]['xchan_addr']) @@ -378,6 +364,7 @@ function post_post(&$a) { logger('mod_zot: ' . print_r($_REQUEST,true), LOGGER_DEBUG); + $encrypted_packet = false; $ret = array('success' => false); $data = json_decode($_REQUEST['data'],true); @@ -390,17 +377,10 @@ function post_post(&$a) { */ if(array_key_exists('iv',$data)) { + $encrypted_packet = true; $data = crypto_unencapsulate($data,get_config('system','prvkey')); logger('mod_zot: decrypt1: ' . $data, LOGGER_DATA); - -// susceptible to Bleichenbacher's attack -// if(! $data) { -// $ret['message'] = 'Decryption failed.'; -// json_return_and_die($ret); -// } - $data = json_decode($data,true); - } if(! $data) { @@ -539,6 +519,8 @@ function post_post(&$a) { } + + /** * All other message types require us to verify the sender. This is a generic check, so we * will do it once here and bail if anything goes wrong. @@ -593,6 +575,78 @@ function post_post(&$a) { if(array_key_exists('recipients',$data)) $recipients = $data['recipients']; + + if($msgtype === 'auth_check') { + + logger('mod_zot: auth_check', LOGGER_DEBUG); + + if(! $encrypted_packet) { + logger('mod_zot: auth_check packet was not encrypted.'); + json_return_and_die($ret); + } + + $arr = $data['sender']; + $sender_hash = base64url_encode(hash('whirlpool',$arr['guid'] . $arr['guid_sig'], true)); + + // garbage collect any old unused notifications + q("delete from verify where type = 'auth' and created < UTC_TIMESTAMP() - INTERVAL 10 MINUTE"); + + $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", + dbesc($sender_hash) + ); + + // We created a unique hash in mod/magic.php when we invoked remote auth, and stored it in + // the verify table. It is now coming back to us as 'secret' and is signed by a channel at the other end. + // First verify their signature. We will have obtained a zot-info packet from them as part of the sender + // verification. + + if((! $y) || (! rsa_verify($data['secret'],base64url_decode($data['secret_sig']),$y[0]['xchan_pubkey']))) { + logger('mod_zot: auth_check: sender not found or secret_sig invalid.'); + json_return_and_die($ret); + } + + // There should be exactly one recipient, the original auth requestor + + if($data['recipients']) { + + $arr = $data['recipients'][0]; + $recip_hash = base64url_encode(hash('whirlpool',$arr['guid'] . $arr['guid_sig'], true)); + $c = q("select channel_id, channel_prvkey from channel where channel_hash = '%s' limit 1", + dbesc($recip_hash) + ); + if(! $c) { + logger('mod_zot: auth_check: recipient channel not found.'); + json_return_and_die($ret); + } + + $confirm = base64url_encode(rsa_sign($data['secret'] . $recip_hash,$c[0]['channel_prvkey'])); + + // This additionally checks for forged sites since we already stored the expected result in meta + // and we've already verified that this is them via zot_gethub() and that their key signed our token + + $z = q("select id from verify where channel = %d and type = 'auth' and token = '%s' and meta = '%s' limit 1", + intval($c[0]['channel_id']), + dbesc($data['secret']), + dbesc($data['sender']['url']) + ); + if(! $z) { + logger('mod_zot: auth_check: verification key not found.'); + json_return_and_die($ret); + } + $r = q("delete from verify where id = %d limit 1", + intval($z[0]['id']) + ); + + logger('mod_zot: auth_check: success', LOGGER_DEBUG); + $ret['success'] = true; + $ret['confirm'] = $confirm; + json_return_and_die($ret); + + } + json_return_and_die($ret); + } + + if($msgtype === 'purge') { if($recipients) { // basically this means "unfriend" @@ -683,67 +737,6 @@ function post_post(&$a) { } - if($msgtype === 'auth_check') { - logger('mod_zot: auth_check'); - $arr = $data['sender']; - $sender_hash = base64url_encode(hash('whirlpool',$arr['guid'] . $arr['guid_sig'], true)); - - // garbage collect any old unused notifications - q("delete from verify where type = 'auth' and created < UTC_TIMESTAMP() - INTERVAL 10 MINUTE"); - - $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", - dbesc($sender_hash) - ); - - // We created a unique hash in mod/magic.php when we invoked remote auth, and stored it in - // the verify table. It is now coming back to us as 'secret' and is signed by the other site. - // First verify their signature. - - if((! $y) || (! rsa_verify($data['secret'],base64url_decode($data['secret_sig']),$y[0]['xchan_pubkey']))) { - logger('mod_zot: auth_check: sender not found or secret_sig invalid.'); - json_return_and_die($ret); - } - - // There should be exactly one recipient - if($data['recipients']) { - - $arr = $data['recipients'][0]; - $recip_hash = base64url_encode(hash('whirlpool',$arr['guid'] . $arr['guid_sig'], true)); - $c = q("select channel_id, channel_prvkey from channel where channel_hash = '%s' limit 1", - dbesc($recip_hash) - ); - if(! $c) { - logger('mod_zot: auth_check: recipient channel not found.'); - json_return_and_die($ret); - } - - $confirm = base64url_encode(rsa_sign($data['secret'] . $recip_hash,$c[0]['channel_prvkey'])); - - // This additionally checks for forged sites since we already stored the expected result in meta - // and we've already verified that this is them via zot_gethub() and that their key signed our token - - $z = q("select id from verify where channel = %d and type = 'auth' and token = '%s' and meta = '%s' limit 1", - intval($c[0]['channel_id']), - dbesc($data['secret']), - dbesc($data['sender']['url']) - ); - if(! $z) { - logger('mod_zot: auth_check: verification key not found.'); - json_return_and_die($ret); - } - $r = q("delete from verify where id = %d limit 1", - intval($z[0]['id']) - ); - - logger('mod_zot: auth_check: success', LOGGER_DEBUG); - $ret['success'] = true; - $ret['confirm'] = $confirm; - json_return_and_die($ret); - - } - json_return_and_die($ret); - } - // catchall json_return_and_die($ret); |