verify_file_signature()WP 5.2.0

Проверяет файл по одной или нескольким цифровым подписям Ed25519.

Вычисляет SHA-384-хеш файла и проверяет подписи с помощью доверенных ключей WordPress, полученных через wp_trusted_keys(). Проверка считается успешной, если хотя бы одна подпись подходит хотя бы к одному ключу.

Подписи и доверенные ключи должны быть закодированы в Base64. Подписи и ключи неправильной длины пропускаются.

Функция проверяет подписи только по встроенным доверенным ключам WordPress. Передать собственный открытый ключ нельзя.

Для проверки необходимы алгоритм SHA-384 и нативное расширение Sodium либо достаточно быстрый полифил Sodium_Compat.

Работает на основе: wp_trusted_keys()
1 раз — 0.000417 сек (быстро) | 50000 раз — 1.72 сек (быстро) | PHP 8.2.28, WP 7.0.2

Хуков нет.

Возвращает

true|false|WP_Error.

  • true - подпись подтверждена.
  • WP_Error - проверка невозможна, подпись отсутствует или не прошла проверку.
  • false - указан в официальном описании для случая, когда проверка не выполнялась, но актуальный код функции не возвращает это значение.

Использование

verify_file_signature( $filename, $signatures, $filename_for_errors );
$filename(string) (обязательный)
Путь к проверяемому файлу.
$signatures(string|array) (обязательный)
Одна подпись или массив подписей Ed25519 в формате Base64.
$filename_for_errors(string|false)

Понятное название файла для сообщений об ошибках. Не влияет на путь к проверяемому файлу. При false используется имя файла из $filename.

По умолчанию: false

Примеры

0

#1 Проверка подписи файла

$result = verify_file_signature(
	WP_CONTENT_DIR . '/upgrade/wordpress.zip',
	$signature,
	'wordpress.zip'
);

if ( is_wp_error( $result ) ) {
	wp_die( esc_html( $result->get_error_message() ) );
}

Список изменений

С версии 5.2.0 Введена.

Код verify_file_signature() WP 7.0.2

function verify_file_signature( $filename, $signatures, $filename_for_errors = false ) {
	if ( ! $filename_for_errors ) {
		$filename_for_errors = wp_basename( $filename );
	}

	// Check we can process signatures.
	if ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) || ! in_array( 'sha384', array_map( 'strtolower', hash_algos() ), true ) ) {
		return new WP_Error(
			'signature_verification_unsupported',
			sprintf(
				/* translators: %s: The filename of the package. */
				__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
				'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
			),
			( ! function_exists( 'sodium_crypto_sign_verify_detached' ) ? 'sodium_crypto_sign_verify_detached' : 'sha384' )
		);
	}

	// 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 utilizes during signing verifications.
			 */
			// phpcs:disable WordPress.NamingConventions.ValidVariableName
			$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;
			// phpcs:enable
		}

		/*
		 * 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: %s: The filename of the package. */
					__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
					'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
				),
				array(
					'php'                => PHP_VERSION,
					'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',
			sprintf(
				/* translators: %s: The filename of the package. */
				__( 'The authenticity of %s could not be verified as no signature was found.' ),
				'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
			),
			array(
				'filename' => $filename_for_errors,
			)
		);
	}

	$trusted_keys = wp_trusted_keys();
	$file_hash    = hash_file( 'sha384', $filename, true );

	mbstring_binary_safe_encoding();

	$skipped_key       = 0;
	$skipped_signature = 0;

	foreach ( (array) $signatures as $signature ) {
		$signature_raw = base64_decode( $signature );

		// Ensure only valid-length signatures are considered.
		if ( SODIUM_CRYPTO_SIGN_BYTES !== strlen( $signature_raw ) ) {
			++$skipped_signature;
			continue;
		}

		foreach ( (array) $trusted_keys as $key ) {
			$key_raw = base64_decode( $key );

			// Only pass valid public keys through.
			if ( SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES !== strlen( $key_raw ) ) {
				++$skipped_key;
				continue;
			}

			if ( sodium_crypto_sign_verify_detached( $signature_raw, $file_hash, $key_raw ) ) {
				reset_mbstring_encoding();
				return true;
			}
		}
	}

	reset_mbstring_encoding();

	return new WP_Error(
		'signature_verification_failed',
		sprintf(
			/* translators: %s: The filename of the package. */
			__( 'The authenticity of %s could not be verified.' ),
			'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
		),
		// Error data helpful for debugging:
		array(
			'filename'    => $filename_for_errors,
			'keys'        => $trusted_keys,
			'signatures'  => $signatures,
			'hash'        => bin2hex( $file_hash ),
			'skipped_key' => $skipped_key,
			'skipped_sig' => $skipped_signature,
			'php'         => PHP_VERSION,
			'sodium'      => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
		)
	);
}