2017-08-18 05:43:24 +00:00
< ? php
namespace Zotlabs\Web ;
2018-06-20 02:33:50 +00:00
use Zotlabs\Lib\ActivityStreams ;
2019-01-17 23:12:58 +00:00
use Zotlabs\Lib\Activity ;
2018-06-20 02:33:50 +00:00
use Zotlabs\Lib\Webfinger ;
2018-12-12 05:42:15 +00:00
use Zotlabs\Lib\Zotfinger ;
use Zotlabs\Lib\Libzot ;
2018-12-14 01:16:01 +00:00
use Zotlabs\Lib\Crypto ;
2018-12-14 20:46:03 +00:00
use Zotlabs\Lib\Keyutils ;
2018-06-20 02:33:50 +00:00
2017-08-18 05:43:24 +00:00
/**
2018-06-20 02:33:50 +00:00
* @ brief Implements HTTP Signatures per draft - cavage - http - signatures - 10.
2018-02-14 23:01:11 +00:00
*
2018-06-20 02:33:50 +00:00
* @ see https :// tools . ietf . org / html / draft - cavage - http - signatures - 10
2017-08-18 05:43:24 +00:00
*/
2018-06-20 02:33:50 +00:00
2017-08-18 05:43:24 +00:00
class HTTPSig {
2018-02-14 23:01:11 +00:00
/**
* @ brief RFC5843
*
* @ see https :// tools . ietf . org / html / rfc5843
*
* @ param string $body The value to create the digest for
2018-06-20 02:33:50 +00:00
* @ param string $alg hash algorithm ( one of 'sha256' , 'sha512' )
* @ return string The generated digest header string for $body
2018-02-14 23:01:11 +00:00
*/
2017-08-18 05:43:24 +00:00
2018-05-24 04:48:19 +00:00
static function generate_digest_header ( $body , $alg = 'sha256' ) {
$digest = base64_encode ( hash ( $alg , $body , true ));
2019-10-10 00:34:18 +00:00
switch ( $alg ) {
2018-05-24 04:48:19 +00:00
case 'sha512' :
return 'SHA-512=' . $digest ;
case 'sha256' :
default :
return 'SHA-256=' . $digest ;
break ;
}
}
2019-12-24 20:26:50 +00:00
2018-05-31 03:32:59 +00:00
static function find_headers ( $data , & $body ) {
2017-08-21 01:30:12 +00:00
2017-08-18 05:43:24 +00:00
// decide if $data arrived via controller submission or curl
2019-12-24 20:26:50 +00:00
// changes $body for the caller
2020-01-31 00:12:41 +00:00
if ( is_array ( $data ) && array_key_exists ( 'header' , $data )) {
2019-10-10 00:34:18 +00:00
if ( ! $data [ 'success' ]) {
2020-01-31 00:12:41 +00:00
$body = EMPTY_STR ;
2018-05-30 04:08:52 +00:00
return [];
2019-10-10 00:34:18 +00:00
}
2018-02-14 23:01:11 +00:00
2020-01-31 00:12:41 +00:00
if ( ! $data [ 'header' ]) {
$body = EMPTY_STR ;
return [];
}
2018-06-20 02:33:50 +00:00
$h = new HTTPHeaders ( $data [ 'header' ]);
2017-08-18 05:43:24 +00:00
$headers = $h -> fetcharr ();
$body = $data [ 'body' ];
2018-10-11 00:58:51 +00:00
$headers [ '(request-target)' ] = $data [ 'request_target' ];
2017-08-18 05:43:24 +00:00
}
else {
$headers = [];
2018-05-30 04:08:52 +00:00
$headers [ '(request-target)' ] = strtolower ( $_SERVER [ 'REQUEST_METHOD' ]) . ' ' . $_SERVER [ 'REQUEST_URI' ];
2018-04-16 04:04:09 +00:00
$headers [ 'content-type' ] = $_SERVER [ 'CONTENT_TYPE' ];
2018-10-11 08:02:34 +00:00
$headers [ 'content-length' ] = $_SERVER [ 'CONTENT_LENGTH' ];
2018-04-16 04:04:09 +00:00
2019-10-10 00:34:18 +00:00
foreach ( $_SERVER as $k => $v ) {
if ( strpos ( $k , 'HTTP_' ) === 0 ) {
2017-08-18 05:43:24 +00:00
$field = str_replace ( '_' , '-' , strtolower ( substr ( $k , 5 )));
$headers [ $field ] = $v ;
}
}
}
2018-05-30 04:08:52 +00:00
//logger('SERVER: ' . print_r($_SERVER,true), LOGGER_ALL);
//logger('headers: ' . print_r($headers,true), LOGGER_ALL);
2018-04-16 04:04:09 +00:00
2018-05-30 04:08:52 +00:00
return $headers ;
}
2018-06-20 02:33:50 +00:00
// See draft-cavage-http-signatures-10
2018-05-30 04:08:52 +00:00
2020-07-15 11:10:02 +00:00
static function verify ( $data , $key = '' , $keytype = '' ) {
2018-05-30 04:08:52 +00:00
$body = $data ;
$headers = null ;
$result = [
'signer' => '' ,
2018-06-26 03:55:53 +00:00
'portable_id' => '' ,
2018-05-30 04:08:52 +00:00
'header_signed' => false ,
'header_valid' => false ,
'content_signed' => false ,
'content_valid' => false
];
2018-05-31 03:32:59 +00:00
$headers = self :: find_headers ( $data , $body );
2018-05-30 04:08:52 +00:00
2019-10-10 00:34:18 +00:00
if ( ! $headers ) {
2018-05-30 04:08:52 +00:00
return $result ;
2019-10-10 00:34:18 +00:00
}
2019-12-24 20:26:50 +00:00
if ( is_array ( $body )) {
btlogger ( 'body is array!' . print_r ( $body , true ));
}
2017-08-18 05:43:24 +00:00
$sig_block = null ;
2019-10-10 00:34:18 +00:00
if ( array_key_exists ( 'signature' , $headers )) {
2017-08-18 05:43:24 +00:00
$sig_block = self :: parse_sigheader ( $headers [ 'signature' ]);
}
2019-10-10 00:34:18 +00:00
elseif ( array_key_exists ( 'authorization' , $headers )) {
2017-08-18 05:43:24 +00:00
$sig_block = self :: parse_sigheader ( $headers [ 'authorization' ]);
}
2019-10-10 00:34:18 +00:00
if ( ! $sig_block ) {
2018-06-20 02:33:50 +00:00
logger ( 'no signature provided.' , LOGGER_DEBUG );
2017-08-21 01:30:12 +00:00
return $result ;
2017-09-22 01:29:41 +00:00
}
2017-08-21 01:30:12 +00:00
2017-10-12 18:36:25 +00:00
// Warning: This log statement includes binary data
// logger('sig_block: ' . print_r($sig_block,true), LOGGER_DATA);
2017-09-21 04:20:00 +00:00
2017-08-21 01:30:12 +00:00
$result [ 'header_signed' ] = true ;
2017-08-18 05:43:24 +00:00
$signed_headers = $sig_block [ 'headers' ];
2019-10-10 00:34:18 +00:00
if ( ! $signed_headers ) {
2017-08-18 05:43:24 +00:00
$signed_headers = [ 'date' ];
2019-10-10 00:34:18 +00:00
}
2017-08-18 05:43:24 +00:00
$signed_data = '' ;
2019-10-10 00:34:18 +00:00
foreach ( $signed_headers as $h ) {
if ( array_key_exists ( $h , $headers )) {
2017-08-18 05:43:24 +00:00
$signed_data .= $h . ': ' . $headers [ $h ] . " \n " ;
}
2019-10-10 00:34:18 +00:00
if ( $h === 'date' ) {
2018-10-10 04:08:57 +00:00
$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' );
2019-10-10 00:34:18 +00:00
if ( $c > $dplus || $c < $dminus ) {
2018-10-10 04:08:57 +00:00
logger ( 'bad time: ' . $c );
return $result ;
}
}
2017-08-18 05:43:24 +00:00
}
$signed_data = rtrim ( $signed_data , " \n " );
$algorithm = null ;
2019-10-10 00:34:18 +00:00
if ( $sig_block [ 'algorithm' ] === 'rsa-sha256' ) {
2017-08-18 05:43:24 +00:00
$algorithm = 'sha256' ;
}
2019-10-10 00:34:18 +00:00
if ( $sig_block [ 'algorithm' ] === 'rsa-sha512' ) {
2017-09-08 00:56:02 +00:00
$algorithm = 'sha512' ;
}
2017-08-18 05:43:24 +00:00
2019-10-10 00:34:18 +00:00
if ( ! array_key_exists ( 'keyId' , $sig_block )) {
2018-05-30 04:08:52 +00:00
return $result ;
2019-10-10 00:34:18 +00:00
}
2018-05-23 06:20:29 +00:00
2018-05-30 04:08:52 +00:00
$result [ 'signer' ] = $sig_block [ 'keyId' ];
2018-05-23 06:20:29 +00:00
2020-07-15 11:10:02 +00:00
$fkey = self :: get_key ( $key , $keytype , $result [ 'signer' ]);
2017-08-18 05:43:24 +00:00
2019-12-06 01:38:26 +00:00
if ( ! ( $fkey && $fkey [ 'public_key' ])) {
2017-09-14 03:40:01 +00:00
return $result ;
2018-06-29 03:25:43 +00:00
}
2017-08-18 05:43:24 +00:00
2019-12-06 01:38:26 +00:00
$x = Crypto :: verify ( $signed_data , $sig_block [ 'signature' ], $fkey [ 'public_key' ], $algorithm );
2017-08-18 05:43:24 +00:00
2017-09-21 04:20:00 +00:00
logger ( 'verified: ' . $x , LOGGER_DEBUG );
2019-10-10 00:34:18 +00:00
if ( ! $x ) {
2019-12-05 04:41:16 +00:00
// try again, ignoring the local actor (xchan) cache and refetching the key
// from its source
2020-07-15 11:10:02 +00:00
$fkey = self :: get_key ( $key , $keytype , $result [ 'signer' ], true );
2019-12-05 04:41:16 +00:00
2019-12-06 01:38:26 +00:00
if ( $fkey && $fkey [ 'public_key' ]) {
$y = Crypto :: verify ( $signed_data , $sig_block [ 'signature' ], $fkey [ 'public_key' ], $algorithm );
2019-12-05 04:41:16 +00:00
logger ( 'verified: (cache reload) ' . $x , LOGGER_DEBUG );
}
if ( ! $y ) {
2019-12-06 01:38:26 +00:00
logger ( 'verify failed for ' . $result [ 'signer' ] . ' alg=' . $algorithm . (( $fkey [ 'public_key' ]) ? '' : ' no key' ));
2019-12-05 04:41:16 +00:00
$sig_block [ 'signature' ] = base64_encode ( $sig_block [ 'signature' ]);
logger ( 'affected sigblock: ' . print_r ( $sig_block , true ));
logger ( 'headers: ' . print_r ( $headers , true ));
logger ( 'server: ' . print_r ( $_SERVER , true ));
return $result ;
}
2018-10-11 00:58:51 +00:00
}
2017-08-21 01:30:12 +00:00
2019-12-06 01:38:26 +00:00
$result [ 'portable_id' ] = $fkey [ 'portable_id' ];
2018-06-25 01:54:29 +00:00
$result [ 'header_valid' ] = true ;
2017-08-18 05:43:24 +00:00
2019-10-10 00:34:18 +00:00
if ( in_array ( 'digest' , $signed_headers )) {
2017-08-21 01:30:12 +00:00
$result [ 'content_signed' ] = true ;
2018-05-31 03:32:59 +00:00
$digest = explode ( '=' , $headers [ 'digest' ], 2 );
2019-10-10 00:34:18 +00:00
if ( $digest [ 0 ] === 'SHA-256' ) {
2017-08-18 05:43:24 +00:00
$hashalg = 'sha256' ;
2019-10-10 00:34:18 +00:00
}
if ( $digest [ 0 ] === 'SHA-512' ) {
2017-09-08 00:56:02 +00:00
$hashalg = 'sha512' ;
2019-10-10 00:34:18 +00:00
}
2017-08-18 05:43:24 +00:00
2019-10-10 00:34:18 +00:00
if ( base64_encode ( hash ( $hashalg , $body , true )) === $digest [ 1 ]) {
2017-08-21 01:30:12 +00:00
$result [ 'content_valid' ] = true ;
2018-01-17 02:15:58 +00:00
}
2018-06-25 01:54:29 +00:00
logger ( 'Content_Valid: ' . (( $result [ 'content_valid' ]) ? 'true' : 'false' ));
2020-02-07 05:43:39 +00:00
if ( ! $result [ 'content_valid' ]) {
logger ( 'invalid content signature: data ' . print_r ( $data , true ));
logger ( 'invalid content signature: headers ' . print_r ( $headers , true ));
logger ( 'invalid content signature: body ' . print_r ( $body , true ));
}
2018-06-25 01:54:29 +00:00
}
2017-09-21 04:20:00 +00:00
2017-08-21 01:30:12 +00:00
return $result ;
2017-08-18 05:43:24 +00:00
}
2020-07-15 11:10:02 +00:00
static function get_key ( $key , $keytype , $id , $force = false ) {
2018-05-30 04:08:52 +00:00
2019-10-10 00:34:18 +00:00
if ( $key ) {
if ( function_exists ( $key )) {
2018-08-05 03:35:27 +00:00
return $key ( $id );
}
return [ 'public_key' => $key ];
2018-05-30 04:08:52 +00:00
}
2020-07-15 11:10:02 +00:00
if ( $keytype === 'zot6' ) {
$key = self :: get_zotfinger_key ( $id , $force );
if ( $key ) {
return $key ;
}
2018-08-29 02:49:30 +00:00
}
2020-07-21 05:40:07 +00:00
2018-05-30 04:08:52 +00:00
2020-07-15 11:10:02 +00:00
if ( strpos ( $id , '#' ) === false ) {
$key = self :: get_webfinger_key ( $id , $force );
if ( $key ) {
return $key ;
}
2018-05-30 04:08:52 +00:00
}
2020-07-15 11:10:02 +00:00
$key = self :: get_activitystreams_key ( $id , $force );
2018-05-30 04:08:52 +00:00
return $key ;
}
2021-03-09 22:12:18 +00:00
static function convertKey ( $key ) {
2018-06-20 02:33:50 +00:00
2019-10-10 00:34:18 +00:00
if ( strstr ( $key , 'RSA ' )) {
2018-12-14 20:46:03 +00:00
return Keyutils :: rsatopem ( $key );
2018-06-20 02:33:50 +00:00
}
2019-10-10 00:34:18 +00:00
elseif ( substr ( $key , 0 , 5 ) === 'data:' ) {
2021-04-06 01:42:01 +00:00
return Keyutils :: convertSalmonKey ( $key );
2018-06-20 02:33:50 +00:00
}
else {
return $key ;
}
}
2018-05-30 04:08:52 +00:00
2018-02-14 23:01:11 +00:00
/**
* @ brief
*
* @ param string $id
* @ return boolean | string
* false if no pub key found , otherwise return the pub key
*/
2017-08-18 05:43:24 +00:00
2021-03-09 01:51:41 +00:00
static function get_activitystreams_key ( $id , $force = false ) {
2018-05-30 04:08:52 +00:00
2021-08-03 22:28:53 +00:00
// Check the local cache first, but remove any fragments like #main-key since these won't be present in our cached data
2018-09-07 04:51:52 +00:00
2021-08-03 22:28:53 +00:00
$cache_url = (( strpos ( $id , '#' )) ? substr ( $id , 0 , strpos ( $id , '#' )) : $id );
2018-09-07 04:51:52 +00:00
2021-08-03 22:28:53 +00:00
// $force is used to ignore the local cache and only use the remote data; for instance the cached key might be stale
2019-12-05 04:41:16 +00:00
if ( ! $force ) {
2021-10-12 21:20:36 +00:00
$x = q ( " select * from xchan left join hubloc on xchan_hash = hubloc_hash where ( hubloc_addr = '%s' or hubloc_id_url = '%s' or hubloc_hash = '%s') order by hubloc_id desc " ,
2021-08-03 22:28:53 +00:00
dbesc ( str_replace ( 'acct:' , '' , $cache_url )),
2021-10-12 21:20:36 +00:00
dbesc ( $cache_url ),
2021-08-03 22:28:53 +00:00
dbesc ( $cache_url )
2019-12-05 04:41:16 +00:00
);
2017-08-18 05:43:24 +00:00
2021-08-03 22:28:53 +00:00
if ( $x ) {
$best = Libzot :: zot_record_preferred ( $x );
}
2018-12-12 05:42:15 +00:00
2021-08-03 22:28:53 +00:00
if ( $best && $best [ 'xchan_pubkey' ]) {
return [ 'portable_id' => $best [ 'xchan_hash' ], 'public_key' => $best [ 'xchan_pubkey' ] , 'hubloc' => $best ];
}
2017-08-18 05:43:24 +00:00
}
2018-04-23 05:10:15 +00:00
2021-08-03 22:28:53 +00:00
// The record wasn't in cache. Fetch it now.
2019-01-14 05:22:45 +00:00
$r = Activity :: fetch ( $id );
2017-08-18 05:43:24 +00:00
2019-10-10 00:34:18 +00:00
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 ) {
2018-09-07 04:51:52 +00:00
$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' => [] ];
2018-06-29 03:25:43 +00:00
}
2017-08-18 05:43:24 +00:00
}
}
2021-08-03 22:28:53 +00:00
// No key was found
2017-08-18 05:43:24 +00:00
return false ;
}
2018-04-23 02:49:26 +00:00
2021-03-09 01:51:41 +00:00
static function get_webfinger_key ( $id , $force = false ) {
2018-05-23 06:20:29 +00:00
2019-12-05 04:41:16 +00:00
if ( ! $force ) {
2021-10-13 03:25:00 +00:00
$x = q ( " select * from xchan left join hubloc on xchan_hash = hubloc_hash where ( hubloc_addr = '%s' or hubloc_id_url = '%s' or hubloc_hash = '%s') order by hubloc_id desc " ,
2019-12-05 04:41:16 +00:00
dbesc ( str_replace ( 'acct:' , '' , $id )),
2021-10-13 03:25:00 +00:00
dbesc ( $id ),
2019-12-05 04:41:16 +00:00
dbesc ( $id )
);
2018-07-30 03:19:02 +00:00
2021-08-03 22:28:53 +00:00
if ( $x ) {
$best = Libzot :: zot_record_preferred ( $x );
}
2018-12-12 05:42:15 +00:00
2021-08-03 22:28:53 +00:00
if ( $best && $best [ 'xchan_pubkey' ]) {
return [ 'portable_id' => $best [ 'xchan_hash' ], 'public_key' => $best [ 'xchan_pubkey' ] , 'hubloc' => $best ];
}
2018-05-30 04:08:52 +00:00
}
2018-06-20 02:33:50 +00:00
$wf = Webfinger :: exec ( $id );
2018-06-29 03:25:43 +00:00
$key = [ 'portable_id' => '' , 'public_key' => '' , 'hubloc' => [] ];
2018-05-23 06:20:29 +00:00
2019-10-10 00:34:18 +00:00
if ( $wf ) {
if ( array_key_exists ( 'properties' , $wf ) && array_key_exists ( 'https://w3id.org/security/v1#publicKeyPem' , $wf [ 'properties' ])) {
2018-07-26 02:56:24 +00:00
$key [ 'public_key' ] = self :: convertKey ( $wf [ 'properties' ][ 'https://w3id.org/security/v1#publicKeyPem' ]);
}
2019-10-10 00:34:18 +00:00
if ( array_key_exists ( 'links' , $wf ) && is_array ( $wf [ 'links' ])) {
foreach ( $wf [ 'links' ] as $l ) {
if ( ! ( is_array ( $l ) && array_key_exists ( 'rel' , $l ))) {
2018-07-26 02:56:24 +00:00
continue ;
}
2019-10-10 00:34:18 +00:00
if ( $l [ 'rel' ] === 'magic-public-key' && array_key_exists ( 'href' , $l ) && $key [ 'public_key' ] === EMPTY_STR ) {
2018-07-26 02:56:24 +00:00
$key [ 'public_key' ] = self :: convertKey ( $l [ 'href' ]);
}
}
}
}
return (( $key [ 'public_key' ]) ? $key : false );
}
2021-03-09 01:51:41 +00:00
static function get_zotfinger_key ( $id , $force = false ) {
2018-07-26 02:56:24 +00:00
2019-12-05 04:41:16 +00:00
if ( ! $force ) {
2021-10-13 03:25:00 +00:00
$x = q ( " select * from xchan left join hubloc on xchan_hash = hubloc_hash where ( hubloc_addr = '%s' or hubloc_id_url = '%s' ) and hubloc_network = 'zot6' order by hubloc_id desc " ,
2019-12-05 04:41:16 +00:00
dbesc ( str_replace ( 'acct:' , '' , $id )),
dbesc ( $id )
);
2018-12-12 05:42:15 +00:00
2021-08-03 22:28:53 +00:00
if ( $x ) {
$best = Libzot :: zot_record_preferred ( $x );
}
2018-12-12 05:42:15 +00:00
2021-08-03 22:28:53 +00:00
if ( $best && $best [ 'xchan_pubkey' ]) {
return [ 'portable_id' => $best [ 'xchan_hash' ], 'public_key' => $best [ 'xchan_pubkey' ] , 'hubloc' => $best ];
}
2018-07-26 02:56:24 +00:00
}
$wf = Webfinger :: exec ( $id );
$key = [ 'portable_id' => '' , 'public_key' => '' , 'hubloc' => [] ];
2019-10-10 00:34:18 +00:00
if ( $wf ) {
if ( array_key_exists ( 'properties' , $wf ) && array_key_exists ( 'https://w3id.org/security/v1#publicKeyPem' , $wf [ 'properties' ])) {
2018-06-26 03:55:53 +00:00
$key [ 'public_key' ] = self :: convertKey ( $wf [ 'properties' ][ 'https://w3id.org/security/v1#publicKeyPem' ]);
2018-06-20 02:33:50 +00:00
}
2019-10-10 00:34:18 +00:00
if ( array_key_exists ( 'links' , $wf ) && is_array ( $wf [ 'links' ])) {
foreach ( $wf [ 'links' ] as $l ) {
if ( ! ( is_array ( $l ) && array_key_exists ( 'rel' , $l ))) {
2018-06-26 03:55:53 +00:00
continue ;
}
2019-10-10 00:34:18 +00:00
if ( $l [ 'rel' ] === 'http://purl.org/zot/protocol/6.0' && array_key_exists ( 'href' , $l ) && $l [ 'href' ] !== EMPTY_STR ) {
2020-07-16 02:28:01 +00:00
// The third argument to Zotfinger::exec() tells it not to verify signatures
// Since we're inside a function that is fetching keys with which to verify signatures,
// this is necessary to prevent infinite loops.
2020-07-15 23:29:17 +00:00
$z = Zotfinger :: exec ( $l [ 'href' ], null , false );
2019-10-10 00:34:18 +00:00
if ( $z ) {
2018-12-12 05:42:15 +00:00
$i = Libzot :: import_xchan ( $z [ 'data' ]);
2019-10-10 00:34:18 +00:00
if ( $i [ 'success' ]) {
2018-06-26 03:55:53 +00:00
$key [ 'portable_id' ] = $i [ 'hash' ];
2018-06-29 03:25:43 +00:00
2021-03-15 23:56:03 +00:00
$x = q ( " select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s' order by hubloc_id desc limit 1 " ,
2018-06-29 03:25:43 +00:00
dbesc ( $l [ 'href' ])
);
2019-10-10 00:34:18 +00:00
if ( $x ) {
2018-06-29 03:25:43 +00:00
$key [ 'hubloc' ] = $x [ 0 ];
}
2018-06-26 03:55:53 +00:00
}
2018-06-20 02:33:50 +00:00
}
}
2019-10-10 00:34:18 +00:00
if ( $l [ 'rel' ] === 'magic-public-key' && array_key_exists ( 'href' , $l ) && $key [ 'public_key' ] === EMPTY_STR ) {
2018-06-26 03:55:53 +00:00
$key [ 'public_key' ] = self :: convertKey ( $l [ 'href' ]);
}
2018-06-20 02:33:50 +00:00
}
2018-05-23 06:20:29 +00:00
}
}
2018-06-26 03:55:53 +00:00
return (( $key [ 'public_key' ]) ? $key : false );
2018-05-23 06:20:29 +00:00
}
2018-02-14 23:01:11 +00:00
/**
* @ brief
*
* @ param array $head
* @ param string $prvkey
2018-06-20 05:09:49 +00:00
* @ param string $keyid ( optional , default '' )
2018-02-14 23:01:11 +00:00
* @ param boolean $auth ( optional , default false )
* @ param string $alg ( optional , default 'sha256' )
2018-06-20 02:33:50 +00:00
* @ param array $encryption [ 'key' , 'algorithm' ] or false
2018-02-14 23:01:11 +00:00
* @ return array
*/
2018-06-20 02:33:50 +00:00
static function create_sig ( $head , $prvkey , $keyid = EMPTY_STR , $auth = false , $alg = 'sha256' , $encryption = false ) {
2017-08-18 05:43:24 +00:00
$return_headers = [];
2019-10-10 00:34:18 +00:00
if ( $alg === 'sha256' ) {
2017-08-18 05:43:24 +00:00
$algorithm = 'rsa-sha256' ;
}
2019-10-10 00:34:18 +00:00
if ( $alg === 'sha512' ) {
2017-09-08 00:56:02 +00:00
$algorithm = 'rsa-sha512' ;
}
2017-08-18 05:43:24 +00:00
2018-06-20 02:33:50 +00:00
$x = self :: sign ( $head , $prvkey , $alg );
2017-08-18 05:43:24 +00:00
2018-06-20 02:33:50 +00:00
$headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm . '",headers="' . $x [ 'headers' ] . '",signature="' . $x [ 'signature' ] . '"' ;
2017-09-24 23:46:52 +00:00
2019-10-10 00:34:18 +00:00
if ( $encryption ) {
2018-12-17 04:37:43 +00:00
$x = Crypto :: encapsulate ( $headerval , $encryption [ 'key' ], $encryption [ 'algorithm' ]);
2019-10-10 00:34:18 +00:00
if ( is_array ( $x )) {
2018-07-23 06:33:13 +00:00
$headerval = 'iv="' . $x [ 'iv' ] . '",key="' . $x [ 'key' ] . '",alg="' . $x [ 'alg' ] . '",data="' . $x [ 'data' ] . '"' ;
}
2017-09-24 23:46:52 +00:00
}
2018-02-14 23:01:11 +00:00
2019-10-10 00:34:18 +00:00
if ( $auth ) {
2017-09-24 23:46:52 +00:00
$sighead = 'Authorization: Signature ' . $headerval ;
2017-09-01 04:38:03 +00:00
}
else {
2017-09-24 23:46:52 +00:00
$sighead = 'Signature: ' . $headerval ;
2017-09-01 04:38:03 +00:00
}
2017-08-18 05:43:24 +00:00
2019-10-10 00:34:18 +00:00
if ( $head ) {
foreach ( $head as $k => $v ) {
2018-06-20 02:33:50 +00:00
// strip the request-target virtual header from the output headers
2019-10-10 00:34:18 +00:00
if ( $k === '(request-target)' ) {
2018-06-20 02:33:50 +00:00
continue ;
2017-08-18 05:43:24 +00:00
}
2018-06-20 02:33:50 +00:00
$return_headers [] = $k . ': ' . $v ;
2017-08-18 05:43:24 +00:00
}
}
2018-06-20 02:33:50 +00:00
$return_headers [] = $sighead ;
2018-02-14 23:01:11 +00:00
2017-08-18 05:43:24 +00:00
return $return_headers ;
}
2018-06-20 05:09:49 +00:00
/**
* @ brief set headers
*
* @ param array $headers
* @ return void
*/
2018-06-20 02:33:50 +00:00
static function set_headers ( $headers ) {
2019-10-10 00:34:18 +00:00
if ( $headers && is_array ( $headers )) {
foreach ( $headers as $h ) {
2018-06-20 02:33:50 +00:00
header ( $h );
}
}
}
2018-02-14 23:01:11 +00:00
/**
* @ brief
*
* @ param array $head
* @ param string $prvkey
* @ param string $alg ( optional ) default 'sha256'
* @ return array
*/
2018-06-20 05:09:49 +00:00
2018-06-20 02:33:50 +00:00
static function sign ( $head , $prvkey , $alg = 'sha256' ) {
2017-08-18 05:43:24 +00:00
$ret = [];
$headers = '' ;
$fields = '' ;
2018-10-17 02:32:17 +00:00
logger ( 'signing: ' . print_r ( $head , true ), LOGGER_DATA );
2019-10-10 00:34:18 +00:00
if ( $head ) {
foreach ( $head as $k => $v ) {
2017-08-18 05:43:24 +00:00
$headers .= strtolower ( $k ) . ': ' . trim ( $v ) . " \n " ;
2019-10-10 00:34:18 +00:00
if ( $fields ) {
2017-08-18 05:43:24 +00:00
$fields .= ' ' ;
2019-10-10 00:34:18 +00:00
}
2017-08-18 05:43:24 +00:00
$fields .= strtolower ( $k );
}
// strip the trailing linefeed
$headers = rtrim ( $headers , " \n " );
}
2018-12-14 01:16:01 +00:00
$sig = base64_encode ( Crypto :: sign ( $headers , $prvkey , $alg ));
2017-08-18 05:43:24 +00:00
$ret [ 'headers' ] = $fields ;
$ret [ 'signature' ] = $sig ;
2018-02-14 23:01:11 +00:00
2017-08-18 05:43:24 +00:00
return $ret ;
}
2018-02-14 23:01:11 +00:00
/**
* @ brief
*
* @ param string $header
* @ return array associate array with
* - \e string \b keyID
* - \e string \b algorithm
* - \e array \b headers
* - \e string \b signature
*/
2018-06-20 05:09:49 +00:00
2017-08-18 05:43:24 +00:00
static function parse_sigheader ( $header ) {
2017-09-24 23:46:52 +00:00
2017-08-18 05:43:24 +00:00
$ret = [];
$matches = [];
2017-09-24 23:46:52 +00:00
// if the header is encrypted, decrypt with (default) site private key and continue
2019-10-10 00:34:18 +00:00
if ( preg_match ( '/iv="(.*?)"/ism' , $header , $matches )) {
2017-09-24 23:46:52 +00:00
$header = self :: decrypt_sigheader ( $header );
2019-10-10 00:34:18 +00:00
}
if ( preg_match ( '/keyId="(.*?)"/ism' , $header , $matches )) {
2017-08-18 05:43:24 +00:00
$ret [ 'keyId' ] = $matches [ 1 ];
2019-10-10 00:34:18 +00:00
}
if ( preg_match ( '/algorithm="(.*?)"/ism' , $header , $matches )) {
2017-08-18 05:43:24 +00:00
$ret [ 'algorithm' ] = $matches [ 1 ];
2019-10-10 00:34:18 +00:00
}
if ( preg_match ( '/headers="(.*?)"/ism' , $header , $matches )) {
2017-08-18 05:43:24 +00:00
$ret [ 'headers' ] = explode ( ' ' , $matches [ 1 ]);
2019-10-10 00:34:18 +00:00
}
if ( preg_match ( '/signature="(.*?)"/ism' , $header , $matches )) {
2017-08-18 05:43:24 +00:00
$ret [ 'signature' ] = base64_decode ( preg_replace ( '/\s+/' , '' , $matches [ 1 ]));
2019-10-10 00:34:18 +00:00
}
2017-08-18 05:43:24 +00:00
2019-10-10 00:34:18 +00:00
if (( $ret [ 'signature' ]) && ( $ret [ 'algorithm' ]) && ( ! $ret [ 'headers' ])) {
2017-08-18 05:43:24 +00:00
$ret [ 'headers' ] = [ 'date' ];
2019-10-10 00:34:18 +00:00
}
2017-08-18 05:43:24 +00:00
return $ret ;
}
2018-02-14 23:01:11 +00:00
/**
* @ brief
*
* @ param string $header
* @ param string $prvkey ( optional ), if not set use site private key
* @ return array | string associative array , empty string if failue
* - \e string \b iv
* - \e string \b key
* - \e string \b alg
* - \e string \b data
*/
2018-06-20 05:09:49 +00:00
2018-02-14 23:01:11 +00:00
static function decrypt_sigheader ( $header , $prvkey = null ) {
2017-09-24 23:46:52 +00:00
$iv = $key = $alg = $data = null ;
2019-10-10 00:34:18 +00:00
if ( ! $prvkey ) {
2018-02-14 23:01:11 +00:00
$prvkey = get_config ( 'system' , 'prvkey' );
2017-09-24 23:46:52 +00:00
}
$matches = [];
2019-10-10 00:34:18 +00:00
if ( preg_match ( '/iv="(.*?)"/ism' , $header , $matches )) {
2017-09-24 23:46:52 +00:00
$iv = $matches [ 1 ];
2019-10-10 00:34:18 +00:00
}
if ( preg_match ( '/key="(.*?)"/ism' , $header , $matches )) {
2017-09-24 23:46:52 +00:00
$key = $matches [ 1 ];
2019-10-10 00:34:18 +00:00
}
if ( preg_match ( '/alg="(.*?)"/ism' , $header , $matches )) {
2017-09-24 23:46:52 +00:00
$alg = $matches [ 1 ];
2019-10-10 00:34:18 +00:00
}
if ( preg_match ( '/data="(.*?)"/ism' , $header , $matches )) {
2017-09-24 23:46:52 +00:00
$data = $matches [ 1 ];
2019-10-10 00:34:18 +00:00
}
2017-09-24 23:46:52 +00:00
2019-10-10 00:34:18 +00:00
if ( $iv && $key && $alg && $data ) {
2018-12-17 04:37:43 +00:00
return Crypto :: unencapsulate ([ 'encrypted' => true , 'iv' => $iv , 'key' => $key , 'alg' => $alg , 'data' => $data ] , $prvkey );
2017-09-24 23:46:52 +00:00
}
2018-02-14 23:01:11 +00:00
return '' ;
2017-09-24 23:46:52 +00:00
}
2017-08-18 05:43:24 +00:00
}