cleanup of jsalmon, return hubloc with verification success to save a lookup

This commit is contained in:
zotlabs 2018-06-28 20:25:43 -07:00
parent 7b841a6d40
commit bf405df4ed
7 changed files with 111 additions and 79 deletions

View file

@ -241,7 +241,13 @@ class Activity {
break;
case 'Mention':
$ret[] = [ 'ttype' => TERM_MENTION, 'url' => $t['href'], 'term' => escape_tags((substr($t['name'],0,1) === '@') ? substr($t['name'],1) : $t['name']) ];
$mention_type = substr($t['name'],0,1);
if($mention_type === '!') {
$ret[] = [ 'ttype' => TERM_FORUM, 'url' => $t['href'], 'term' => escape_tags(substr($t['name'],1)) ];
}
else {
$ret[] = [ 'ttype' => TERM_MENTION, 'url' => $t['href'], 'term' => escape_tags((substr($t['name'],0,1) === '@') ? substr($t['name'],1) : $t['name']) ];
}
break;
default:
@ -268,6 +274,10 @@ class Activity {
}
break;
case TERM_FORUM:
$ret[] = [ 'type' => 'Mention', 'href' => $t['url'], 'name' => '!' . $t['term'] ];
break;
case TERM_MENTION:
$ret[] = [ 'type' => 'Mention', 'href' => $t['url'], 'name' => '@' . $t['term'] ];
break;

View file

@ -7,6 +7,7 @@ namespace Zotlabs\Lib;
*
* Parses an ActivityStream JSON string.
*/
class ActivityStreams {
public $raw = null;
@ -45,7 +46,27 @@ class ActivityStreams {
}
if($this->data) {
// verify and unpack JSalmon signature if present
if(is_array($this->data) && array_key_exists('signed',$this->data)) {
$ret = JSalmon::verify($this->data);
$tmp = JSalmon::unpack($this->data['data']);
if($ret && $ret['success']) {
if($ret['signer']) {
$saved = json_encode($this->data,JSON_UNESCAPED_SLASHES);
$this->data = $tmp;
$this->data['signer'] = $ret['signer'];
$this->data['signed_data'] = $saved;
if($ret['hubloc']) {
$this->data['hubloc'] = $ret['hubloc'];
}
}
}
}
$this->valid = true;
}
if($this->is_valid()) {
@ -249,6 +270,24 @@ class ActivityStreams {
$x = $this->fetch_property($x);
}
// verify and unpack JSalmon signature if present
if(is_array($x) && array_key_exists('signed',$x)) {
$ret = JSalmon::verify($x);
$tmp = JSalmon::unpack($x['data']);
if($ret && $ret['success']) {
if($ret['signer']) {
$saved = json_encode($x,JSON_UNESCAPED_SLASHES);
$x = $tmp;
$x['signer'] = $ret['signer'];
$x['signed_data'] = $saved;
if($ret['hubloc']) {
$x['hubloc'] = $ret['hubloc'];
}
}
}
}
return $x;
}

View file

@ -6,12 +6,9 @@ use Zotlabs\Web\HTTPSig;
class JSalmon {
static function sign($data,$key_id,$key) {
static function sign($data,$key_id,$key,$data_type = 'application/x-zot+json') {
$arr = $data;
$data = json_encode($data,JSON_UNESCAPED_SLASHES);
$data = base64url_encode($data, false); // do not strip padding
$data_type = 'application/x-zot+json';
$data = base64url_encode(json_encode($data,true),true); // strip padding
$encoding = 'base64url';
$algorithm = 'RSA-SHA256';
@ -19,9 +16,9 @@ class JSalmon {
// precomputed base64url encoding of data_type, encoding, algorithm concatenated with periods
$precomputed = '.' . base64url_encode($data_type,false) . '.YmFzZTY0dXJs.UlNBLVNIQTI1Ng==';
$precomputed = '.' . base64url_encode($data_type,true) . '.YmFzZTY0dXJs.UlNBLVNIQTI1Ng';
$signature = base64url_encode(rsa_sign($data . $precomputed, $key), false);
$signature = base64url_encode(rsa_sign($data . $precomputed, $key), true);
return ([
'signed' => true,
@ -31,7 +28,7 @@ class JSalmon {
'alg' => $algorithm,
'sigs' => [
'value' => $signature,
'key_id' => base64url_encode($key_id)
'key_id' => base64url_encode($key_id, true)
]
]);
@ -39,6 +36,7 @@ class JSalmon {
static function verify($x) {
logger('verify');
$ret = [ 'results' => [] ];
if(! is_array($x)) {
@ -48,14 +46,17 @@ class JSalmon {
return $false;
}
$signed_data = preg_replace('/\s+/','',$x) . '.' . base64url_encode($x['data_type'],false) . '.' . base64url_encode($x['encoding'],false) . '.' . base64url_encode($x['alg'],false);
$signed_data = preg_replace('/\s+/','',$x['data']) . '.'
. base64url_encode($x['data_type'],true) . '.'
. base64url_encode($x['encoding'],true) . '.'
. base64url_encode($x['alg'],true);
foreach($sigs as $sig) {
$key = HTTPSig::get_key(EMPTY_STR,base64url_decode($x['sig']['key_id']));
if($key['portable_id'] && $key['public_key']) {
if(rsa_verify($signed_data,base64url_decode($x['sigs']['value']),$key['public_key'])) {
$ret['results'][] = [ 'success' => true, 'signer' => $key['portable_id'] ];
}
$key = HTTPSig::get_key(EMPTY_STR,base64url_decode($x['sigs']['key_id']));
// logger('key: ' . print_r($key,true));
if($key['portable_id'] && $key['public_key']) {
if(rsa_verify($signed_data,base64url_decode($x['sigs']['value']),$key['public_key'])) {
logger('verified');
$ret = [ 'success' => true, 'signer' => $key['portable_id'], 'hubloc' => $key['hubloc'] ];
}
}

View file

@ -126,6 +126,10 @@ class Libzot {
}
if ($msg) {
$actor = channel_url($channel);
if ($encoding === 'activitystreams' && array_key_exists('actor',$msg) && is_string($msg['actor']) && $actor === $msg['actor']) {
$msg = JSalmon::sign($msg,$actor,$channel['channel_prvkey']);
}
$data['data'] = $msg;
}
else {
@ -1115,7 +1119,8 @@ class Libzot {
if(array_key_exists('recipients',$env) && count($env['recipients'])) {
logger('specific recipients');
logger('recipients: ' . print_r($recipients,true));
logger('recipients: ' . print_r($recipients,true),LOGGER_DEBUG);
$recip_arr = array();
foreach($env['recipients'] as $recip) {
$recip_arr[] = $recip;
@ -1265,8 +1270,8 @@ logger('recipients: ' . print_r($recipients,true));
if($env['encoding'] === 'zot' && array_key_exists('flags',$env) && in_array('thread_parent', $env['flags'])) {
return true;
}
if($act['encoding'] === 'activitystreams') {
if(array_key_exists('inReplyTo',$act['data']) && $act['data']['inReplyTo']) {
if($env['encoding'] === 'activitystreams') {
if(array_key_exists('inReplyTo',$env['data']) && $env['data']['inReplyTo']) {
return false;
}
return true;
@ -1329,25 +1334,26 @@ logger('recipients: ' . print_r($recipients,true));
$r[] = $sys['channel_hash'];
}
//@fixme
// look for any public mentions on this site
// They will get filtered by tgroup_check() so we don't need to check permissions now
if($check_mentions) {
// It's a top level post. Look at the tags. See if any of them are mentions and are on this hub.
if($msg['message']['tags']) {
if(is_array($msg['message']['tags']) && $msg['message']['tags']) {
foreach($msg['message']['tags'] as $tag) {
if(($tag['type'] === 'mention' || $tag['type'] === 'forum') && (strpos($tag['url'],z_root()) !== false)) {
$address = basename($tag['url']);
if(array_path_exists('data/object/tag',$msg)) {
if(is_array($msg['data']['object']['tag']) && $msg['data']['object']['tag']) {
foreach($msg['data']['object']['tag'] as $tag) {
if($tag['type'] === 'Mention' && (strpos($tag['href'],z_root()) !== false)) {
$address = basename($tag['href']);
if($address) {
$z = q("select channel_hash as hash from channel where channel_address = '%s'
and channel_removed = 0 limit 1",
dbesc($address)
);
if($z)
$r = array_merge($r,$z);
if($z) {
$r[] = $z[0]['hash'];
}
}
}
}
@ -1359,12 +1365,15 @@ logger('recipients: ' . print_r($recipients,true));
// everybody that stored a copy of the parent. This way we know we're covered. We'll check the
// comment permissions when we deliver them.
if($msg['message']['message_top']) {
if(array_path_exists('data/inReplyTo',$msg)) {
$z = q("select owner_xchan as hash from item where parent_mid = '%s' ",
dbesc($msg['message']['message_top'])
dbesc($msg['data']['inReplyTo'])
);
if($z)
$r = array_merge($r,$z);
if($z) {
foreach($z as $zv) {
$r[] = $zv['hash'];
}
}
}
}

View file

@ -138,8 +138,9 @@ class HTTPSig {
$key = self::get_key($key,$result['signer']);
if(! $key['public_key'])
if(! ($key && $key['public_key'])) {
return $result;
}
$x = rsa_verify($signed_data,$sig_block['signature'],$key['public_key'],$algorithm);
@ -213,13 +214,13 @@ class HTTPSig {
function get_activitystreams_key($id) {
$x = q("select xchan_pubkey, xchan_hash from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1",
$x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1",
dbesc(str_replace('acct:','',$id)),
dbesc($id)
);
if($x && $x[0]['xchan_pubkey']) {
return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] ];
return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ];
}
$r = ActivityStreams::fetch_property($id);
@ -227,11 +228,10 @@ class HTTPSig {
if($r) {
$j = json_decode($r,true);
if(array_key_exists('publicKey',$j) && array_key_exists('publicKeyPem',$j['publicKey'])) {
if((array_key_exists('id',$j['publicKey']) && $j['publicKey']['id'] !== $id) && $j['id'] !== $id)
return false;
return self::convertKey($j['publicKey']['publicKeyPem']);
if(array_key_exists('publicKey',$j) && array_key_exists('publicKeyPem',$j['publicKey']) && array_key_exists('id',$j['publicKey'])) {
if($j['publicKey']['id'] === $id || $j['id'] === $id) {
return [ 'public_key' => self::convertKey($j['publicKey']['publicKeyPem']), 'portable_id' => '', 'hubloc' => [] ];
}
}
}
@ -241,16 +241,16 @@ class HTTPSig {
function get_webfinger_key($id) {
$x = q("select xchan_pubkey, xchan_hash from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1",
$x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1",
dbesc(str_replace('acct:','',$id)),
dbesc($id)
);
if($x && $x[0]['xchan_pubkey']) {
return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] ];
return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ];
}
$wf = Webfinger::exec($id);
$key = [ 'portable_id' => '', 'public_key' => '' ];
$key = [ 'portable_id' => '', 'public_key' => '', 'hubloc' => [] ];
if($wf) {
if(array_key_exists('properties',$wf) && array_key_exists('https://w3id.org/security/v1#publicKeyPem',$wf['properties'])) {
@ -267,6 +267,13 @@ class HTTPSig {
$i = Zotlabs\Lib\Libzot::import_xchan($z['data']);
if($i['success']) {
$key['portable_id'] = $i['hash'];
$x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s' limit 1",
dbesc($l['href'])
);
if($x) {
$key['hubloc'] = $x[0];
}
}
}
}

View file

@ -22,7 +22,6 @@ function abook_store_lowlevel($arr) {
'abook_pending' => ((array_key_exists('abook_pending',$arr)) ? $arr['abook_pending'] : 0),
'abook_unconnected' => ((array_key_exists('abook_unconnected',$arr)) ? $arr['abook_unconnected'] : 0),
'abook_self' => ((array_key_exists('abook_self',$arr)) ? $arr['abook_self'] : 0),
'abook_rself' => ((array_key_exists('abook_rself',$arr)) ? $arr['abook_rself'] : 0),
'abook_feed' => ((array_key_exists('abook_feed',$arr)) ? $arr['abook_feed'] : 0),
'abook_not_here' => ((array_key_exists('abook_not_here',$arr)) ? $arr['abook_not_here'] : 0),
'abook_profile' => ((array_key_exists('abook_profile',$arr)) ? $arr['abook_profile'] : ''),

View file

@ -2566,35 +2566,18 @@ function tag_deliver($uid, $item_id) {
$plustagged = false;
$matches = array();
$pattern = '/[\!@]\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'],'/') . '\[\/zrl\]/';
$pattern = '/[\!@]\!?\[[uz]rl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'],'/') . '\[\/[uz]rl\]/';
if(preg_match($pattern,$body,$matches))
$tagged = true;
// original red forum tagging sequence @forumname+
// standard forum tagging sequence !forumname
$pluspattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/';
$forumpattern = '/\!\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\[\/zrl\]/';
$forumpattern = '/\!\!?\[[uz]rl\=([^\]]*?)\]((?:.(?!\[[uz]rl\=))*?)\[\/[uz]rl\]/';
$found = false;
$matches = array();
if(preg_match_all($pluspattern,$body,$matches,PREG_SET_ORDER)) {
foreach($matches as $match) {
$matched_forums ++;
if($term['url'] === $match[1] && $term['term'] === $match[2] && intval($term['ttype']) === TERM_MENTION) {
if($matched_forums <= $max_forums) {
$plustagged = true;
$found = true;
break;
}
logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring');
}
}
}
if(preg_match_all($forumpattern,$body,$matches,PREG_SET_ORDER)) {
foreach($matches as $match) {
$matched_forums ++;
@ -2822,28 +2805,12 @@ function tgroup_check($uid, $item) {
$body = preg_replace('/\[share(.*?)\[\/share\]/','',$item['body']);
$pluspattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/';
$forumpattern = '/\!\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\[\/zrl\]/';
$forumpattern = '/\!\!?\[[uz]rl\=([^\]]*?)\]((?:.(?!\[[uz]rl\=))*?)\[\/[uz]rl\]/';
$found = false;
$matches = array();
if(preg_match_all($pluspattern,$body,$matches,PREG_SET_ORDER)) {
foreach($matches as $match) {
$matched_forums ++;
if($term['url'] === $match[1] && $term['term'] === $match[2] && intval($term['ttype']) === TERM_MENTION) {
if($matched_forums <= $max_forums) {
$found = true;
break;
}
logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring');
}
}
}
if(preg_match_all($forumpattern,$body,$matches,PREG_SET_ORDER)) {
foreach($matches as $match) {
$matched_forums ++;