Store keypairs as options keyed to user IDs. (#416)

This commit is contained in:
Matt Wiebe 2023-09-07 15:04:39 -05:00 committed by GitHub
parent 8dcbe0c6fd
commit 8a74aa5891
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 217 additions and 157 deletions

View file

@ -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 ) ) {

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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.
*

View file

@ -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,

View 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 );
}
}