Store keypairs as options keyed to user IDs. (#416)
This commit is contained in:
parent
8dcbe0c6fd
commit
8a74aa5891
6 changed files with 217 additions and 157 deletions
|
@ -4,7 +4,6 @@ namespace Activitypub;
|
|||
use WP_Error;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Activitypub\Model\User;
|
||||
use Activitypub\Collection\Users;
|
||||
|
||||
/**
|
||||
|
@ -23,22 +22,14 @@ class Signature {
|
|||
*
|
||||
* @return mixed The public key.
|
||||
*/
|
||||
public static function get_public_key( $user_id, $force = false ) {
|
||||
public static function get_public_key_for( $user_id, $force = false ) {
|
||||
if ( $force ) {
|
||||
self::generate_key_pair( $user_id );
|
||||
self::generate_key_pair_for( $user_id );
|
||||
}
|
||||
|
||||
if ( User::APPLICATION_USER_ID === $user_id ) {
|
||||
$key = \get_option( 'activitypub_magic_sig_public_key' );
|
||||
} else {
|
||||
$key = \get_user_meta( $user_id, 'magic_sig_public_key', true );
|
||||
}
|
||||
$key_pair = self::get_keypair_for( $user_id );
|
||||
|
||||
if ( ! $key ) {
|
||||
return self::get_public_key( $user_id, true );
|
||||
}
|
||||
|
||||
return $key;
|
||||
return $key_pair['public_key'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,22 +40,32 @@ class Signature {
|
|||
*
|
||||
* @return mixed The private key.
|
||||
*/
|
||||
public static function get_private_key( $user_id, $force = false ) {
|
||||
public static function get_private_key_for( $user_id, $force = false ) {
|
||||
if ( $force ) {
|
||||
self::generate_key_pair( $user_id );
|
||||
self::generate_key_pair_for( $user_id );
|
||||
}
|
||||
|
||||
if ( User::APPLICATION_USER_ID === $user_id ) {
|
||||
$key = \get_option( 'activitypub_magic_sig_private_key' );
|
||||
} else {
|
||||
$key = \get_user_meta( $user_id, 'magic_sig_private_key', true );
|
||||
$key_pair = self::get_keypair_for( $user_id );
|
||||
|
||||
return $key_pair['private_key'];
|
||||
}
|
||||
|
||||
if ( ! $key ) {
|
||||
return self::get_private_key( $user_id, true );
|
||||
/**
|
||||
* Return the key pair for a given user.
|
||||
*
|
||||
* @param int $user_id The WordPress User ID.
|
||||
*
|
||||
* @return array The key pair.
|
||||
*/
|
||||
public static function get_keypair_for( $user_id ) {
|
||||
$option_key = self::get_signature_options_key_for( $user_id );
|
||||
$key_pair = \get_option( $option_key );
|
||||
|
||||
if ( ! $key_pair ) {
|
||||
$key_pair = self::generate_key_pair_for( $user_id );
|
||||
}
|
||||
|
||||
return $key;
|
||||
return $key_pair;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,9 +73,18 @@ class Signature {
|
|||
*
|
||||
* @param int $user_id The WordPress User ID.
|
||||
*
|
||||
* @return void
|
||||
* @return array The key pair.
|
||||
*/
|
||||
public static function generate_key_pair() {
|
||||
protected static function generate_key_pair_for( $user_id ) {
|
||||
$option_key = self::get_signature_options_key_for( $user_id );
|
||||
$key_pair = self::check_legacy_key_pair_for( $user_id );
|
||||
|
||||
if ( $key_pair ) {
|
||||
\add_option( $option_key, $key_pair );
|
||||
|
||||
return $key_pair;
|
||||
}
|
||||
|
||||
$config = array(
|
||||
'digest_alg' => 'sha512',
|
||||
'private_key_bits' => 2048,
|
||||
|
@ -88,10 +98,76 @@ class Signature {
|
|||
|
||||
$detail = \openssl_pkey_get_details( $key );
|
||||
|
||||
// check if keys are valid
|
||||
if (
|
||||
empty( $priv_key ) || ! is_string( $priv_key ) ||
|
||||
! isset( $detail['key'] ) || ! is_string( $detail['key'] )
|
||||
) {
|
||||
return array(
|
||||
'private_key' => null,
|
||||
'public_key' => null,
|
||||
);
|
||||
}
|
||||
|
||||
$key_pair = array(
|
||||
'private_key' => $priv_key,
|
||||
'public_key' => $detail['key'],
|
||||
);
|
||||
|
||||
// persist keys
|
||||
\add_option( $option_key, $key_pair );
|
||||
|
||||
return $key_pair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param [type] $user_id
|
||||
* @return void
|
||||
*/
|
||||
protected static function get_signature_options_key_for( $user_id ) {
|
||||
$id = $user_id;
|
||||
|
||||
if ( $user_id > 0 ) {
|
||||
$user = \get_userdata( $user_id );
|
||||
$id = $user->user_login;
|
||||
}
|
||||
|
||||
return 'activitypub_keypair_for_' . $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is a legacy key pair
|
||||
*
|
||||
* @param int $user_id The WordPress User ID.
|
||||
*
|
||||
* @return array|bool The key pair or false.
|
||||
*/
|
||||
protected static function check_legacy_key_pair_for( $user_id ) {
|
||||
switch ( $user_id ) {
|
||||
case 0:
|
||||
$public_key = \get_option( 'activitypub_blog_user_public_key' );
|
||||
$private_key = \get_option( 'activitypub_blog_user_private_key' );
|
||||
break;
|
||||
case -1:
|
||||
$public_key = \get_option( 'activitypub_application_user_public_key' );
|
||||
$private_key = \get_option( 'activitypub_application_user_private_key' );
|
||||
break;
|
||||
default:
|
||||
$public_key = \get_user_meta( $user_id, 'magic_sig_public_key', true );
|
||||
$private_key = \get_user_meta( $user_id, 'magic_sig_private_key', true );
|
||||
break;
|
||||
}
|
||||
|
||||
if ( ! empty( $public_key ) && is_string( $public_key ) && ! empty( $private_key ) && is_string( $private_key ) ) {
|
||||
return array(
|
||||
'private_key' => $private_key,
|
||||
'public_key' => $public_key,
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,7 +183,7 @@ class Signature {
|
|||
*/
|
||||
public static function generate_signature( $user_id, $http_method, $url, $date, $digest = null ) {
|
||||
$user = Users::get_by_id( $user_id );
|
||||
$key = $user->get__private_key();
|
||||
$key = self::get_private_key_for( $user->get__id() );
|
||||
|
||||
$url_parts = \wp_parse_url( $url );
|
||||
|
||||
|
@ -136,7 +212,6 @@ class Signature {
|
|||
\openssl_sign( $signed_string, $signature, $key, \OPENSSL_ALGO_SHA256 );
|
||||
$signature = \base64_encode( $signature ); // phpcs:ignore
|
||||
|
||||
$user = Users::get_by_id( $user_id );
|
||||
$key_id = $user->get_url() . '#main-key';
|
||||
|
||||
if ( ! empty( $digest ) ) {
|
||||
|
|
|
@ -46,46 +46,6 @@ class Application_User extends Blog_User {
|
|||
return $this::get_name();
|
||||
}
|
||||
|
||||
public function get__public_key() {
|
||||
$key = \get_option( 'activitypub_application_user_public_key' );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
$key = \get_option( 'activitypub_application_user_public_key' );
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get__private_key() {
|
||||
$key = \get_option( 'activitypub_application_user_private_key' );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
return \get_option( 'activitypub_application_user_private_key' );
|
||||
}
|
||||
|
||||
private function generate_key_pair() {
|
||||
$key_pair = Signature::generate_key_pair();
|
||||
|
||||
if ( ! is_wp_error( $key_pair ) ) {
|
||||
\update_option( 'activitypub_application_user_public_key', $key_pair['public_key'] );
|
||||
\update_option( 'activitypub_application_user_private_key', $key_pair['private_key'] );
|
||||
}
|
||||
}
|
||||
|
||||
public function get_followers() {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -187,48 +187,6 @@ class Blog_User extends User {
|
|||
return \gmdate( 'Y-m-d\TH:i:s\Z', $time );
|
||||
}
|
||||
|
||||
public function get__public_key() {
|
||||
$key = \get_option( 'activitypub_blog_user_public_key' );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
$key = \get_option( 'activitypub_blog_user_public_key' );
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User-Private-Key.
|
||||
*
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get__private_key() {
|
||||
$key = \get_option( 'activitypub_blog_user_private_key' );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
return \get_option( 'activitypub_blog_user_private_key' );
|
||||
}
|
||||
|
||||
private function generate_key_pair() {
|
||||
$key_pair = Signature::generate_key_pair();
|
||||
|
||||
if ( ! is_wp_error( $key_pair ) ) {
|
||||
\update_option( 'activitypub_blog_user_public_key', $key_pair['public_key'] );
|
||||
\update_option( 'activitypub_blog_user_private_key', $key_pair['private_key'] );
|
||||
}
|
||||
}
|
||||
|
||||
public function get_attachment() {
|
||||
return array();
|
||||
}
|
||||
|
|
|
@ -159,53 +159,10 @@ class User extends Actor {
|
|||
return array(
|
||||
'id' => $this->get_id() . '#main-key',
|
||||
'owner' => $this->get_id(),
|
||||
'publicKeyPem' => $this->get__public_key(),
|
||||
'publicKeyPem' => Signature::get_public_key_for( $this->get__id() ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $this->get__id()
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get__public_key() {
|
||||
$key = \get_user_meta( $this->get__id(), 'magic_sig_public_key', true );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
return \get_user_meta( $this->get__id(), 'magic_sig_public_key', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $this->get__id()
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get__private_key() {
|
||||
$key = \get_user_meta( $this->get__id(), 'magic_sig_private_key', true );
|
||||
|
||||
if ( $key ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$this->generate_key_pair();
|
||||
|
||||
return \get_user_meta( $this->get__id(), 'magic_sig_private_key', true );
|
||||
}
|
||||
|
||||
private function generate_key_pair() {
|
||||
$key_pair = Signature::generate_key_pair();
|
||||
|
||||
if ( ! is_wp_error( $key_pair ) ) {
|
||||
\update_user_meta( $this->get__id(), 'magic_sig_public_key', $key_pair['public_key'], true );
|
||||
\update_user_meta( $this->get__id(), 'magic_sig_private_key', $key_pair['private_key'], true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Inbox-API-Endpoint.
|
||||
*
|
||||
|
|
|
@ -45,7 +45,7 @@ class Test_Activitypub_Signature_Verification extends WP_UnitTestCase {
|
|||
|
||||
$user = Activitypub\Collection\Users::get_by_id( 1 );
|
||||
|
||||
$public_key = $user->get__public_key();
|
||||
$public_key = Activitypub\Signature::get_public_key_for( $user->get__id() );
|
||||
|
||||
// signature_verification
|
||||
$verified = \openssl_verify( $signed_data, $signature_block['signature'], $public_key, 'rsa-sha256' ) > 0;
|
||||
|
@ -57,7 +57,7 @@ class Test_Activitypub_Signature_Verification extends WP_UnitTestCase {
|
|||
'pre_get_remote_metadata_by_actor',
|
||||
function( $json, $actor ) {
|
||||
$user = Activitypub\Collection\Users::get_by_id( 1 );
|
||||
$public_key = $user->get__public_key();
|
||||
$public_key = Activitypub\Signature::get_public_key_for( $user->get__id() );
|
||||
// return ActivityPub Profile with signature
|
||||
return array(
|
||||
'id' => $actor,
|
||||
|
|
110
tests/test-class-activitypub-signature.php
Normal file
110
tests/test-class-activitypub-signature.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
class Test_Activitypub_Signature extends WP_UnitTestCase {
|
||||
public function test_signature_creation() {
|
||||
$user = Activitypub\Collection\Users::get_by_id( 1 );
|
||||
|
||||
$key_pair = Activitypub\Signature::get_keypair_for( $user->get__id() );
|
||||
$public_key = Activitypub\Signature::get_public_key_for( $user->get__id() );
|
||||
$private_key = Activitypub\Signature::get_private_key_for( $user->get__id() );
|
||||
|
||||
$this->assertNotEmpty( $key_pair );
|
||||
$this->assertEquals( $key_pair['public_key'], $public_key );
|
||||
$this->assertEquals( $key_pair['private_key'], $private_key );
|
||||
}
|
||||
|
||||
public function test_signature_legacy() {
|
||||
// check user
|
||||
$user = Activitypub\Collection\Users::get_by_id( 1 );
|
||||
|
||||
$public_key = 'public key ' . $user->get__id();
|
||||
$private_key = 'private key ' . $user->get__id();
|
||||
|
||||
update_user_meta( $user->get__id(), 'magic_sig_public_key', $public_key );
|
||||
update_user_meta( $user->get__id(), 'magic_sig_private_key', $private_key );
|
||||
|
||||
$key_pair = Activitypub\Signature::get_keypair_for( $user->get__id() );
|
||||
|
||||
$this->assertNotEmpty( $key_pair );
|
||||
$this->assertEquals( $key_pair['public_key'], $public_key );
|
||||
$this->assertEquals( $key_pair['private_key'], $private_key );
|
||||
|
||||
// check application user
|
||||
$user = Activitypub\Collection\Users::get_by_id( -1 );
|
||||
|
||||
$public_key = 'public key ' . $user->get__id();
|
||||
$private_key = 'private key ' . $user->get__id();
|
||||
|
||||
add_option( 'activitypub_application_user_public_key', $public_key );
|
||||
add_option( 'activitypub_application_user_private_key', $private_key );
|
||||
|
||||
$key_pair = Activitypub\Signature::get_keypair_for( $user->get__id() );
|
||||
|
||||
$this->assertNotEmpty( $key_pair );
|
||||
$this->assertEquals( $key_pair['public_key'], $public_key );
|
||||
$this->assertEquals( $key_pair['private_key'], $private_key );
|
||||
|
||||
// check blog user
|
||||
\define( 'ACTIVITYPUB_DISABLE_BLOG_USER', false );
|
||||
$user = Activitypub\Collection\Users::get_by_id( 0 );
|
||||
|
||||
$public_key = 'public key ' . $user->get__id();
|
||||
$private_key = 'private key ' . $user->get__id();
|
||||
|
||||
add_option( 'activitypub_blog_user_public_key', $public_key );
|
||||
add_option( 'activitypub_blog_user_private_key', $private_key );
|
||||
|
||||
$key_pair = Activitypub\Signature::get_keypair_for( $user->get__id() );
|
||||
|
||||
$this->assertNotEmpty( $key_pair );
|
||||
$this->assertEquals( $key_pair['public_key'], $public_key );
|
||||
$this->assertEquals( $key_pair['private_key'], $private_key );
|
||||
}
|
||||
|
||||
public function test_signature_consistancy() {
|
||||
// check user
|
||||
$user = Activitypub\Collection\Users::get_by_id( 1 );
|
||||
|
||||
$public_key = 'public key ' . $user->get__id();
|
||||
$private_key = 'private key ' . $user->get__id();
|
||||
|
||||
update_user_meta( $user->get__id(), 'magic_sig_public_key', $public_key );
|
||||
update_user_meta( $user->get__id(), 'magic_sig_private_key', $private_key );
|
||||
|
||||
$key_pair = Activitypub\Signature::get_keypair_for( $user->get__id() );
|
||||
|
||||
$this->assertNotEmpty( $key_pair );
|
||||
$this->assertEquals( $key_pair['public_key'], $public_key );
|
||||
$this->assertEquals( $key_pair['private_key'], $private_key );
|
||||
|
||||
update_user_meta( $user->get__id(), 'magic_sig_public_key', $public_key . '-update' );
|
||||
update_user_meta( $user->get__id(), 'magic_sig_private_key', $private_key . '-update' );
|
||||
|
||||
$key_pair = Activitypub\Signature::get_keypair_for( $user->get__id() );
|
||||
|
||||
$this->assertNotEmpty( $key_pair );
|
||||
$this->assertEquals( $key_pair['public_key'], $public_key );
|
||||
$this->assertEquals( $key_pair['private_key'], $private_key );
|
||||
}
|
||||
|
||||
public function test_signature_consistancy2() {
|
||||
$user = Activitypub\Collection\Users::get_by_id( 1 );
|
||||
|
||||
$key_pair = Activitypub\Signature::get_keypair_for( $user->get__id() );
|
||||
$public_key = Activitypub\Signature::get_public_key_for( $user->get__id() );
|
||||
$private_key = Activitypub\Signature::get_private_key_for( $user->get__id() );
|
||||
|
||||
$this->assertNotEmpty( $key_pair );
|
||||
$this->assertEquals( $key_pair['public_key'], $public_key );
|
||||
$this->assertEquals( $key_pair['private_key'], $private_key );
|
||||
|
||||
update_user_meta( $user->get__id(), 'magic_sig_public_key', 'test' );
|
||||
update_user_meta( $user->get__id(), 'magic_sig_private_key', 'test' );
|
||||
|
||||
$key_pair = Activitypub\Signature::get_keypair_for( $user->get__id() );
|
||||
|
||||
$this->assertNotEmpty( $key_pair );
|
||||
$this->assertEquals( $key_pair['public_key'], $public_key );
|
||||
$this->assertEquals( $key_pair['private_key'], $private_key );
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue