diff --git a/src/wp-admin/includes/file.php b/src/wp-admin/includes/file.php index cd3551bfad..538ec2e1ca 100644 --- a/src/wp-admin/includes/file.php +++ b/src/wp-admin/includes/file.php @@ -1199,6 +1199,39 @@ function verify_file_signature( $filename, $signatures, $filename_for_errors = f } + // Verify runtime speed of Sodium_Compat is acceptable. + if ( ! extension_loaded( 'sodium' ) && ! ParagonIE_Sodium_Compat::polyfill_is_fast() ) { + $sodium_compat_is_fast = false; + + // Allow for an old version of Sodium_Compat being loaded before the bundled WordPress one. + if ( method_exists( 'ParagonIE_Sodium_Compat', 'runtime_speed_test' ) ) { + // Run `ParagonIE_Sodium_Compat::runtime_speed_test()` in optimized integer mode, as that's what WordPress utilises during signing verifications. + $old_fastMult = ParagonIE_Sodium_Compat::$fastMult; + ParagonIE_Sodium_Compat::$fastMult = true; + $sodium_compat_is_fast = ParagonIE_Sodium_Compat::runtime_speed_test( 100, 10 ); + ParagonIE_Sodium_Compat::$fastMult = $old_fastMult; + } + + // This cannot be performed in a reasonable amount of time + // https://github.com/paragonie/sodium_compat#help-sodium_compat-is-slow-how-can-i-make-it-fast + if ( ! $sodium_compat_is_fast ) { + return new WP_Error( + 'signature_verification_unsupported', + sprintf( + /* translators: 1: The filename of the package. */ + __( 'The authenticity of %1$s could not be verified as signature verification is unavailable on this system.' ), + '' . esc_html( $filename_for_errors ) . '' + ), + array( + 'php' => phpversion(), + 'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ), + 'polyfill_is_fast' => false, + 'max_execution_time' => ini_get( 'max_execution_time' ), + ) + ); + } + } + if ( ! $signatures ) { return new WP_Error( 'signature_verification_no_signature', diff --git a/tests/phpunit/tests/file.php b/tests/phpunit/tests/file.php index 25a417e512..d36824e4df 100644 --- a/tests/phpunit/tests/file.php +++ b/tests/phpunit/tests/file.php @@ -183,4 +183,63 @@ class Tests_File extends WP_UnitTestCase { ); } + /** + * @ticket 47186 + */ + function test_file_signature_functions_as_expected() { + $file = wp_tempnam(); + file_put_contents( $file, 'WordPress' ); + + // The signature of 'WordPress' after SHA384 hashing, for verification against the key within self::filter_trust_plus85Tq_key(). + $expected_signature = 'PmNv0b1ziwJAsVhjdpjd4+PQZidZWSlBm5b+GbbwE9m9HVKDFhEyvyRTHkRYOLypB8P2YvbW7CoOMZqGh8mEAA=='; + + add_filter( 'wp_trusted_keys', array( $this, 'filter_trust_plus85Tq_key' ) ); + + // Measure how long the call takes. + $timer_start = microtime( 1 ); + $verify = verify_file_signature( $file, $expected_signature, 'WordPress' ); + $timer_end = microtime( 1 ); + $time_taken = ( $timer_end - $timer_start ); + + unlink( $file ); + remove_filter( 'wp_trusted_keys', array( $this, 'filter_trust_plus85Tq_key' ) ); + + // verify_file_signature() should intentionally never take more than 10s to run. + $this->assertLessThan( 10, $time_taken, 'verify_file_signature() took longer than 10 seconds.' ); + + // Check to see if the system parameters prevent signature verifications. + if ( is_wp_error( $verify ) && 'signature_verification_unsupported' == $verify->get_error_code() ) { + $this->markTestSkipped( 'This system does not support Signature Verification.' ); + } + + $this->assertNotWPError( $verify ); + $this->assertTrue( $verify ); + } + + /** + * @ticket 47186 + */ + function test_file_signature_expected_failure() { + $file = wp_tempnam(); + file_put_contents( $file, 'WordPress' ); + + // Test an invalid signature. + $expected_signature = base64_encode( str_repeat( 'A', SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES ) ); + $verify = verify_file_signature( $file, $expected_signature, 'WordPress' ); + unlink( $file ); + + if ( is_wp_error( $verify ) && 'signature_verification_unsupported' == $verify->get_error_code() ) { + $this->markTestSkipped( 'This system does not support Signature Verification.' ); + } + + $this->assertWPError( $verify ); + $this->assertEquals( 'signature_verification_failed', $verify->get_error_code() ); + } + + function filter_trust_plus85Tq_key( $keys ) { + // A static once-off key used to verify verify_file_signature() works as expected. + $keys[] = '+85TqMhxQVAYVW4BSCVkJQvZH4q7z8I9lePbvngvf7A='; + + return $keys; + } }