aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorzotlabs <mike@macgirvin.com>2018-11-06 20:44:40 -0800
committerzotlabs <mike@macgirvin.com>2018-11-06 20:44:40 -0800
commitbb8b3b92913832750a393533f491725b4330a8e1 (patch)
tree3fbcd1466f061fc85129e3caa165b9df999a59a1
parente7f1d350c92bb2be87adefd33ab877ef6e121af2 (diff)
downloadvolse-hubzilla-bb8b3b92913832750a393533f491725b4330a8e1.tar.gz
volse-hubzilla-bb8b3b92913832750a393533f491725b4330a8e1.tar.bz2
volse-hubzilla-bb8b3b92913832750a393533f491725b4330a8e1.zip
this is brutal
-rw-r--r--Zotlabs/Lib/ActivityStreams.php191
-rw-r--r--Zotlabs/Lib/JSalmon.php50
-rw-r--r--Zotlabs/Lib/Libzot.php74
-rw-r--r--Zotlabs/Lib/Zotfinger.php23
-rw-r--r--Zotlabs/Module/Channel.php44
-rw-r--r--Zotlabs/Zot6/HTTPSig.php90
-rw-r--r--Zotlabs/Zot6/Receiver.php2
-rw-r--r--Zotlabs/Zot6/Zot6Handler.php15
-rw-r--r--include/channel.php31
-rw-r--r--include/xchan.php1
10 files changed, 397 insertions, 124 deletions
diff --git a/Zotlabs/Lib/ActivityStreams.php b/Zotlabs/Lib/ActivityStreams.php
index 37e717f58..a322637fd 100644
--- a/Zotlabs/Lib/ActivityStreams.php
+++ b/Zotlabs/Lib/ActivityStreams.php
@@ -7,22 +7,25 @@ namespace Zotlabs\Lib;
*
* Parses an ActivityStream JSON string.
*/
+
class ActivityStreams {
- public $raw = null;
- public $data;
- public $valid = false;
- public $id = '';
- public $type = '';
- public $actor = null;
- public $obj = null;
- public $tgt = null;
- public $origin = null;
- public $owner = null;
- public $signer = null;
- public $ldsig = null;
- public $sigok = false;
- public $recips = null;
+ public $raw = null;
+ public $data = null;
+ public $valid = false;
+ public $deleted = false;
+ public $id = '';
+ public $parent_id = '';
+ public $type = '';
+ public $actor = null;
+ public $obj = null;
+ public $tgt = null;
+ public $origin = null;
+ public $owner = null;
+ public $signer = null;
+ public $ldsig = null;
+ public $sigok = false;
+ public $recips = null;
public $raw_recips = null;
/**
@@ -35,16 +38,49 @@ class ActivityStreams {
function __construct($string) {
$this->raw = $string;
- $this->data = json_decode($string, true);
+
+ if(is_array($string)) {
+ $this->data = $string;
+ }
+ else {
+ $this->data = json_decode($string, true);
+ }
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(array_key_exists('type',$this->data) && array_key_exists('actor',$this->data) && array_key_exists('object',$this->data)) {
+ if($this->data['type'] === 'Delete' && $this->data['actor'] === $this->data['object']) {
+ $this->deleted = $this->data['actor'];
+ $this->valid = false;
+ }
+ }
+
}
if($this->is_valid()) {
$this->id = $this->get_property_obj('id');
$this->type = $this->get_primary_type();
- $this->actor = $this->get_compound_property('actor');
+ $this->actor = $this->get_actor('actor','','');
$this->obj = $this->get_compound_property('object');
$this->tgt = $this->get_compound_property('target');
$this->origin = $this->get_compound_property('origin');
@@ -53,14 +89,31 @@ class ActivityStreams {
$this->ldsig = $this->get_compound_property('signature');
if($this->ldsig) {
$this->signer = $this->get_compound_property('creator',$this->ldsig);
- if($this->signer && $this->signer['publicKey'] && $this->signer['publicKey']['publicKeyPem']) {
- $this->sigok = \Zotlabs\Lib\LDSignatures::verify($this->data,$this->signer['publicKey']['publicKeyPem']);
+ if($this->signer && is_array($this->signer) && array_key_exists('publicKey',$this->signer) && is_array($this->signer['publicKey']) && $this->signer['publicKey']['publicKeyPem']) {
+ $this->sigok = LDSignatures::verify($this->data,$this->signer['publicKey']['publicKeyPem']);
}
}
- if(($this->type === 'Note') && (! $this->obj)) {
+ if(! $this->obj) {
$this->obj = $this->data;
$this->type = 'Create';
+ if(! $this->actor) {
+ $this->actor = $this->get_actor('attributedTo',$this->obj);
+ }
+ }
+
+ if($this->obj && is_array($this->obj) && $this->obj['actor'])
+ $this->obj['actor'] = $this->get_actor('actor',$this->obj);
+ if($this->tgt && is_array($this->tgt) && $this->tgt['actor'])
+ $this->tgt['actor'] = $this->get_actor('actor',$this->tgt);
+
+ $this->parent_id = $this->get_property_obj('inReplyTo');
+
+ if((! $this->parent_id) && is_array($this->obj)) {
+ $this->parent_id = $this->obj['inReplyTo'];
+ }
+ if((! $this->parent_id) && is_array($this->obj)) {
+ $this->parent_id = $this->obj['id'];
}
}
}
@@ -190,44 +243,122 @@ class ActivityStreams {
$base = (($base) ? $base : $this->data);
$propname = (($prefix) ? $prefix . ':' : '') . $property;
+ if(! is_array($base)) {
+ btlogger('not an array: ' . print_r($base,true));
+ return null;
+ }
+
return ((array_key_exists($propname, $base)) ? $base[$propname] : null);
}
+
/**
* @brief Fetches a property from an URL.
*
* @param string $url
* @return NULL|mixed
*/
+
function fetch_property($url) {
+ return self::fetch($url);
+ }
+
+ static function fetch($url) {
$redirects = 0;
if(! check_siteallowed($url)) {
logger('blacklisted: ' . $url);
return null;
}
-
+ logger('fetch: ' . $url, LOGGER_DEBUG);
$x = z_fetch_url($url, true, $redirects,
- ['headers' => [ 'Accept: application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ]]);
- if($x['success'])
+ [ 'headers' => [ 'Accept: application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ]]);
+ if($x['success']) {
+ $y = json_decode($x['body'],true);
+ logger('returned: ' . json_encode($y,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
return json_decode($x['body'], true);
+ }
+ else {
+ logger('fetch failed: ' . $url);
+ }
+ return null;
+ }
+
+ static function is_an_actor($s) {
+ return(in_array($s,[ 'Application','Group','Service','Person','Service' ]));
+ }
+
+ /**
+ * @brief
+ *
+ * @param string $property
+ * @param array $base
+ * @param string $namespace (optional) default empty
+ * @return NULL|mixed
+ */
+
+ function get_actor($property,$base='',$namespace = '') {
+ $x = $this->get_property_obj($property, $base, $namespace);
+ if($this->is_url($x)) {
+
+ // SECURITY: If we have already stored the actor profile, re-generate it
+ // from cached data - don't refetch it from the network
+ $r = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s' limit 1",
+ dbesc($x)
+ );
+ if($r) {
+ $y = Activity::encode_person($r[0]);
+ $y['cached'] = true;
+ return $y;
+ }
+ }
+ $actor = $this->get_compound_property($property,$base,$namespace,true);
+ if(is_array($actor) && self::is_an_actor($actor['type'])) {
+ if(array_key_exists('id',$actor) && (! array_key_exists('inbox',$actor))) {
+ $actor = $this->fetch_property($actor['id']);
+ }
+ return $actor;
+ }
return null;
}
+
/**
* @brief
*
* @param string $property
* @param array $base
* @param string $namespace (optional) default empty
+ * @param boolean $first (optional) default false, if true and result is a sequential array return only the first element
* @return NULL|mixed
*/
- function get_compound_property($property, $base = '', $namespace = '') {
+ function get_compound_property($property, $base = '', $namespace = '', $first = false) {
$x = $this->get_property_obj($property, $base, $namespace);
if($this->is_url($x)) {
$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'];
+ }
+ }
+ }
+ }
+ if($first && is_array($x) && array_key_exists(0,$x)) {
+ return $x[0];
+ }
+
return $x;
}
@@ -273,4 +404,18 @@ class ActivityStreams {
return $x;
}
+
+ static function is_as_request() {
+
+ $x = getBestSupportedMimeType([
+ 'application/ld+json;profile="https://www.w3.org/ns/activitystreams"',
+ 'application/activity+json',
+ 'application/ld+json;profile="http://www.w3.org/ns/activitystreams"'
+ ]);
+
+ return(($x) ? true : false);
+
+ }
+
+
} \ No newline at end of file
diff --git a/Zotlabs/Lib/JSalmon.php b/Zotlabs/Lib/JSalmon.php
index 43d5f9d09..f35bf6235 100644
--- a/Zotlabs/Lib/JSalmon.php
+++ b/Zotlabs/Lib/JSalmon.php
@@ -2,15 +2,13 @@
namespace Zotlabs\Lib;
+use Zotlabs\Zot6\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';
@@ -18,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,
@@ -30,9 +28,45 @@ class JSalmon {
'alg' => $algorithm,
'sigs' => [
'value' => $signature,
- 'key_id' => base64url_encode($key_id)
+ 'key_id' => base64url_encode($key_id, true)
]
]);
}
+
+ static function verify($x) {
+
+ logger('verify');
+ $ret = [ 'results' => [] ];
+
+ if(! is_array($x)) {
+ return $false;
+ }
+ if(! ( array_key_exists('signed',$x) && $x['signed'])) {
+ return $false;
+ }
+
+ $signed_data = preg_replace('/\s+/','',$x['data']) . '.'
+ . base64url_encode($x['data_type'],true) . '.'
+ . base64url_encode($x['encoding'],true) . '.'
+ . base64url_encode($x['alg'],true);
+
+ $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'] ];
+ }
+ }
+
+ return $ret;
+
+ }
+
+ static function unpack($data) {
+ return json_decode(base64url_decode($data),true);
+ }
+
+
} \ No newline at end of file
diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php
index 3d18df051..23703a040 100644
--- a/Zotlabs/Lib/Libzot.php
+++ b/Zotlabs/Lib/Libzot.php
@@ -109,7 +109,7 @@ class Libzot {
$data = [
'type' => $type,
'encoding' => $encoding,
- 'sender' => $channel['channel_hash'],
+ 'sender' => $channel['channel_portable_id'],
'site_id' => self::make_xchan_hash(z_root(), get_config('system','pubkey')),
'version' => System::get_zot_revision(),
];
@@ -324,14 +324,19 @@ class Libzot {
logger('zot-info: ' . print_r($record,true), LOGGER_DATA, LOG_DEBUG);
$x = self::import_xchan($record['data'], (($force) ? UPDATE_FLAGS_FORCED : UPDATE_FLAGS_UPDATED));
-logger('1');
+
if(! $x['success'])
return false;
-logger('2');
+
if($channel && $record['data']['permissions']) {
-logger('3');
- $old_read_stream_perm = their_perms_contains($channel['channel_id'],$x['hash'],'view_stream');
- set_abconfig($channel['channel_id'],$x['hash'],'system','their_perms',$record['data']['permissions']);
+ $permissions = explode(',',$record['data']['permissions']);
+ if($permissions && is_array($permissions)) {
+ $old_read_stream_perm = get_abconfig($channel['channel_id'],$x['hash'],'their_perms','view_stream');
+
+ foreach($permissions as $k => $v) {
+ set_abconfig($channel['channel_id'],$x['hash'],'their_perms',$k,$v);
+ }
+ }
if(array_key_exists('profile',$record['data']) && array_key_exists('next_birthday',$record['data']['profile'])) {
$next_birthday = datetime_convert('UTC','UTC',$record['data']['profile']['next_birthday']);
@@ -380,14 +385,16 @@ logger('4');
else {
$p = Permissions::connect_perms($channel['channel_id']);
- $my_perms = Permissions::serialise($p['perms']);
+ $my_perms = $p['perms'];
$automatic = $p['automatic'];
// new connection
if($my_perms) {
- set_abconfig($channel['channel_id'],$x['hash'],'system','my_perms',$my_perms);
+ foreach($my_perms as $k => $v) {
+ set_abconfig($channel['channel_id'],$x['hash'],'my_perms',$k,$v);
+ }
}
$closeness = get_pconfig($channel['channel_id'],'system','new_abook_closeness');
@@ -410,7 +417,7 @@ logger('4');
if($y) {
logger("New introduction received for {$channel['channel_name']}");
- $new_perms = get_all_perms($channel['channel_id'],$x['hash']);
+ $new_perms = get_all_perms($channel['channel_id'],$x['hash'],false);
// Send a clone sync packet and a permissions update if permissions have changed
@@ -426,7 +433,7 @@ logger('4');
[
'type' => NOTIFY_INTRO,
'from_xchan' => $x['hash'],
- 'to_xchan' => $channel['channel_hash'],
+ 'to_xchan' => $channel['channel_portable_id'],
'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id']
]
);
@@ -777,7 +784,7 @@ logger('4');
// see if this is a channel clone that's hosted locally - which we treat different from other xchans/connections
- $local = q("select channel_account_id, channel_id from channel where channel_hash = '%s' limit 1",
+ $local = q("select channel_account_id, channel_id from channel where channel_portable_id = '%s' limit 1",
dbesc($xchan_hash)
);
if($local) {
@@ -1130,7 +1137,7 @@ logger('4');
if($recip_arr) {
stringify_array_elms($recip_arr,true);
$recips = implode(',',$recip_arr);
- $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) and channel_removed = 0 ");
+ $r = q("select channel_portable_id as hash from channel where channel_portable_id in ( " . $recips . " ) and channel_removed = 0 ");
}
if(! $r) {
@@ -1304,12 +1311,12 @@ logger('4');
$r = [];
- $c = q("select channel_id, channel_hash from channel where channel_removed = 0");
+ $c = q("select channel_id, channel_portable_id from channel where channel_removed = 0");
if($c) {
foreach($c as $cc) {
if(perm_is_allowed($cc['channel_id'],$msg['sender'],$perm)) {
- $r[] = $cc['channel_hash'];
+ $r[] = $cc['channel_portable_id'];
}
}
}
@@ -1317,7 +1324,7 @@ logger('4');
if($include_sys) {
$sys = get_sys_channel();
if($sys)
- $r[] = $sys['channel_hash'];
+ $r[] = $sys['channel_portable_id'];
}
@@ -1333,7 +1340,7 @@ logger('4');
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'
+ $z = q("select channel_portable_id as hash from channel where channel_address = '%s'
and channel_removed = 0 limit 1",
dbesc($address)
);
@@ -1354,7 +1361,7 @@ logger('4');
$thread_parent = self::find_parent($msg,$act);
if($thread_parent) {
- $z = q("select channel_hash as hash from channel left join item on channel.channel_id = item.uid where ( item.thr_parent = '%s' OR item.parent_mid = '%s' ) ",
+ $z = q("select channel_portable_id as hash from channel left join item on channel.channel_id = item.uid where ( item.thr_parent = '%s' OR item.parent_mid = '%s' ) ",
dbesc($thread_parent),
dbesc($thread_parent)
);
@@ -1428,7 +1435,7 @@ logger('4');
* access checks.
*/
- if($sender === $channel['channel_hash'] && $arr['author_xchan'] === $channel['channel_hash'] && $arr['mid'] === $arr['parent_mid']) {
+ if($sender === $channel['channel_portable_id'] && $arr['author_xchan'] === $channel['channel_portable_id'] && $arr['mid'] === $arr['parent_mid']) {
$DR->update('self delivery ignored');
$result[] = $DR->get();
continue;
@@ -1710,7 +1717,7 @@ logger('4');
$stored = (($item_result && $item_result['item']) ? $item_result['item'] : false);
if((is_array($stored)) && ($stored['id'] != $stored['parent'])
- && ($stored['author_xchan'] === $channel['channel_hash'])) {
+ && ($stored['author_xchan'] === $channel['channel_hash'] || $stored['author_xchan'] === $channel['channel_portable_id'])) {
retain_item($stored['item']['parent']);
}
@@ -1810,9 +1817,9 @@ logger('4');
}
logger('FOF Activity received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG);
- logger('FOF Activity recipient: ' . $channel['channel_hash'], LOGGER_DATA, LOG_DEBUG);
+ logger('FOF Activity recipient: ' . $channel['channel_portable_id'], LOGGER_DATA, LOG_DEBUG);
- $result = self::process_delivery($arr['owner_xchan'],$arr, [ $channel['channel_hash'] ],false,false,true);
+ $result = self::process_delivery($arr['owner_xchan'],$arr, [ $channel['channel_portable_id'] ],false,false,true);
if ($result) {
$ret = array_merge($ret, $result);
}
@@ -2048,7 +2055,7 @@ logger('4');
$DR = new DReport(z_root(),$sender,$d,$arr['mid']);
- $r = q("select * from channel where channel_hash = '%s' limit 1",
+ $r = q("select * from channel where channel_portable_id = '%s' limit 1",
dbesc($d['hash'])
);
@@ -2203,7 +2210,7 @@ logger('4');
$loc = $locations[0];
- $r = q("select * from channel where channel_hash = '%s' limit 1",
+ $r = q("select * from channel where channel_portable_id = '%s' limit 1",
dbesc($sender_hash)
);
@@ -2211,7 +2218,7 @@ logger('4');
return;
if($loc['url'] !== z_root()) {
- $x = q("update channel set channel_moved = '%s' where channel_hash = '%s' limit 1",
+ $x = q("update channel set channel_moved = '%s' where channel_portable_id = '%s' limit 1",
dbesc($loc['url']),
dbesc($sender_hash)
);
@@ -2247,7 +2254,7 @@ logger('4');
static function encode_locations($channel) {
$ret = [];
- $x = self::get_hublocs($channel['channel_hash']);
+ $x = self::get_hublocs($channel['channel_portable_id']);
if($x && count($x)) {
foreach($x as $hub) {
@@ -2558,6 +2565,9 @@ logger('4');
static function zotinfo($arr) {
+ logger('arr: ' . print_r($arr,true));
+
+
$ret = [];
$zhash = ((x($arr,'guid_hash')) ? $arr['guid_hash'] : '');
@@ -2594,13 +2604,13 @@ logger('4');
$r = null;
if(strlen($zhash)) {
- $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash
- where channel_hash = '%s' limit 1",
+ $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash
+ where channel_portable_id = '%s' limit 1",
dbesc($zhash)
);
}
elseif(strlen($zguid) && strlen($zguid_sig)) {
- $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash
+ $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash
where channel_guid = '%s' and channel_guid_sig = '%s' limit 1",
dbesc($zguid),
dbesc($zguid_sig)
@@ -2608,7 +2618,7 @@ logger('4');
}
elseif(strlen($zaddr)) {
if(strpos($zaddr,'[system]') === false) { /* normal address lookup */
- $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash
+ $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash
where ( channel_address = '%s' or xchan_addr = '%s' ) limit 1",
dbesc($zaddr),
dbesc($zaddr)
@@ -2628,10 +2638,10 @@ logger('4');
*
*/
- $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash
+ $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash
where channel_system = 1 order by channel_id limit 1");
if(! $r) {
- $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash
+ $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash
where channel_removed = 0 order by channel_id limit 1");
}
}
@@ -2775,7 +2785,7 @@ logger('4');
if(! $ret['follow_url'])
$ret['follow_url'] = z_root() . '/follow?f=&url=%s';
- $permissions = get_all_perms($e['channel_id'],$ztarget_hash,false);
+ $permissions = get_all_perms($e['channel_id'],$ztarget_hash,false,false);
if($ztarget_hash) {
$permissions['connected'] = false;
diff --git a/Zotlabs/Lib/Zotfinger.php b/Zotlabs/Lib/Zotfinger.php
index 537e440d4..d094fdc8d 100644
--- a/Zotlabs/Lib/Zotfinger.php
+++ b/Zotlabs/Lib/Zotfinger.php
@@ -2,7 +2,7 @@
namespace Zotlabs\Lib;
-use Zotlabs\Web\HTTPSig;
+use Zotlabs\Zot6\HTTPSig;
class Zotfinger {
@@ -12,10 +12,19 @@ class Zotfinger {
return false;
}
- if($channel) {
+ $m = parse_url($resource);
+
+ $data = json_encode([ 'zot_token' => random_string() ]);
+
+ if($channel && $m) {
+
$headers = [
- 'Accept' => 'application/x-zot+json',
- 'X-Zot-Token' => random_string(),
+ 'Accept' => 'application/x-zot+json',
+ 'Content-Type' => 'application/x-zot+json',
+ 'X-Zot-Token' => random_string(),
+ 'Digest' => HTTPSig::generate_digest_header($data),
+ 'Host' => $m['host'],
+ '(request-target)' => 'post ' . get_request_string($resource)
];
$h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel),false);
}
@@ -27,7 +36,9 @@ class Zotfinger {
$redirects = 0;
- $x = z_fetch_url($resource,false,$redirects, [ 'headers' => $h ] );
+ $x = z_post_url($resource,$data,$redirects, [ 'headers' => $h ] );
+
+ logger('fetch: ' . print_r($x,true));
if($x['success']) {
@@ -39,6 +50,8 @@ class Zotfinger {
$result['data'] = json_decode(crypto_unencapsulate($result['data'],get_config('system','prvkey')),true);
}
+ logger('decrypted: ' . print_r($result,true));
+
return $result;
}
diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php
index f1537ed15..12d87885f 100644
--- a/Zotlabs/Module/Channel.php
+++ b/Zotlabs/Module/Channel.php
@@ -6,6 +6,8 @@ namespace Zotlabs\Module;
use App;
use Zotlabs\Web\Controller;
use Zotlabs\Lib\PermissionDescription;
+use Zotlabs\Zot6\HTTPSig;
+use Zotlabs\Lib\Libzot;
require_once('include/items.php');
require_once('include/security.php');
@@ -44,6 +46,48 @@ class Channel extends Controller {
$channel = App::get_channel();
if((local_channel()) && (argc() > 2) && (argv(2) === 'view')) {
+ $which = $channel['channel_address'];
+ $profile = argv(1);
+ }
+
+ $channel = channelx_by_nick($which);
+ if(! $channel) {
+ http_status_exit(404, 'Not found');
+ }
+
+ // handle zot6 channel discovery
+
+ if(Libzot::is_zot_request()) {
+
+ $sigdata = HTTPSig::verify(file_get_contents('php://input'));
+
+ if($sigdata && $sigdata['signer'] && $sigdata['header_valid']) {
+ $data = json_encode(Libzot::zotinfo([ 'address' => $channel['channel_address'], 'target_url' => $sigdata['signer'] ]));
+ $s = q("select site_crypto, hubloc_sitekey from site left join hubloc on hubloc_url = site_url where hubloc_id_url = '%s' and hubloc_network = 'zot6' limit 1",
+ dbesc($sigdata['signer'])
+ );
+
+ if($s) {
+ $data = json_encode(crypto_encapsulate($data,$s[0]['hubloc_sitekey'],Libzot::best_algorithm($s[0]['site_crypto'])));
+ }
+ }
+ else {
+ $data = json_encode(Libzot::zotinfo([ 'address' => $channel['channel_address'] ]));
+ }
+
+ $headers = [
+ 'Content-Type' => 'application/x-zot+json',
+ 'Digest' => HTTPSig::generate_digest_header($data),
+ '(request-target)' => strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']
+ ];
+ $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel));
+ HTTPSig::set_headers($h);
+ echo $data;
+ killme();
+ }
+
+
+ if((local_channel()) && (argc() > 2) && (argv(2) === 'view')) {
$which = $channel['channel_address'];
$profile = argv(1);
}
diff --git a/Zotlabs/Zot6/HTTPSig.php b/Zotlabs/Zot6/HTTPSig.php
index f5c59f9d6..6d52e5033 100644
--- a/Zotlabs/Zot6/HTTPSig.php
+++ b/Zotlabs/Zot6/HTTPSig.php
@@ -48,12 +48,14 @@ class HTTPSig {
$h = new HTTPHeaders($data['header']);
$headers = $h->fetcharr();
$body = $data['body'];
+ $headers['(request-target)'] = $data['request_target'];
}
else {
$headers = [];
$headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI'];
$headers['content-type'] = $_SERVER['CONTENT_TYPE'];
+ $headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
foreach($_SERVER as $k => $v) {
if(strpos($k,'HTTP_') === 0) {
@@ -121,6 +123,17 @@ class HTTPSig {
if(array_key_exists($h,$headers)) {
$signed_data .= $h . ': ' . $headers[$h] . "\n";
}
+ if($h === 'date') {
+ $d = new \DateTime($headers[$h]);
+ $d->setTimeZone(new \DateTimeZone('UTC'));
+ $dplus = datetime_convert('UTC','UTC','now + 1 day');
+ $dminus = datetime_convert('UTC','UTC','now - 1 day');
+ $c = $d->format('Y-m-d H:i:s');
+ if($c > $dplus || $c < $dminus) {
+ logger('bad time: ' . $c);
+ return $result;
+ }
+ }
}
$signed_data = rtrim($signed_data,"\n");
@@ -147,8 +160,15 @@ class HTTPSig {
logger('verified: ' . $x, LOGGER_DEBUG);
- if(! $x)
+ if(! $x) {
+ logger('verify failed for ' . $result['signer'] . ' alg=' . $algorithm . (($key['public_key']) ? '' : ' no key'));
+ $sig_block['signature'] = base64url_encode($sig_block['signature']);
+ logger('affected sigblock: ' . print_r($sig_block,true));
+ logger('signed_data: ' . print_r($signed_data,true));
+ logger('headers: ' . print_r($headers,true));
+ logger('server: ' . print_r($_SERVER,true));
return $result;
+ }
$result['portable_id'] = $key['portable_id'];
$result['header_valid'] = true;
@@ -180,7 +200,9 @@ class HTTPSig {
return [ 'public_key' => $key ];
}
- $key = self::get_webfinger_key($id);
+ if(strpos($id,'#') === false) {
+ $key = self::get_webfinger_key($id);
+ }
if(! $key) {
$key = self::get_activitystreams_key($id);
@@ -215,50 +237,43 @@ class HTTPSig {
*/
function get_activitystreams_key($id) {
- $x = q("select * from xchan left join hubloc on xchan_portable_id = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1",
- dbesc(str_replace('acct:','',$id)),
- dbesc($id)
+
+ // remove fragment
+
+ $url = ((strpos($id,'#')) ? substr($id,0,strpos($id,'#')) : $id);
+
+ $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:','',$url)),
+ dbesc($url)
);
- if(! $x) {
- $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]['hubloc_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ];
+ return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ];
}
- $r = ActivityStreams::fetch_property($id);
+ $r = ActivityStreams::fetch($id);
if($r) {
- 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' => [] ];
+ if(array_key_exists('publicKey',$r) && array_key_exists('publicKeyPem',$r['publicKey']) && array_key_exists('id',$r['publicKey'])) {
+ if($r['publicKey']['id'] === $id || $r['id'] === $id) {
+ $portable_id = ((array_key_exists('owner',$r['publicKey'])) ? $r['publicKey']['owner'] : EMPTY_STR);
+ return [ 'public_key' => self::convertKey($r['publicKey']['publicKeyPem']), 'portable_id' => $portable_id, 'hubloc' => [] ];
}
}
}
-
- return false;
+ return false;
}
function get_webfinger_key($id) {
- $x = q("select * from xchan left join hubloc on xchan_portable_id = 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 = 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]['hubloc_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ];
+ return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ];
}
$wf = Webfinger::exec($id);
@@ -280,26 +295,18 @@ class HTTPSig {
}
}
- return (($key['public_key']) ? $key : false);
+ return (($key['public_key']) ? $key : false);
}
function get_zotfinger_key($id) {
- $x = q("select * from xchan left join hubloc on xchan_portable_id = 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 = 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]['hubloc_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ];
+ return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ];
}
$wf = Webfinger::exec($id);
@@ -321,14 +328,9 @@ class HTTPSig {
if($i['success']) {
$key['portable_id'] = $i['hash'];
- $x = q("select * from xchan left join hubloc on xchan_portable_id = hubloc_hash where hubloc_id_url = '%s' limit 1",
+ $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) {
- $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];
}
@@ -433,6 +435,8 @@ class HTTPSig {
$headers = '';
$fields = '';
+ logger('signing: ' . print_r($head,true), LOGGER_DATA);
+
if($head) {
foreach($head as $k => $v) {
$headers .= strtolower($k) . ': ' . trim($v) . "\n";
diff --git a/Zotlabs/Zot6/Receiver.php b/Zotlabs/Zot6/Receiver.php
index 4f26e2b0c..66559c9a5 100644
--- a/Zotlabs/Zot6/Receiver.php
+++ b/Zotlabs/Zot6/Receiver.php
@@ -4,7 +4,7 @@ namespace Zotlabs\Zot6;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\Libzot;
-use Zotlabs\Web\HTTPSig;
+
class Receiver {
diff --git a/Zotlabs/Zot6/Zot6Handler.php b/Zotlabs/Zot6/Zot6Handler.php
index 1fae81ae6..e320e7825 100644
--- a/Zotlabs/Zot6/Zot6Handler.php
+++ b/Zotlabs/Zot6/Zot6Handler.php
@@ -72,8 +72,8 @@ class Zot6Handler implements IHandler {
foreach ($recipients as $recip) {
$r = q("select channel.*,xchan.* from channel
- left join xchan on channel_portable_id = xchan_portable_id
- where xchan_portable_id ='%s' limit 1",
+ left join xchan on channel_portable_id = xchan_hash
+ where xchan_hash ='%s' limit 1",
dbesc($recip)
);
@@ -141,7 +141,7 @@ class Zot6Handler implements IHandler {
$arr = $data['recipients'][0];
- $c = q("select * from channel left join xchan on channel_portable_id = xchan_portable_id where channel_portable_id = '%s' limit 1",
+ $c = q("select * from channel left join xchan on channel_portable_id = xchan_hash where channel_portable_id = '%s' limit 1",
dbesc($arr['portable_id'])
);
if (! $c) {
@@ -197,7 +197,7 @@ class Zot6Handler implements IHandler {
return $ret;
}
- $r = q("select * from xchan where xchan_hash = '%s' or xchan_portable_id = '%s' limit 1",
+ $r = q("select * from xchan where xchan_hash = '%s' limit 1",
dbesc($sender)
);
@@ -231,15 +231,14 @@ class Zot6Handler implements IHandler {
// basically this means "unfriend"
foreach ($recipients as $recip) {
$r = q("select channel.*,xchan.* from channel
- left join xchan on channel_portable_id = xchan_portable_id
+ left join xchan on channel_portable_id = xchan_hash
where channel_portable_id = '%s' limit 1",
dbesc($recip)
);
if ($r) {
- $r = q("select abook_id from abook where uid = %d and (abook_xchan = '%s' or abook_xchan = '%s') limit 1",
+ $r = q("select abook_id from abook where uid = %d and abook_xchan = '%s' limit 1",
intval($r[0]['channel_id']),
- dbesc($sender),
- dbesc($r[0]['xchan_portable_id'])
+ dbesc($sender)
);
if ($r) {
contact_remove($r[0]['channel_id'],$r[0]['abook_id']);
diff --git a/include/channel.php b/include/channel.php
index 030efffb9..1e5570f6b 100644
--- a/include/channel.php
+++ b/include/channel.php
@@ -10,7 +10,7 @@ use Zotlabs\Access\Permissions;
use Zotlabs\Daemon\Master;
use Zotlabs\Lib\System;
use Zotlabs\Render\Comanche;
-use Zotlabs\Lib\Zotlib;
+use Zotlabs\Lib\Libzot;
require_once('include/zot.php');
require_once('include/crypto.php');
@@ -233,7 +233,7 @@ function create_identity($arr) {
$sig = base64url_encode(rsa_sign($guid,$key['prvkey']));
$hash = make_xchan_hash($guid,$sig);
- $zhash = Zotlib::make_xchan_hash($guid,$key['pubkey']);
+ $zhash = Libzot::make_xchan_hash($guid,$key['pubkey']);
// Force a few things on the short term until we can provide a theme or app with choice
@@ -353,10 +353,12 @@ function create_identity($arr) {
'hubloc_guid' => $guid,
'hubloc_guid_sig' => 'sha256.' . $sig,
'hubloc_hash' => $zhash,
+ 'hubloc_id_url' => channel_url($ret['channel']),
'hubloc_addr' => channel_reddress($ret['channel']),
'hubloc_primary' => intval($primary),
'hubloc_url' => z_root(),
'hubloc_url_sig' => 'sha256.' . base64url_encode(rsa_sign(z_root(),$ret['channel']['channel_prvkey'])),
+ 'hubloc_site_id' => Libzot::make_xchan_hash(z_root(),get_config('system','pubkey')),
'hubloc_host' => App::get_hostname(),
'hubloc_callback' => z_root() . '/zot',
'hubloc_sitekey' => get_config('system','pubkey'),
@@ -373,7 +375,6 @@ function create_identity($arr) {
$r = xchan_store_lowlevel(
[
'xchan_hash' => $hash,
- 'xchan_portable_id' => $zhash,
'xchan_guid' => $guid,
'xchan_guid_sig' => $sig,
'xchan_pubkey' => $key['pubkey'],
@@ -393,6 +394,30 @@ function create_identity($arr) {
]
);
+ $r = xchan_store_lowlevel(
+ [
+ 'xchan_hash' => $zhash,
+ 'xchan_guid' => $guid,
+ 'xchan_guid_sig' => 'sha256.' . $sig,
+ 'xchan_pubkey' => $key['pubkey'],
+ 'xchan_photo_mimetype' => (($photo_type) ? $photo_type : 'image/png'),
+ 'xchan_photo_l' => z_root() . "/photo/profile/l/{$newuid}",
+ 'xchan_photo_m' => z_root() . "/photo/profile/m/{$newuid}",
+ 'xchan_photo_s' => z_root() . "/photo/profile/s/{$newuid}",
+ 'xchan_addr' => channel_reddress($ret['channel']),
+ 'xchan_url' => z_root() . '/channel/' . $ret['channel']['channel_address'],
+ 'xchan_follow' => z_root() . '/follow?f=&url=%s',
+ 'xchan_connurl' => z_root() . '/poco/' . $ret['channel']['channel_address'],
+ 'xchan_name' => $ret['channel']['channel_name'],
+ 'xchan_network' => 'zot6',
+ 'xchan_photo_date' => datetime_convert(),
+ 'xchan_name_date' => datetime_convert(),
+ 'xchan_system' => $system
+ ]
+ );
+
+
+
// Not checking return value.
// It's ok for this to fail if it's an imported channel, and therefore the hash is a duplicate
diff --git a/include/xchan.php b/include/xchan.php
index 5516d046f..4cbfb42c5 100644
--- a/include/xchan.php
+++ b/include/xchan.php
@@ -12,7 +12,6 @@ function xchan_store_lowlevel($arr) {
$store = [
'xchan_hash' => ((array_key_exists('xchan_hash',$arr)) ? $arr['xchan_hash'] : ''),
- 'xchan_portable_id' => ((array_key_exists('xchan_portable_id',$arr)) ? $arr['xchan_portable_id'] : ''),
'xchan_guid' => ((array_key_exists('xchan_guid',$arr)) ? $arr['xchan_guid'] : ''),
'xchan_guid_sig' => ((array_key_exists('xchan_guid_sig',$arr)) ? $arr['xchan_guid_sig'] : ''),
'xchan_pubkey' => ((array_key_exists('xchan_pubkey',$arr)) ? $arr['xchan_pubkey'] : ''),