' . sprintf( __( 'Attention! The %1$s (%2$s) constant is missing. Germanized uses a derived key based on the LOGGED_IN_KEY constant instead. This constant might change under certain circumstances. To prevent data losses, please insert the following snippet within your wp-config.php file:', 'woocommerce-germanized' ), $constant, $explanation, 'https://wordpress.org/support/article/editing-wp-config-php/' ) . '

'; $notice .= '

' . "define( '" . $constant . "', '" . $new_key . "' );

"; } return $notice; } /** * @return string|WP_Error */ public static function get_random_encryption_key() { try { return sodium_bin2hex( sodium_crypto_secretbox_keygen() ); } catch ( \Exception $e ) { return self::log_error( new WP_Error( 'encrypt-key-error', sprintf( 'Error while creating new encryption key: %s', wc_print_r( $e, true ) ) ) ); } } public static function get_encryption_key_constant( $encryption_type = '' ) { return apply_filters( 'woocommerce_gzd_encryption_key_constant', 'WC_GZD_ENCRYPTION_KEY', $encryption_type ); } /** * @param string $salt * @param string $encryption_type * * @return array|WP_Error */ public static function get_encryption_key_data( $salt = '', $encryption_type = '', $force_fallback = false ) { $result = array( 'key' => '', 'salt' => ! empty( $salt ) ? $salt : random_bytes( SODIUM_CRYPTO_PWHASH_SALTBYTES ), ); if ( self::has_valid_encryption_key( $encryption_type ) && ! $force_fallback ) { $result['key'] = sodium_hex2bin( constant( self::get_encryption_key_constant( $encryption_type ) ) ); } else { try { $pw = LOGGED_IN_KEY; $result['key'] = sodium_crypto_pwhash( SODIUM_CRYPTO_SECRETBOX_KEYBYTES, $pw, $result['salt'], SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE ); self::memzero( $pw ); } catch ( \Exception $e ) { return self::log_error( new WP_Error( 'encrypt-key-error', sprintf( 'Error while retrieving encryption key: %s', wc_print_r( $e, true ) ) ) ); } } return $result; } /** * The sodium_compat does not support zeroing memory and throws an exception: * https://github.com/paragonie/sodium_compat/blob/master/src/Compat.php#L3301 * * @param $pw */ protected static function memzero( $pw ) { try { sodium_memzero( $pw ); } catch ( \SodiumException $e ) { return; } } public static function has_valid_encryption_key( $encryption_type = '' ) { return defined( self::get_encryption_key_constant( $encryption_type ) ); } /** * @param $message * @param string $encryption_type * * @return string|WP_Error */ public static function encrypt( $message, $encryption_type = '' ) { try { $key_data = self::get_encryption_key_data( $encryption_type ); $nonce = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); if ( is_wp_error( $key_data ) ) { return $key_data; } return base64_encode( $key_data['salt'] . $nonce . sodium_crypto_secretbox( $message, $nonce, $key_data['key'] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode } catch ( \Exception $e ) { return self::log_error( new WP_Error( 'encrypt-error', sprintf( 'Error while encrypting data: %s', wc_print_r( $e, true ) ) ) ); } } /** * Decrypts a message of a certain type. * * @param $cipher * @param string $encryption_type * * @return WP_Error|mixed */ public static function decrypt( $cipher, $encryption_type = '' ) { if ( is_null( $cipher ) ) { return null; } $decoded = base64_decode( $cipher ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $error = new \WP_Error(); if ( false === $decoded ) { $error->add( 'decrypt-decode', 'Error while decoding the encrypted message.' ); return self::log_error( $error ); } try { if ( mb_strlen( $decoded, '8bit' ) < ( SODIUM_CRYPTO_PWHASH_SALTBYTES + SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES ) ) { $error->add( 'decrypt-truncate', 'Message was truncated.' ); return self::log_error( $error ); } $salt = mb_substr( $decoded, 0, SODIUM_CRYPTO_PWHASH_SALTBYTES, '8bit' ); $key_data = self::get_encryption_key_data( $salt, $encryption_type ); if ( is_wp_error( $key_data ) ) { return $key_data; } $key = $key_data['key']; $nonce = mb_substr( $decoded, SODIUM_CRYPTO_PWHASH_SALTBYTES, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit' ); $ciphertext = mb_substr( $decoded, SODIUM_CRYPTO_PWHASH_SALTBYTES + SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit' ); $plain = sodium_crypto_secretbox_open( $ciphertext, $nonce, $key ); /** * Try the fallback key. */ if ( false === $plain ) { $key_data = self::get_encryption_key_data( $salt, $encryption_type, true ); if ( is_wp_error( $key_data ) ) { return $key_data; } $key = $key_data['key']; $plain = sodium_crypto_secretbox_open( $ciphertext, $nonce, $key ); } if ( false === $plain ) { $error->add( 'decrypt', 'Message could not be decrypted.' ); return self::log_error( $error ); } self::memzero( $ciphertext ); self::memzero( $key ); return $plain; } catch ( \Exception $e ) { $error->add( 'decrypt-error', sprintf( 'Error while decrypting data: %s', wc_print_r( $e, true ) ) ); return self::log_error( $error ); } } /** * Checks whether this installation supports auto-inserting the encryption key to the wp-config.php file. * * @return bool */ public static function supports_auto_insert() { $supports = false; /** * Determine the path to wp-config.php to check whether auto-inserting the encryption key is possible or not. * Plugin review team: This path is NOT used to include the wp-config.php file. */ $path_to_wp_config = ABSPATH . '/wp-config.php'; // phpcs:ignore if ( @file_exists( $path_to_wp_config ) && @is_writeable( $path_to_wp_config ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $supports = true; } return $supports; } /** * Try to insert the encryption key (e.g. for securely storing API credentials) in the wp-config.php file. * * @param $encryption_type * * @return bool */ public static function maybe_insert_missing_key( $encryption_type = '' ) { $updated = false; if ( ! self::has_valid_encryption_key( $encryption_type ) ) { $constant = self::get_encryption_key_constant( $encryption_type ); $key_value = self::get_random_encryption_key(); if ( is_wp_error( $key_value ) ) { return false; } /** * Determine the path to wp-config.php to auto-insert the encryption key. * Plugin review team: This path is NOT used to include the wp-config.php file. */ $path_to_wp_config = ABSPATH . '/wp-config.php'; // phpcs:ignore if ( @file_exists( $path_to_wp_config ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged error_reporting( 0 ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting,WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting // Load file data $config_file = file( $path_to_wp_config ); $last_define_line = false; $stop_line = false; $path_line = false; $to_insert = "define( '" . $constant . "', '" . addcslashes( $key_value, "\\'" ) . "' );\r\n"; $exists = false; if ( ! $config_file ) { return false; } foreach ( $config_file as $line_num => $line ) { if ( strstr( $line, 'stop editing! Happy publishing' ) ) { $stop_line = $line_num; continue; } if ( strstr( $line, $constant ) ) { $exists = true; break; } if ( strstr( $line, 'Absolute path to the WordPress directory' ) ) { $path_line = $line_num; } if ( ! preg_match( '/^define\(\s*\'([A-Z_]+)\',([ ]+)/', $line, $match ) ) { continue; } $last_define_line = $line_num; } if ( ! $exists ) { if ( $stop_line ) { array_splice( $config_file, $stop_line, 0, $to_insert ); } elseif ( $path_line ) { array_splice( $config_file, $path_line, 0, $to_insert ); } elseif ( $last_define_line ) { array_splice( $config_file, $last_define_line + 1, 0, $to_insert ); } $handle = fopen( $path_to_wp_config, 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen if ( $handle ) { foreach ( $config_file as $line ) { fwrite( $handle, $line ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite } fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose $updated = true; } } } } return $updated; } /** * @param \WP_Error $error */ protected static function log_error( $error ) { update_option( 'woocommerce_gzd_has_encryption_error', 'yes' ); if ( apply_filters( 'woocommerce_gzd_encryption_enable_logging', wc_gzd_is_extended_debug_mode_enabled() ) && ( $logger = wc_get_logger() ) ) { foreach ( $error->get_error_messages() as $message ) { $logger->error( $message, array( 'source' => apply_filters( 'woocommerce_gzd_encryption_log_context', 'wc-gzd-encryption' ) ) ); } } return $error; } public static function has_errors() { return 'yes' === get_option( 'woocommerce_gzd_has_encryption_error', 'no' ); } } }