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 WP_Error;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use DateTimeZone;
|
use DateTimeZone;
|
||||||
use Activitypub\Model\User;
|
|
||||||
use Activitypub\Collection\Users;
|
use Activitypub\Collection\Users;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,22 +22,14 @@ class Signature {
|
||||||
*
|
*
|
||||||
* @return mixed The public key.
|
* @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 ) {
|
if ( $force ) {
|
||||||
self::generate_key_pair( $user_id );
|
self::generate_key_pair_for( $user_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( User::APPLICATION_USER_ID === $user_id ) {
|
$key_pair = self::get_keypair_for( $user_id );
|
||||||
$key = \get_option( 'activitypub_magic_sig_public_key' );
|
|
||||||
} else {
|
|
||||||
$key = \get_user_meta( $user_id, 'magic_sig_public_key', true );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! $key ) {
|
return $key_pair['public_key'];
|
||||||
return self::get_public_key( $user_id, true );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,22 +40,32 @@ class Signature {
|
||||||
*
|
*
|
||||||
* @return mixed The private key.
|
* @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 ) {
|
if ( $force ) {
|
||||||
self::generate_key_pair( $user_id );
|
self::generate_key_pair_for( $user_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( User::APPLICATION_USER_ID === $user_id ) {
|
$key_pair = self::get_keypair_for( $user_id );
|
||||||
$key = \get_option( 'activitypub_magic_sig_private_key' );
|
|
||||||
} else {
|
return $key_pair['private_key'];
|
||||||
$key = \get_user_meta( $user_id, 'magic_sig_private_key', 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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! $key ) {
|
return $key_pair;
|
||||||
return self::get_private_key( $user_id, true );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,9 +73,18 @@ class Signature {
|
||||||
*
|
*
|
||||||
* @param int $user_id The WordPress User ID.
|
* @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(
|
$config = array(
|
||||||
'digest_alg' => 'sha512',
|
'digest_alg' => 'sha512',
|
||||||
'private_key_bits' => 2048,
|
'private_key_bits' => 2048,
|
||||||
|
@ -88,10 +98,76 @@ class Signature {
|
||||||
|
|
||||||
$detail = \openssl_pkey_get_details( $key );
|
$detail = \openssl_pkey_get_details( $key );
|
||||||
|
|
||||||
return array(
|
// 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,
|
'private_key' => $priv_key,
|
||||||
'public_key' => $detail['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 ) {
|
public static function generate_signature( $user_id, $http_method, $url, $date, $digest = null ) {
|
||||||
$user = Users::get_by_id( $user_id );
|
$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 );
|
$url_parts = \wp_parse_url( $url );
|
||||||
|
|
||||||
|
@ -136,7 +212,6 @@ class Signature {
|
||||||
\openssl_sign( $signed_string, $signature, $key, \OPENSSL_ALGO_SHA256 );
|
\openssl_sign( $signed_string, $signature, $key, \OPENSSL_ALGO_SHA256 );
|
||||||
$signature = \base64_encode( $signature ); // phpcs:ignore
|
$signature = \base64_encode( $signature ); // phpcs:ignore
|
||||||
|
|
||||||
$user = Users::get_by_id( $user_id );
|
|
||||||
$key_id = $user->get_url() . '#main-key';
|
$key_id = $user->get_url() . '#main-key';
|
||||||
|
|
||||||
if ( ! empty( $digest ) ) {
|
if ( ! empty( $digest ) ) {
|
||||||
|
|
|
@ -46,46 +46,6 @@ class Application_User extends Blog_User {
|
||||||
return $this::get_name();
|
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() {
|
public function get_followers() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,48 +187,6 @@ class Blog_User extends User {
|
||||||
return \gmdate( 'Y-m-d\TH:i:s\Z', $time );
|
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() {
|
public function get_attachment() {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,53 +159,10 @@ class User extends Actor {
|
||||||
return array(
|
return array(
|
||||||
'id' => $this->get_id() . '#main-key',
|
'id' => $this->get_id() . '#main-key',
|
||||||
'owner' => $this->get_id(),
|
'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.
|
* 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 );
|
$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
|
// signature_verification
|
||||||
$verified = \openssl_verify( $signed_data, $signature_block['signature'], $public_key, 'rsa-sha256' ) > 0;
|
$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',
|
'pre_get_remote_metadata_by_actor',
|
||||||
function( $json, $actor ) {
|
function( $json, $actor ) {
|
||||||
$user = Activitypub\Collection\Users::get_by_id( 1 );
|
$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 ActivityPub Profile with signature
|
||||||
return array(
|
return array(
|
||||||
'id' => $actor,
|
'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