2019-05-11 12:27:21 +01:00
< ? php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* ActivityPub implementation for GNU social
*
* @ package GNUsocial
* @ author Diogo Cordeiro < diogo @ fc . up . pt >
* @ copyright 2018 - 2019 Free Software Foundation , Inc http :// www . fsf . org
* @ license https :// www . gnu . org / licenses / agpl . html GNU AGPL v3 or later
* @ link http :// www . gnu . org / software / social /
*/
defined ( 'GNUSOCIAL' ) || die ();
/**
* ActivityPub Keys System
*
* @ category Plugin
* @ package GNUsocial
* @ author Diogo Cordeiro < diogo @ fc . up . pt >
* @ license https :// www . gnu . org / licenses / agpl . html GNU AGPL v3 or later
*/
class Activitypub_rsa extends Managed_DataObject
{
public $__table = 'activitypub_rsa' ;
public $profile_id ; // int(4) primary_key not_null
public $private_key ; // text() not_null
public $public_key ; // text() not_null
public $created ; // datetime() not_null default_CURRENT_TIMESTAMP
public $modified ; // datetime() not_null default_CURRENT_TIMESTAMP
/**
* Return table definition for Schema setup and DB_DataObject usage .
*
* @ return array array of column definitions
2019-10-11 17:08:37 +01:00
* @ author Diogo Cordeiro < diogo @ fc . up . pt >
2019-05-11 12:27:21 +01:00
*/
public static function schemaDef ()
{
return [
2019-10-11 17:08:37 +01:00
'fields' => [
'profile_id' => [ 'type' => 'int' , 'not null' => true ],
'private_key' => [ 'type' => 'text' ],
'public_key' => [ 'type' => 'text' , 'not null' => true ],
'created' => [ 'type' => 'datetime' , 'not null' => true , 'default' => 'CURRENT_TIMESTAMP' , 'description' => 'date this record was created' ],
'modified' => [ 'type' => 'datetime' , 'not null' => true , 'default' => 'CURRENT_TIMESTAMP' , 'description' => 'date this record was modified' ],
],
'primary key' => [ 'profile_id' ],
'foreign keys' => [
2020-06-28 18:05:11 +01:00
'activitypub_rsa_profile_id_fkey' => [ 'profile' , [ 'profile_id' => 'id' ]],
2019-10-11 17:08:37 +01:00
],
2019-05-11 12:27:21 +01:00
];
}
2019-10-11 17:08:37 +01:00
/**
* Private key getter
*
* @ param Profile $profile
2019-12-10 22:52:28 +00:00
* @ return string The private key
* @ throws Exception Throws exception if tries to fetch a private key of an actor we don ' t own
2019-10-11 17:08:37 +01:00
*/
public function get_private_key ( Profile $profile ) : string
2019-05-11 12:27:21 +01:00
{
$this -> profile_id = $profile -> getID ();
$apRSA = self :: getKV ( 'profile_id' , $this -> profile_id );
if ( ! $apRSA instanceof Activitypub_rsa ) {
2019-12-10 22:52:28 +00:00
// Nonexistent key pair for this profile
2019-05-11 12:27:21 +01:00
if ( $profile -> isLocal ()) {
self :: generate_keys ( $this -> private_key , $this -> public_key );
$this -> store_keys ();
2019-07-08 19:23:48 +01:00
$apRSA -> private_key = $this -> private_key ;
2019-05-11 12:27:21 +01:00
} else {
throw new Exception ( 'This is a remote Profile, there is no Private Key for this Profile.' );
}
}
return $apRSA -> private_key ;
}
/**
* Guarantees a Public Key for a given profile .
*
* @ param Profile $profile
2019-12-10 22:52:28 +00:00
* @ param bool $fetch = true Should attempt to fetch keys from a remote profile ?
2019-05-11 12:27:21 +01:00
* @ return string The public key
* @ throws ServerException It should never occur , but if so , we break everything !
2019-10-11 17:08:37 +01:00
* @ throws Exception
2019-05-11 12:27:21 +01:00
* @ author Diogo Cordeiro < diogo @ fc . up . pt >
*/
2019-10-11 17:08:37 +01:00
public function ensure_public_key ( Profile $profile , bool $fetch = true ) : string
2019-05-11 12:27:21 +01:00
{
$this -> profile_id = $profile -> getID ();
$apRSA = self :: getKV ( 'profile_id' , $this -> profile_id );
if ( ! $apRSA instanceof Activitypub_rsa ) {
// No existing key pair for this profile
if ( $profile -> isLocal ()) {
self :: generate_keys ( $this -> private_key , $this -> public_key );
$this -> store_keys ();
2019-07-08 19:23:48 +01:00
$apRSA -> public_key = $this -> public_key ;
2019-05-11 12:27:21 +01:00
} else {
2019-11-16 15:32:49 +00:00
// ASSERT: This should never happen, but try to recover!
common_log ( LOG_ERR , " Activitypub_rsa: An impossible thing has happened... Please let the devs know that it entered in line 116 at Activitypub_rsa.php " );
2019-05-11 12:27:21 +01:00
if ( $fetch ) {
2019-12-10 22:27:32 +00:00
$res = Activitypub_explorer :: get_remote_user_activity ( $profile -> getUri ());
2019-05-11 12:27:21 +01:00
Activitypub_rsa :: update_public_key ( $profile , $res [ 'publicKey' ][ 'publicKeyPem' ]);
return self :: ensure_public_key ( $profile , false );
} else {
throw new ServerException ( 'Activitypub_rsa: Failed to find keys for given profile. That should have not happened!' );
}
}
}
return $apRSA -> public_key ;
}
/**
* Insert the current object variables into the database .
*
2019-10-11 17:08:37 +01:00
* @ throws ServerException
2019-05-11 12:27:21 +01:00
* @ author Diogo Cordeiro < diogo @ fc . up . pt >
* @ access public
*/
2019-10-11 17:08:37 +01:00
public function store_keys () : void
2019-05-11 12:27:21 +01:00
{
$this -> created = $this -> modified = common_sql_now ();
$ok = $this -> insert ();
if ( $ok === false ) {
throw new ServerException ( 'Cannot save ActivityPub RSA.' );
}
}
/**
* Generates a pair of RSA keys .
*
2019-11-16 15:32:49 +00:00
* @ param string $private_key out
* @ param string $public_key out
2019-10-11 17:08:37 +01:00
* @ author PHP Manual Contributed Notes < dirt @ awoms . com >
2019-05-11 12:27:21 +01:00
*/
2019-11-16 15:32:49 +00:00
public static function generate_keys ( ? string & $private_key , ? string & $public_key ) : void
2019-05-11 12:27:21 +01:00
{
$config = [
2019-10-11 17:08:37 +01:00
'digest_alg' => 'sha512' ,
2019-05-11 12:27:21 +01:00
'private_key_bits' => 2048 ,
'private_key_type' => OPENSSL_KEYTYPE_RSA ,
];
// Create the private and public key
$res = openssl_pkey_new ( $config );
// Extract the private key from $res to $private_key
openssl_pkey_export ( $res , $private_key );
// Extract the public key from $res to $pubKey
$pubKey = openssl_pkey_get_details ( $res );
$public_key = $pubKey [ " key " ];
unset ( $pubKey );
}
/**
* Update public key .
*
2019-10-11 17:08:37 +01:00
* @ param Profile | Activitypub_profile $profile
2019-05-11 12:27:21 +01:00
* @ param string $public_key
* @ throws Exception
* @ author Diogo Cordeiro < diogo @ fc . up . pt >
*/
2019-10-11 17:08:37 +01:00
public static function update_public_key ( $profile , string $public_key ) : void
2019-05-11 12:27:21 +01:00
{
// Public Key
$apRSA = new Activitypub_rsa ();
$apRSA -> profile_id = $profile -> getID ();
$apRSA -> public_key = $public_key ;
2020-06-28 18:05:11 +01:00
$apRSA -> created = common_sql_now ();
2019-05-11 12:27:21 +01:00
if ( ! $apRSA -> update ()) {
$apRSA -> insert ();
}
}
}