*/ class ParTCP { public $storageClass; public $localId; public $remoteId; public $useSsl; public $remotePort; public $lastRequest; public $lastResponse; public $lastError; public function __construct(){ $args = func_get_args(); if ( isset( $args[1] ) ){ $storageClass = $args[0]; $storageParams = $args[1]; } else { $storageClass = 'ParTCP_Key_Storage_Fs'; $storageParams = [ 'storageDir' => $args[0] ]; } $this->storageClass = $storageClass; $keyClassName = substr( strtolower( $storageClass ), 7 ); if ( file_exists( __DIR__ . "/{$keyClassName}.class.php" ) ){ require_once __DIR__ . "/{$keyClassName}.class.php"; } require_once __DIR__ . '/crypto.class.php'; require_once __DIR__ . '/incoming_message.class.php'; require_once __DIR__ . '/outgoing_message.class.php'; require_once __DIR__ . '/identity.class.php'; ParTCP_Identity::$storage = $storageClass; foreach ( $storageParams as $name => $value ){ $storageClass::$$name = $value; } } public function set_remote_id( $server, $useSsl = NULL, $remotePort = NULL ){ $this->lastError = ''; $remoteId = new ParTCP_Public_Identity( $server, TRUE ); if ( ! $remoteId->pubKey ){ return FALSE; } $this->remoteId = $remoteId; $this->useSsl = $useSsl; $this->remotePort = $remotePort; return $remoteId; } public function set_local_id( $name, $create = FALSE, $persistent = FALSE ){ $this->lastError = ''; if ( ! $name ){ $this->localId = NULL; return TRUE; } if ( $name === TRUE ){ // generate temporary identity return TRUE; } $localId = new ParTCP_Private_Identity( $name, $create, $persistent ); if ( ! $localId->pubKey ){ return FALSE; } $this->localId = $localId; return $localId; } public function get_public_key( $name, $forceRetrieve = FALSE ){ $this->lastError = ''; $id = new ParTCP_Private_Identity( $name ); if ( ! empty( $id->pubKey ) ){ return $id->pubKey; } $id = new ParTCP_Public_Identity( $name, TRUE, $forceRetrieve ); if ( ! empty( $id->pubKey ) ){ return $id->pubKey; } return FALSE; } public function list_public_keys( $server = NULL ){ $this->lastError = ''; return $this->storageClass::list_pubkeys( $server ); } public function delete_public_key( $name ){ $this->lastError = ''; return $this->storageClass::delete_pubkey( $name ); } public function list_local_identities( $server = NULL ){ $this->lastError = ''; return $this->storageClass::list_keypairs( $server ); } public function delete_local_identity( $name ){ $this->lastError = ''; return $this->storageClass::delete_keypair( $name ); } public function send_message( $msgData, $withDate = TRUE, $withPubKey = FALSE ){ if ( ! $this->remoteId ){ $this->lastError = 'No remote identity specified'; return FALSE; } $msg = new ParTCP_Outgoing_Message( $this->remoteId, $this->localId, $msgData ); if ( $withDate ){ $msg->set_date(); } if ( $withPubKey ){ $msg->set_public_key(); } $this->lastRequest = $msg; $host = $msg->data['To']; $msgType = $msg->data['Message-Type']; $this->lastResponse = $msg->send( NULL, $this->useSsl, $this->remotePort ); if ( $this->lastResponse['status'] != 200 ){ $this->lastError = "{$host} responded with unexpected status " ."{$this->lastResponse['status']} to {$msgType}"; return FALSE; } $msg = new ParTCP_Incoming_Message( $this->lastResponse['body'] ); if ( $msg->parseError ){ $this->lastError = "Response from {$host} to {$msgType} could not be parsed"; return FALSE; } if ( $msg->get('Message-Type') == 'failure-notice' ){ $this->lastError = "{$host} could not process {$msgType}: " . $msg->get('Failure-Description'); return FALSE; } if ( $msg->get('Message-Type') == 'rejection-notice' ){ $this->lastError = "{$host} rejected {$msgType} with code " . "{$msg->get('Rejection-Code')}: {$msg->get('Rejection-Reason')}"; return FALSE; } if ( $msg->get('Signature') && ! $msg->get_signature_status() ){ $this->lastError = "Signature of {$msgType} from {$host} " . "could not be verified: {$msg->signatureStatusMessage}"; return FALSE; } return $msg; } public function ping( $testEncryption = NULL ){ $this->lastError = ''; if ( is_null( $testEncryption ) ){ $testEncryption = (bool) $this->localId; } if ( $testEncryption && ! $this->localId ){ $this->lastError = 'No local identity specified'; return FALSE; } $msgData['Message-Type'] = 'ping'; if ( $testEncryption ){ $msgData['Decryption-Request~~'] = 'abc123'; $msgData['Encryption-Request'] = 'abc123'; } $response = $this->send_message( $msgData ); if ( ! $response ){ return FALSE; } if ( $response->get('Message-Type') != 'echo' ){ $this->lastError = 'Unexpected message type: ' . $response->get('Message-Type'); return FALSE; } if ( $testEncryption ){ if ( $response->get('Decryption-Result') != 'abc123' ){ $this->lastError = 'Invalid decryption result'; return FALSE; } if ( $response->get('Encryption-Result') != 'abc123' ){ $this->lastError = 'Invalid encryption result'; return FALSE; } } return TRUE; } public function get_help( $topic = NULL ){ $this->lastError = ''; $msgData['Message-Type'] = 'help-request'; if ( $topic ){ $msgData['Help-Topic'] = $topic; } $response = $this->send_message( $msgData ); if ( ! $response ){ return FALSE; } if ( $response->get('Message-Type') != 'help' ){ $this->lastError = 'Unexpected message type: ' . $response->get('Message-Type'); return FALSE; } return $response->get('Help-Content'); } public function get_server_details( $includeOptions = [] ){ $this->lastError = ''; $msgData['Message-Type'] = 'server-details-request'; if ( in_array( 'events', $includeOptions ) ){ $msgData['Include-Events'] = TRUE; } if ( in_array( 'subgroups', $includeOptions ) ){ $msgData['Include-Subgroups'] = TRUE; } if ( in_array( 'admin', $includeOptions ) ){ $msgData['Include-Admin-Info'] = TRUE; } $response = $this->send_message( $msgData ); if ( ! $response ){ return FALSE; } if ( $response->get('Message-Type') != 'server-details' ){ $this->lastError = 'Unexpected message type: ' . $response->get('Message-Type'); return FALSE; } $result['server'] = $response->get('Server-Data'); $result['groups'] = $response->get('Groups') ?: []; if ( ! empty( $msgData['Include-Events'] ) ){ $result['events'] = $response->get('Events') ?: []; } return $result; } public function get_participant_details( $ptcpId ){ $this->lastError = ''; $msgData['Message-Type'] = 'participant-details-request'; $msgData['Participant-Id'] = $ptcpId; $response = $this->send_message( $msgData ); if ( ! $response ){ return FALSE; } if ( $response->get('Message-Type') != 'participant-details' ){ $this->lastError = 'Unexpected message type: ' . $response->get('Message-Type'); return FALSE; } return $response->get('Participant-Data'); } public function get_credential( $ptcpId, $permanence = FALSE, $consent = '' ){ $participant = $this->get_participant_details( $ptcpId ); if ( $participant ){ return $this->permit_key_renewal( $ptcpId, $permanence ); } return $this->register_participant( $ptcpId, $permanence, $consent ); } public function register_participant( $ptcpId, $permanence = FALSE, $consent = '', $flags = [], $attributes = [] ){ $this->lastError = ''; $credential = base64_encode( random_bytes( 12 ) ); $msgData = [ 'Message-Type' => 'registration', 'Participant-Id' => $ptcpId, 'Credential' => hash( 'sha256', $credential ), 'Credential-Permanence' => $permanence, 'Consent-Statement' => $consent ]; if ( $flags ){ $msgData['Flags'] = $flags; } if ( $attributes ){ $msgData['Attributes~~'] = $attributes; } $response = $this->send_message( $msgData ); if ( ! $response ){ return FALSE; } if ( $response->get('Message-Type') != 'registration-confirmation' ){ $this->lastError = 'Unexpected message type: ' . $response->get('Message-Type'); return FALSE; } return $credential; } public function permit_key_renewal( $ptcpId, $permanence = FALSE ){ $this->lastError = ''; $credential = base64_encode( random_bytes( 12 ) ); $msgData = [ 'Message-Type' => 'key-renewal-permission', 'Participant-Id' => $ptcpId, 'Credential' => hash( 'sha256', $credential ), 'Credential-Permanence' => $permanence, ]; $response = $this->send_message( $msgData ); if ( ! $response ){ return FALSE; } if ( $response->get('Message-Type') != 'participant-update-confirmation' ){ $this->lastError = 'Unexpected message type: ' . $response->get('Message-Type'); return FALSE; } return $credential; } public function submit_key( $credential ){ $this->lastError = ''; if ( strrchr( $this->localId->id, '@' ) != "@{$this->remoteId->id}" ){ $this->lastError = 'Local identity must correspond to remote identity'; return FALSE; } $msgData = [ 'Message-Type' => 'key-submission', 'Credential~~' => $credential, ]; $withDate = strpos( $this->localId->id, '+' ) === FALSE; $response = $this->send_message( $msgData, $withDate, TRUE ); if ( ! $response ){ return FALSE; } if ( $response->get('Message-Type') != 'participant-details' ){ $this->lastError = 'Unexpected message type: ' . $response->get('Message-Type'); return FALSE; } return TRUE; } } // end of file partcp.class.php