diff --git a/.github/changelog/1557-from-description b/.github/changelog/1557-from-description new file mode 100644 index 000000000..8fa7cc371 --- /dev/null +++ b/.github/changelog/1557-from-description @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Increased compatibility with Mobilizon and other platforms by improving signature verification for different key formats. diff --git a/includes/class-signature.php b/includes/class-signature.php index 89aa52114..63bc1842c 100644 --- a/includes/class-signature.php +++ b/includes/class-signature.php @@ -332,7 +332,7 @@ public static function verify_http_signature( $request ) { * * @param string $key_id The URL to the public key. * - * @return WP_Error|string The public key or WP_Error. + * @return resource|WP_Error The public key resource or WP_Error. */ public static function get_remote_key( $key_id ) { $actor = get_remote_metadata_by_actor( strip_fragment_from_url( $key_id ) ); @@ -343,9 +343,14 @@ public static function get_remote_key( $key_id ) { array( 'status' => 401 ) ); } + if ( isset( $actor['publicKey']['publicKeyPem'] ) ) { - return \rtrim( $actor['publicKey']['publicKeyPem'] ); + $key_resource = \openssl_pkey_get_public( \rtrim( $actor['publicKey']['publicKeyPem'] ) ); + if ( $key_resource ) { + return $key_resource; + } } + return new WP_Error( 'activitypub_no_remote_key_found', __( 'No Public-Key found', 'activitypub' ), diff --git a/tests/includes/class-test-signature.php b/tests/includes/class-test-signature.php index 1d21fd9b3..c9d8844b4 100644 --- a/tests/includes/class-test-signature.php +++ b/tests/includes/class-test-signature.php @@ -16,6 +16,65 @@ * @coversDefaultClass \Activitypub\Signature */ class Test_Signature extends \WP_UnitTestCase { + + /** + * The public key in PKCS#1 format. + * + * @var string + */ + private $pkcs1_key = '-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAtAVnFFbWG+6NBFKhMZdt59Gx2/vKxWxbxOAYyi/ypZ/9aDY6C/UB +Rei8SqnhKcKXQaiSwme/wpqgCdkrf53H85OioBitCEvKNA6uDxkCtcdgtQ3X55QD +XmatWd32ln6elRmKG45U9R386j82OHzff8Ju65QxGL1LlyCKQ/XFx/pgvblF3cGj +shk0dhNcyGAztODN5HFp9Qzf9d7+gi+xdKeGNhXBAulXoaDzx8FvLEXNfPJb3jUM +1Ug0STFsiICcf7VxmQow6N6d0+HtWxrdtjUBdXrPxz998Ns/cu9jjg06d+XV3TcS +U+AOldmGLJuB/AWV/+F9c9DlczqmnXqd1QIDAQAB +-----END RSA PUBLIC KEY----- +'; + + /** + * The public key in X.509 format. + * + * @var string + */ + private $x509_key = '-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA19218d19uYisOYUZ3oqN +wSRyixAX8V1JHJSngbjAjZr1vYcwMte8CPqqELbNwtQWAMy42UnQpyIqgvLpOaVr +vQWjUuR+7i8wETrVNJq8JQNNCiQ+8+I4TPcGyZDBclHkLtKiCoBtjUH0itVh4Sg0 +KQLSb8ZHu9lGh8TJMcLXVUdVkvkUjqHl6I5BoftMVDSKQF+V4X8Qyk7qP7wU8mpE ++O6RuhUpZ3QXM+dBIalyey8NKLf2yN6CmKyW1220wdNupOYHbc8DSYEq6NDQZfZb +yP2KLHN3rdNwsnlAP02Ws1qroBivHSV71KLebQUDU2KpDLKQF2Ix6X47IBFOXnb9 +FwIDAQAB +-----END PUBLIC KEY----- +'; + + /** + * The public key in EC format. + * + * @var string + */ + private $ec_key = '-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/jw3kftaHGIB2OTKTYFUTTqyzDs0 +eWKe+6k1Kh6HSrinXriBLbIhMPY9pQsvqkeT6wW975NDn7+8awb8kHRmIg== +-----END PUBLIC KEY----- +'; + + /** + * The public key in PKCS#8 format. + * + * @var string + */ + private $pkcs8_key = '-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8dfWmTltr09m49uyESj +x6UnQ9G/iVq+3dJbUdCdVEPR256UD6DLHE8uM4DgXhtoLVrBcvTAl9h0nRGX4uVN +5jE+pTh47B9IUim0bVw2sOBNwPCTUuKbMVx3Cso/6UxJsot41q7+FHIxcAurDxfR +xfJkf+1ecYSb5czoeOG+NUcTEQv1LQntAOJ1ngrmjKyL4UlKZgcs2TfueqlK1v2t +Gw4ylFOQYRx1Nj5YttQAuXc+VpGfztyRK90R74WkE/N6miOoDHcvc+7AeW4zyWsh +ZfLXCbngI45TVhUr3ljxWs1Ykc8d4Xt3JrtcUzltbc6nWS0vstcUmxTLTRURn3SX +4wIDAQAB +-----END PUBLIC KEY----- +'; + /** * Tear down. */ @@ -113,11 +172,11 @@ public function test_signature_legacy() { } /** - * Test signature consistancy. + * Test signature consistency. * * @covers ::get_keypair_for */ - public function test_signature_consistancy() { + public function test_signature_consistency() { // Check user. $user = Actors::get_by_id( 1 ); @@ -148,7 +207,7 @@ public function test_signature_consistancy() { * * @covers ::get_keypair_for */ - public function test_signature_consistancy2() { + public function test_signature_consistency2() { $user = Actors::get_by_id( 1 ); $key_pair = Signature::get_keypair_for( $user->get__id() ); @@ -168,4 +227,123 @@ public function test_signature_consistancy2() { $this->assertEquals( $key_pair['public_key'], $public_key ); $this->assertEquals( $key_pair['private_key'], $private_key ); } + + /** + * Test handling of different public key formats. + * + * @covers ::get_remote_key + */ + public function test_key_format_handling() { + $expected = '-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtAVnFFbWG+6NBFKhMZdt +59Gx2/vKxWxbxOAYyi/ypZ/9aDY6C/UBRei8SqnhKcKXQaiSwme/wpqgCdkrf53H +85OioBitCEvKNA6uDxkCtcdgtQ3X55QDXmatWd32ln6elRmKG45U9R386j82OHzf +f8Ju65QxGL1LlyCKQ/XFx/pgvblF3cGjshk0dhNcyGAztODN5HFp9Qzf9d7+gi+x +dKeGNhXBAulXoaDzx8FvLEXNfPJb3jUM1Ug0STFsiICcf7VxmQow6N6d0+HtWxrd +tjUBdXrPxz998Ns/cu9jjg06d+XV3TcSU+AOldmGLJuB/AWV/+F9c9DlczqmnXqd +1QIDAQAB +-----END PUBLIC KEY----- +'; + + \add_filter( 'pre_get_remote_metadata_by_actor', array( $this, 'pre_get_remote_metadata_by_actor' ), 10, 2 ); + + // X.509 key should remain unchanged. + $result = Signature::get_remote_key( 'https://example.com/author/x509' ); + $key_resource = \openssl_pkey_get_details( $result ); + $this->assertNotFalse( $key_resource ); + $this->assertSame( $this->x509_key, $key_resource['key'] ); + + // PKCS#1 key should be converted to X.509 format. + $result = Signature::get_remote_key( 'https://example.com/author/pkcs1' ); + $key_resource = \openssl_pkey_get_details( $result ); + $this->assertNotFalse( $key_resource ); + $this->assertSame( $expected, $key_resource['key'] ); + + // EC key should be handled correctly. + $result = Signature::get_remote_key( 'https://example.com/author/ec' ); + $key_resource = \openssl_pkey_get_details( $result ); + $this->assertNotFalse( $key_resource ); + + // PKCS#8 key should be handled correctly. + $result = Signature::get_remote_key( 'https://example.com/author/pkcs8' ); + $key_resource = \openssl_pkey_get_details( $result ); + $this->assertNotFalse( $key_resource ); + + // Test with invalid key. + $result = Signature::get_remote_key( 'https://example.com/author/invalid' ); + $this->assertWPError( $result ); + + \remove_filter( 'pre_get_remote_metadata_by_actor', array( $this, 'pre_get_remote_metadata_by_actor' ) ); + } + + /** + * Pre get remote metadata by actor. + * + * @param mixed $value The value. + * @param string $url The URL. + * @return array|\WP_Error + */ + public function pre_get_remote_metadata_by_actor( $value, $url ) { + if ( 'https://example.com/author/x509' === $url ) { + return array( + 'name' => 'Test Actor', + 'url' => 'https://example.com/author/x509', + 'publicKey' => array( + 'id' => 'https://example.com/author#main-key', + 'owner' => 'https://example.com/author', + 'publicKeyPem' => $this->x509_key, + ), + ); + } + + if ( 'https://example.com/author/pkcs1' === $url ) { + return array( + 'name' => 'Test Actor', + 'url' => 'https://example.com/author/pkcs1', + 'publicKey' => array( + 'id' => 'https://example.com/author#main-key', + 'owner' => 'https://example.com/author', + 'publicKeyPem' => $this->pkcs1_key, + ), + ); + } + + if ( 'https://example.com/author/ec' === $url ) { + return array( + 'name' => 'Test Actor', + 'url' => 'https://example.com/author/ec', + 'publicKey' => array( + 'id' => 'https://example.com/author#main-key', + 'owner' => 'https://example.com/author', + 'publicKeyPem' => $this->ec_key, + ), + ); + } + + if ( 'https://example.com/author/pkcs8' === $url ) { + return array( + 'name' => 'Test Actor', + 'url' => 'https://example.com/author/pkcs8', + 'publicKey' => array( + 'id' => 'https://example.com/author#main-key', + 'owner' => 'https://example.com/author', + 'publicKeyPem' => $this->pkcs8_key, + ), + ); + } + + if ( 'https://example.com/author/invalid' === $url ) { + return array( + 'name' => 'Test Actor', + 'url' => 'https://example.com/author/invalid', + 'publicKey' => array( + 'id' => 'https://example.com/author#main-key', + 'owner' => 'https://example.com/author', + 'publicKeyPem' => 'INVALID KEY DATA', + ), + ); + } + + return new \WP_Error( 'invalid_url', $url ); + } }