diff options
-rw-r--r-- | Zotlabs/Lib/Activity.php | 2 | ||||
-rw-r--r-- | Zotlabs/Lib/JSalmon.php | 2 | ||||
-rw-r--r-- | Zotlabs/Lib/Libzot.php | 2 | ||||
-rw-r--r-- | Zotlabs/Lib/ZotURL.php | 2 | ||||
-rw-r--r-- | Zotlabs/Lib/Zotfinger.php | 2 | ||||
-rw-r--r-- | Zotlabs/Module/Cdav.php | 5 | ||||
-rw-r--r-- | Zotlabs/Module/Channel.php | 2 | ||||
-rw-r--r-- | Zotlabs/Module/Dav.php | 9 | ||||
-rw-r--r-- | Zotlabs/Module/Getfile.php | 6 | ||||
-rw-r--r-- | Zotlabs/Module/Id.php | 2 | ||||
-rw-r--r-- | Zotlabs/Module/Item.php | 2 | ||||
-rw-r--r-- | Zotlabs/Module/Magic.php | 7 | ||||
-rw-r--r-- | Zotlabs/Module/Owa.php | 6 | ||||
-rw-r--r-- | Zotlabs/Module/Zfinger.php | 8 | ||||
-rw-r--r-- | Zotlabs/Module/Zot_probe.php | 2 | ||||
-rw-r--r-- | Zotlabs/Web/HTTPSig.php | 362 | ||||
-rw-r--r-- | Zotlabs/Zot/Finger.php | 7 | ||||
-rw-r--r-- | Zotlabs/Zot6/Finger.php | 5 | ||||
-rw-r--r-- | include/import.php | 6 | ||||
-rw-r--r-- | include/xchan.php | 2 | ||||
-rw-r--r-- | include/zot.php | 5 | ||||
-rw-r--r-- | tests/unit/Web/HttpSigTest.php | 27 |
22 files changed, 296 insertions, 177 deletions
diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index 701e19d37..f974d215b 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -3,7 +3,7 @@ namespace Zotlabs\Lib; use Zotlabs\Daemon\Master; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; require_once('include/event.php'); diff --git a/Zotlabs/Lib/JSalmon.php b/Zotlabs/Lib/JSalmon.php index f35bf6235..bed748432 100644 --- a/Zotlabs/Lib/JSalmon.php +++ b/Zotlabs/Lib/JSalmon.php @@ -2,7 +2,7 @@ namespace Zotlabs\Lib; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; class JSalmon { diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index 0ad8afc94..2a13744a3 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -2,7 +2,7 @@ namespace Zotlabs\Lib; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; use Zotlabs\Access\Permissions; use Zotlabs\Access\PermissionLimits; use Zotlabs\Daemon\Master; diff --git a/Zotlabs/Lib/ZotURL.php b/Zotlabs/Lib/ZotURL.php index bc14c516a..98d1febe5 100644 --- a/Zotlabs/Lib/ZotURL.php +++ b/Zotlabs/Lib/ZotURL.php @@ -2,7 +2,7 @@ namespace Zotlabs\Lib; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; class ZotURL { diff --git a/Zotlabs/Lib/Zotfinger.php b/Zotlabs/Lib/Zotfinger.php index d094fdc8d..2d2e6796b 100644 --- a/Zotlabs/Lib/Zotfinger.php +++ b/Zotlabs/Lib/Zotfinger.php @@ -2,7 +2,7 @@ namespace Zotlabs\Lib; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; class Zotfinger { diff --git a/Zotlabs/Module/Cdav.php b/Zotlabs/Module/Cdav.php index de639e281..b199019c1 100644 --- a/Zotlabs/Module/Cdav.php +++ b/Zotlabs/Module/Cdav.php @@ -4,6 +4,7 @@ namespace Zotlabs\Module; use App; use Zotlabs\Lib\Apps; use Zotlabs\Web\Controller; +use Zotlabs\Web\HTTPSig; require_once('include/event.php'); @@ -41,7 +42,7 @@ class Cdav extends Controller { continue; } - $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER[$head]); + $sigblock = HTTPSig::parse_sigheader($_SERVER[$head]); if($sigblock) { $keyId = str_replace('acct:','',$sigblock['keyId']); if($keyId) { @@ -64,7 +65,7 @@ class Cdav extends Controller { continue; if($record) { - $verified = \Zotlabs\Web\HTTPSig::verify('',$record['channel']['channel_pubkey']); + $verified = HTTPSig::verify('',$record['channel']['channel_pubkey']); if(! ($verified && $verified['header_signed'] && $verified['header_valid'])) { $record = null; } diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php index 144c2472a..afd82ed2f 100644 --- a/Zotlabs/Module/Channel.php +++ b/Zotlabs/Module/Channel.php @@ -6,7 +6,7 @@ namespace Zotlabs\Module; use App; use Zotlabs\Web\Controller; use Zotlabs\Lib\PermissionDescription; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; use Zotlabs\Lib\Libzot; require_once('include/items.php'); diff --git a/Zotlabs/Module/Dav.php b/Zotlabs/Module/Dav.php index 9f64e2fea..866520461 100644 --- a/Zotlabs/Module/Dav.php +++ b/Zotlabs/Module/Dav.php @@ -8,8 +8,9 @@ namespace Zotlabs\Module; -use \Sabre\DAV as SDAV; -use \Zotlabs\Storage; +use Sabre\DAV as SDAV; +use Zotlabs\Storage; +use Zotlabs\Web\HTTPSig; require_once('include/attach.php'); require_once('include/auth.php'); @@ -46,7 +47,7 @@ class Dav extends \Zotlabs\Web\Controller { continue; } - $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER[$head]); + $sigblock = HTTPSig::parse_sigheader($_SERVER[$head]); if($sigblock) { $keyId = str_replace('acct:','',$sigblock['keyId']); if($keyId) { @@ -69,7 +70,7 @@ class Dav extends \Zotlabs\Web\Controller { continue; if($record) { - $verified = \Zotlabs\Web\HTTPSig::verify('',$record['channel']['channel_pubkey']); + $verified = HTTPSig::verify('',$record['channel']['channel_pubkey']); if(! ($verified && $verified['header_signed'] && $verified['header_valid'])) { $record = null; } diff --git a/Zotlabs/Module/Getfile.php b/Zotlabs/Module/Getfile.php index 583cf38f0..6d31d23fd 100644 --- a/Zotlabs/Module/Getfile.php +++ b/Zotlabs/Module/Getfile.php @@ -1,6 +1,8 @@ <?php namespace Zotlabs\Module; +use Zotlabs\Web\HTTPSig; + /** * module: getfile * @@ -46,7 +48,7 @@ class Getfile extends \Zotlabs\Web\Controller { continue; } - $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER[$head]); + $sigblock = HTTPSig::parse_sigheader($_SERVER[$head]); if($sigblock) { $keyId = $sigblock['keyId']; @@ -57,7 +59,7 @@ class Getfile extends \Zotlabs\Web\Controller { ); if($r) { $hubloc = $r[0]; - $verified = \Zotlabs\Web\HTTPSig::verify('',$hubloc['xchan_pubkey']); + $verified = HTTPSig::verify('',$hubloc['xchan_pubkey']); if($verified && $verified['header_signed'] && $verified['header_valid'] && $hash == $hubloc['hubloc_hash']) { $header_verified = true; } diff --git a/Zotlabs/Module/Id.php b/Zotlabs/Module/Id.php index 15abfa2a3..e08568d00 100644 --- a/Zotlabs/Module/Id.php +++ b/Zotlabs/Module/Id.php @@ -12,7 +12,7 @@ namespace Zotlabs\Module; use Zotlabs\Lib\Activity; use Zotlabs\Lib\ActivityStreams; use Zotlabs\Lib\LDSignatures; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; use Zotlabs\Web\Controller; use Zotlabs\Lib\Libzot; use Zotlabs\Lib\ThreadListener; diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index 3eac2b6a0..cb78e4047 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -9,7 +9,7 @@ use Zotlabs\Daemon\Master; use Zotlabs\Lib\Activity; use Zotlabs\Lib\ActivityStreams; use Zotlabs\Lib\LDSignatures; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; use Zotlabs\Lib\Libzot; use Zotlabs\Lib\ThreadListener; use App; diff --git a/Zotlabs/Module/Magic.php b/Zotlabs/Module/Magic.php index e8e960574..6ac656a04 100644 --- a/Zotlabs/Module/Magic.php +++ b/Zotlabs/Module/Magic.php @@ -1,6 +1,8 @@ <?php namespace Zotlabs\Module; +use Zotlabs\Web\HTTPSig; + @require_once('include/zot.php'); @@ -152,10 +154,9 @@ class Magic extends \Zotlabs\Web\Controller { $headers['Accept'] = 'application/x-zot+json' ; $headers['X-Open-Web-Auth'] = random_string(); $headers['Host'] = $parsed['host']; - $headers['Digest'] = 'SHA-256=' . \Zotlabs\Web\HTTPSig::generate_digest($data,false); + $headers['Digest'] = HTTPSig::generate_digest_header($data); - $headers = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], - 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,true,'sha512'); + $headers = HTTPSig::create_sig($headers,$channel['channel_prvkey'], 'acct:' . channel_reddress($channel),true,'sha512'); $x = z_post_url($basepath . '/owa',$data,$redirects,[ 'headers' => $headers ]); if($x['success']) { diff --git a/Zotlabs/Module/Owa.php b/Zotlabs/Module/Owa.php index cf116a96c..89f83bf8f 100644 --- a/Zotlabs/Module/Owa.php +++ b/Zotlabs/Module/Owa.php @@ -2,6 +2,8 @@ namespace Zotlabs\Module; +use Zotlabs\Web\HTTPSig; + /** * OpenWebAuth verifier and token generator * See https://macgirvin.com/wiki/mike/OpenWebAuth/Home @@ -25,7 +27,7 @@ class Owa extends \Zotlabs\Web\Controller { continue; } - $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER[$head]); + $sigblock = HTTPSig::parse_sigheader($_SERVER[$head]); if($sigblock) { $keyId = $sigblock['keyId']; @@ -65,7 +67,7 @@ class Owa extends \Zotlabs\Web\Controller { if ($r) { foreach($r as $hubloc) { - $verified = \Zotlabs\Web\HTTPSig::verify(file_get_contents('php://input'),$hubloc['xchan_pubkey']); + $verified = HTTPSig::verify(file_get_contents('php://input'),$hubloc['xchan_pubkey']); if($verified && $verified['header_signed'] && $verified['header_valid']) { logger('OWA header: ' . print_r($verified,true),LOGGER_DATA); logger('OWA success: ' . $hubloc['hubloc_addr'],LOGGER_DATA); diff --git a/Zotlabs/Module/Zfinger.php b/Zotlabs/Module/Zfinger.php index 6ed001df5..3a20144a5 100644 --- a/Zotlabs/Module/Zfinger.php +++ b/Zotlabs/Module/Zfinger.php @@ -1,6 +1,7 @@ <?php namespace Zotlabs\Module; +use Zotlabs\Web\HTTPSig; class Zfinger extends \Zotlabs\Web\Controller { @@ -23,10 +24,9 @@ class Zfinger extends \Zotlabs\Web\Controller { $ret = json_encode($x); if($chan) { - $hash = \Zotlabs\Web\HTTPSig::generate_digest($ret,false); - $headers['Digest'] = 'SHA-256=' . $hash; - \Zotlabs\Web\HTTPSig::create_sig('',$headers,$chan['channel_prvkey'], - 'acct:' . $chan['channel_address'] . '@' . \App::get_hostname(),true); + $headers['Digest'] = HTTPSig::generate_digest_header($ret); + $h = HTTPSig::create_sig($headers,$chan['channel_prvkey'],'acct:' . channel_reddress($chan)); + HTTPSig::set_headers($h); } else { foreach($headers as $k => $v) { diff --git a/Zotlabs/Module/Zot_probe.php b/Zotlabs/Module/Zot_probe.php index d0c7e688f..648ed2175 100644 --- a/Zotlabs/Module/Zot_probe.php +++ b/Zotlabs/Module/Zot_probe.php @@ -3,7 +3,7 @@ namespace Zotlabs\Module; use Zotlabs\Lib\Zotfinger; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; class Zot_probe extends \Zotlabs\Web\Controller { diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php index fe0b9428f..3d050fd9b 100644 --- a/Zotlabs/Web/HTTPSig.php +++ b/Zotlabs/Web/HTTPSig.php @@ -2,11 +2,17 @@ namespace Zotlabs\Web; +use Zotlabs\Lib\ActivityStreams; +use Zotlabs\Lib\Webfinger; +use Zotlabs\Web\HTTPHeaders; +use Zotlabs\Lib\Libzot; + /** - * @brief Implements HTTP Signatures per draft-cavage-http-signatures-07. + * @brief Implements HTTP Signatures per draft-cavage-http-signatures-10. * - * @see https://tools.ietf.org/html/draft-cavage-http-signatures-07 + * @see https://tools.ietf.org/html/draft-cavage-http-signatures-10 */ + class HTTPSig { /** @@ -15,41 +21,32 @@ class HTTPSig { * @see https://tools.ietf.org/html/rfc5843 * * @param string $body The value to create the digest for - * @param boolean $set (optional, default true) - * If set send a Digest HTTP header - * @return string The generated digest of $body + * @param string $alg hash algorithm (one of 'sha256','sha512') + * @return string The generated digest header string for $body */ - static function generate_digest($body, $set = true) { - $digest = base64_encode(hash('sha256', $body, true)); - if($set) { - header('Digest: SHA-256=' . $digest); + static function generate_digest_header($body,$alg = 'sha256') { + + $digest = base64_encode(hash($alg, $body, true)); + switch($alg) { + case 'sha512': + return 'SHA-512=' . $digest; + case 'sha256': + default: + return 'SHA-256=' . $digest; + break; } - return $digest; } - // See draft-cavage-http-signatures-08 - - static function verify($data,$key = '') { - - $body = $data; - $headers = null; - $spoofable = false; - - $result = [ - 'signer' => '', - 'header_signed' => false, - 'header_valid' => false, - 'content_signed' => false, - 'content_valid' => false - ]; + static function find_headers($data,&$body) { // decide if $data arrived via controller submission or curl + if(is_array($data) && $data['header']) { if(! $data['success']) - return $result; + return []; - $h = new \Zotlabs\Web\HTTPHeaders($data['header']); + $h = new HTTPHeaders($data['header']); $headers = $h->fetcharr(); $body = $data['body']; $headers['(request-target)'] = $data['request_target']; @@ -57,9 +54,7 @@ class HTTPSig { else { $headers = []; - $headers['(request-target)'] = - strtolower($_SERVER['REQUEST_METHOD']) . ' ' . - $_SERVER['REQUEST_URI']; + $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; $headers['content-type'] = $_SERVER['CONTENT_TYPE']; $headers['content-length'] = $_SERVER['CONTENT_LENGTH']; @@ -71,9 +66,35 @@ class HTTPSig { } } - // logger('SERVER: ' . print_r($_SERVER,true), LOGGER_ALL); + //logger('SERVER: ' . print_r($_SERVER,true), LOGGER_ALL); + + //logger('headers: ' . print_r($headers,true), LOGGER_ALL); + + return $headers; + } + + + // See draft-cavage-http-signatures-10 + + static function verify($data,$key = '') { + + $body = $data; + $headers = null; + + $result = [ + 'signer' => '', + 'portable_id' => '', + 'header_signed' => false, + 'header_valid' => false, + 'content_signed' => false, + 'content_valid' => false + ]; + + + $headers = self::find_headers($data,$body); - // logger('headers: ' . print_r($headers,true), LOGGER_ALL); + if(! $headers) + return $result; $sig_block = null; @@ -85,7 +106,7 @@ class HTTPSig { } if(! $sig_block) { - logger('no signature provided.'); + logger('no signature provided.', LOGGER_DEBUG); return $result; } @@ -103,9 +124,6 @@ class HTTPSig { if(array_key_exists($h,$headers)) { $signed_data .= $h . ': ' . $headers[$h] . "\n"; } - if(strpos($h,'.')) { - $spoofable = true; - } if($h === 'date') { $d = new \DateTime($headers[$h]); $d->setTimeZone(new \DateTimeZone('UTC')); @@ -128,63 +146,89 @@ class HTTPSig { $algorithm = 'sha512'; } - if($key && function_exists($key)) { - $result['signer'] = $sig_block['keyId']; - $key = $key($sig_block['keyId']); - } + if(! array_key_exists('keyId',$sig_block)) + return $result; - if(! $key) { - $result['signer'] = $sig_block['keyId']; - $key = self::get_activitypub_key($sig_block['keyId']); - } + $result['signer'] = $sig_block['keyId']; - if(! $key) + $key = self::get_key($key,$result['signer']); + + if(! ($key && $key['public_key'])) { return $result; + } - $x = rsa_verify($signed_data,$sig_block['signature'],$key,$algorithm); + $x = rsa_verify($signed_data,$sig_block['signature'],$key['public_key'],$algorithm); 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'] = base64_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; + } - if(! $spoofable) - $result['header_valid'] = true; + $result['portable_id'] = $key['portable_id']; + $result['header_valid'] = true; if(in_array('digest',$signed_headers)) { $result['content_signed'] = true; - $digest = explode('=', $headers['digest']); + $digest = explode('=', $headers['digest'], 2); if($digest[0] === 'SHA-256') $hashalg = 'sha256'; if($digest[0] === 'SHA-512') $hashalg = 'sha512'; - // The explode operation will have stripped the '=' padding, so compare against unpadded base64 - if(rtrim(base64_encode(hash($hashalg,$body,true)),'=') === $digest[1]) { + if(base64_encode(hash($hashalg,$body,true)) === $digest[1]) { $result['content_valid'] = true; } + + logger('Content_Valid: ' . (($result['content_valid']) ? 'true' : 'false')); } + return $result; + } - if(in_array('x-zot-digest',$signed_headers)) { - $result['content_signed'] = true; - $digest = explode('=', $headers['x-zot-digest']); - if($digest[0] === 'SHA-256') - $hashalg = 'sha256'; - if($digest[0] === 'SHA-512') - $hashalg = 'sha512'; + static function get_key($key,$id) { - // The explode operation will have stripped the '=' padding, so compare against unpadded base64 - if(rtrim(base64_encode(hash($hashalg,$_POST['data'],true)),'=') === $digest[1]) { - $result['content_valid'] = true; + if($key) { + if(function_exists($key)) { + return $key($id); } + return [ 'public_key' => $key ]; } - logger('Content_Valid: ' . (($result['content_valid']) ? 'true' : 'false')); + if(strpos($id,'#') === false) { + $key = self::get_webfinger_key($id); + } + + if(! $key) { + $key = self::get_activitystreams_key($id); + } + + return $key; + + } + + + function convertKey($key) { + + if(strstr($key,'RSA ')) { + return rsatopem($key); + } + elseif(substr($key,0,5) === 'data:') { + return convert_salmon_key($key); + } + else { + return $key; + } - return $result; } + /** * @brief * @@ -192,57 +236,131 @@ class HTTPSig { * @return boolean|string * false if no pub key found, otherwise return the pub key */ - function get_activitypub_key($id) { - if(strpos($id,'acct:') === 0) { - $x = q("select xchan_pubkey from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' limit 1", - dbesc(str_replace('acct:','',$id)) - ); + function get_activitystreams_key($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[0]['xchan_pubkey']) { + return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; } - else { - $x = q("select xchan_pubkey from xchan where xchan_hash = '%s' and xchan_network = 'activitypub' ", - dbesc($id) - ); + + $r = ActivityStreams::fetch($id); + + if($r) { + 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; + } + + + function get_webfinger_key($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:','',$id)), + dbesc($id) + ); if($x && $x[0]['xchan_pubkey']) { - return ($x[0]['xchan_pubkey']); + return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; } - if(function_exists('as_fetch')) - $r = as_fetch($id); + $wf = Webfinger::exec($id); + $key = [ 'portable_id' => '', 'public_key' => '', 'hubloc' => [] ]; - if($r) { - $j = json_decode($r,true); + if($wf) { + if(array_key_exists('properties',$wf) && array_key_exists('https://w3id.org/security/v1#publicKeyPem',$wf['properties'])) { + $key['public_key'] = self::convertKey($wf['properties']['https://w3id.org/security/v1#publicKeyPem']); + } + if(array_key_exists('links', $wf) && is_array($wf['links'])) { + foreach($wf['links'] as $l) { + if(! (is_array($l) && array_key_exists('rel',$l))) { + continue; + } + if($l['rel'] === 'magic-public-key' && array_key_exists('href',$l) && $key['public_key'] === EMPTY_STR) { + $key['public_key'] = self::convertKey($l['href']); + } + } + } + } + + return (($key['public_key']) ? $key : false); + } + + + function get_zotfinger_key($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:','',$id)), + dbesc($id) + ); + if($x && $x[0]['xchan_pubkey']) { + return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; + } - 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; + $wf = Webfinger::exec($id); + $key = [ 'portable_id' => '', 'public_key' => '', 'hubloc' => [] ]; - return($j['publicKey']['publicKeyPem']); + if($wf) { + if(array_key_exists('properties',$wf) && array_key_exists('https://w3id.org/security/v1#publicKeyPem',$wf['properties'])) { + $key['public_key'] = self::convertKey($wf['properties']['https://w3id.org/security/v1#publicKeyPem']); + } + if(array_key_exists('links', $wf) && is_array($wf['links'])) { + foreach($wf['links'] as $l) { + if(! (is_array($l) && array_key_exists('rel',$l))) { + continue; + } + if($l['rel'] === 'http://purl.org/zot/protocol/6.0' && array_key_exists('href',$l) && $l['href'] !== EMPTY_STR) { + $z = \Zotlabs\Lib\Zotfinger::exec($l['href']); + if($z) { + $i = 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]; + } + } + } + } + if($l['rel'] === 'magic-public-key' && array_key_exists('href',$l) && $key['public_key'] === EMPTY_STR) { + $key['public_key'] = self::convertKey($l['href']); + } + } } } - return false; + return (($key['public_key']) ? $key : false); } + /** * @brief * - * @param string $request * @param array $head * @param string $prvkey - * @param string $keyid (optional, default 'Key') - * @param boolean $send_headers (optional, default false) - * If set send a HTTP header + * @param string $keyid (optional, default '') * @param boolean $auth (optional, default false) * @param string $alg (optional, default 'sha256') - * @param string $crypt_key (optional, default null) - * @param string $crypt_algo (optional, default 'aes256ctr') + * @param array $encryption [ 'key', 'algorithm' ] or false * @return array */ - static function create_sig($request, $head, $prvkey, $keyid = 'Key', $send_headers = false, $auth = false, - $alg = 'sha256', $crypt_key = null, $crypt_algo = 'aes256ctr') { + static function create_sig($head, $prvkey, $keyid = EMPTY_STR, $auth = false, $alg = 'sha256', $encryption = false ) { $return_headers = []; @@ -253,14 +371,15 @@ class HTTPSig { $algorithm = 'rsa-sha512'; } - $x = self::sign($request,$head,$prvkey,$alg); + $x = self::sign($head,$prvkey,$alg); - $headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm - . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; + $headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; - if($crypt_key) { - $x = crypto_encapsulate($headerval,$crypt_key,$crypt_algo); - $headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"'; + if($encryption) { + $x = crypto_encapsulate($headerval,$encryption['key'],$encryption['algorithm']); + if(is_array($x)) { + $headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"'; + } } if($auth) { @@ -272,43 +391,52 @@ class HTTPSig { if($head) { foreach($head as $k => $v) { - if($send_headers) { - header($k . ': ' . $v); - } - else { - $return_headers[] = $k . ': ' . $v; + // strip the request-target virtual header from the output headers + if($k === '(request-target)') { + continue; } + $return_headers[] = $k . ': ' . $v; } } - if($send_headers) { - header($sighead); - } - else { - $return_headers[] = $sighead; - } + $return_headers[] = $sighead; return $return_headers; } /** + * @brief set headers + * + * @param array $headers + * @return void + */ + + + static function set_headers($headers) { + if($headers && is_array($headers)) { + foreach($headers as $h) { + header($h); + } + } + } + + + /** * @brief * - * @param string $request * @param array $head * @param string $prvkey * @param string $alg (optional) default 'sha256' * @return array */ - static function sign($request, $head, $prvkey, $alg = 'sha256') { + + static function sign($head, $prvkey, $alg = 'sha256') { $ret = []; $headers = ''; $fields = ''; - if($request) { - $headers = '(request-target)' . ': ' . trim($request) . "\n"; - $fields = '(request-target)'; - } + + logger('signing: ' . print_r($head,true), LOGGER_DATA); if($head) { foreach($head as $k => $v) { @@ -340,11 +468,8 @@ class HTTPSig { * - \e array \b headers * - \e string \b signature */ - static function parse_sigheader($header) { - if(is_array($header)) { - btlogger('is_array: ' . print_r($header,true)); - } + static function parse_sigheader($header) { $ret = []; $matches = []; @@ -381,6 +506,7 @@ class HTTPSig { * - \e string \b alg * - \e string \b data */ + static function decrypt_sigheader($header, $prvkey = null) { $iv = $key = $alg = $data = null; diff --git a/Zotlabs/Zot/Finger.php b/Zotlabs/Zot/Finger.php index cb38c7f2b..778b701cd 100644 --- a/Zotlabs/Zot/Finger.php +++ b/Zotlabs/Zot/Finger.php @@ -2,6 +2,8 @@ namespace Zotlabs\Zot; +use Zotlabs\Web\HTTPSig; + /** * @brief Finger * @@ -95,8 +97,7 @@ class Finger { $headers['X-Zot-Nonce'] = random_string(); $headers['Host'] = $parsed_host; - $xhead = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], - 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false); + $xhead = HTTPSig::create_sig($headers,$channel['channel_prvkey'],'acct:' . channel_reddress($channel)); $retries = 0; @@ -129,7 +130,7 @@ class Finger { $x = json_decode($result['body'], true); - $verify = \Zotlabs\Web\HTTPSig::verify($result,(($x) ? $x['key'] : '')); + $verify = HTTPSig::verify($result,(($x) ? $x['key'] : '')); if($x && (! $verify['header_valid'])) { $signed_token = ((is_array($x) && array_key_exists('signed_token', $x)) ? $x['signed_token'] : null); diff --git a/Zotlabs/Zot6/Finger.php b/Zotlabs/Zot6/Finger.php index f1fe41352..22ce4685d 100644 --- a/Zotlabs/Zot6/Finger.php +++ b/Zotlabs/Zot6/Finger.php @@ -88,8 +88,7 @@ class Finger { $headers = []; $headers['X-Zot-Channel'] = $channel['channel_address'] . '@' . \App::get_hostname(); $headers['X-Zot-Nonce'] = random_string(); - $xhead = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], - 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false); + $xhead = HTTPSig::create_sig($headers,$channel['channel_prvkey'],'acct:' . channel_reddress($channel)); $retries = 0; @@ -122,7 +121,7 @@ class Finger { $x = json_decode($result['body'], true); - $verify = \Zotlabs\Web\HTTPSig::verify($result,(($x) ? $x['key'] : '')); + $verify = HTTPSig::verify($result,(($x) ? $x['key'] : '')); if($x && (! $verify['header_valid'])) { $signed_token = ((is_array($x) && array_key_exists('signed_token', $x)) ? $x['signed_token'] : null); diff --git a/include/import.php b/include/import.php index caf25f5d2..1d3b7c035 100644 --- a/include/import.php +++ b/include/import.php @@ -2,6 +2,8 @@ use Zotlabs\Lib\IConfig; +use Zotlabs\Web\HTTPSig; + require_once('include/menu.php'); require_once('include/perm_upgrade.php'); @@ -1329,7 +1331,7 @@ function sync_files($channel, $files) { $headers = []; $headers['Accept'] = 'application/x-zot+json' ; $headers['Sigtoken'] = random_string(); - $headers = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,true,'sha512'); + $headers = HTTPSig::create_sig($headers,$channel['channel_prvkey'], 'acct:' . channel_reddress($channel),true,'sha512'); $x = z_post_url($fetch_url,$parr,$redirects,[ 'filep' => $fp, 'headers' => $headers]); fclose($fp); @@ -1415,7 +1417,7 @@ function sync_files($channel, $files) { $headers = []; $headers['Accept'] = 'application/x-zot+json' ; $headers['Sigtoken'] = random_string(); - $headers = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,true,'sha512'); + $headers = HTTPSig::create_sig($headers,$channel['channel_prvkey'],'acct:' . channel_reddress($channel),true,'sha512'); $x = z_post_url($fetch_url,$parr,$redirects,[ 'filep' => $fp, 'headers' => $headers]); fclose($fp); diff --git a/include/xchan.php b/include/xchan.php index 4fcdf9fce..d69d707aa 100644 --- a/include/xchan.php +++ b/include/xchan.php @@ -1,6 +1,6 @@ <?php -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; use Zotlabs\Lib\Libzot; diff --git a/include/zot.php b/include/zot.php index 5fd900765..53c3d4d86 100644 --- a/include/zot.php +++ b/include/zot.php @@ -303,9 +303,8 @@ function zot_zot($url, $data, $channel = null,$crypto = null) { if($channel) { $headers['X-Zot-Token'] = random_string(); - $hash = \Zotlabs\Web\HTTPSig::generate_digest($data,false); - $headers['X-Zot-Digest'] = 'SHA-256=' . $hash; - $h = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'],'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,false,'sha512',(($crypto) ? $crypto['hubloc_sitekey'] : ''), (($crypto) ? zot_best_algorithm($crypto['site_crypto']) : '')); + $headers['X-Zot-Digest'] = \Zotlabs\Web\HTTPSig::generate_digest_header($data); + $h = \Zotlabs\Web\HTTPSig::create_sig($headers,$channel['channel_prvkey'],'acct:' . channel_reddress($channel),false,'sha512',(($crypto) ? $crypto['hubloc_sitekey'] : ''), (($crypto) ? zot_best_algorithm($crypto['site_crypto']) : '')); } $redirects = 0; diff --git a/tests/unit/Web/HttpSigTest.php b/tests/unit/Web/HttpSigTest.php index 9909a9883..db0f9700f 100644 --- a/tests/unit/Web/HttpSigTest.php +++ b/tests/unit/Web/HttpSigTest.php @@ -43,45 +43,30 @@ class PermissionDescriptionTest extends UnitTestCase { function testGenerate_digest($text, $digest) { $this->assertSame( $digest, - HTTPSig::generate_digest($text, false) + HTTPSig::generate_digest_header($text) ); } public function generate_digestProvider() { return [ 'empty body text' => [ '', - '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + 'SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' ], 'sample body text' => [ 'body text', - '2fu8kUkvuzuo5XyhWwORNOcJgDColXgxWkw1T5EXzPI=' + 'SHA-256=2fu8kUkvuzuo5XyhWwORNOcJgDColXgxWkw1T5EXzPI=' ], 'NULL body text' => [ null, - '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + 'SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' ], ]; } function testGeneratedDigestsOfDifferentTextShouldNotBeEqual() { $this->assertNotSame( - HTTPSig::generate_digest('text1', false), - HTTPSig::generate_digest('text2', false) - ); - } - - /** - * Process separation needed for header() check. - * @runInSeparateProcess - */ - function testGenerate_digestSendsHttpHeader() { - $ret = HTTPSig::generate_digest('body text', true); - - $this->assertSame('2fu8kUkvuzuo5XyhWwORNOcJgDColXgxWkw1T5EXzPI=', $ret); - $this->assertContains( - 'Digest: SHA-256=2fu8kUkvuzuo5XyhWwORNOcJgDColXgxWkw1T5EXzPI=', - xdebug_get_headers(), - 'HTTP header Digest does not match' + HTTPSig::generate_digest_header('text1'), + HTTPSig::generate_digest_header('text2') ); } |