Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion src/wp-admin/setup-config.php
Original file line number Diff line number Diff line change
Expand Up @@ -390,13 +390,29 @@ function setup_config_display_header( $body_classes = array() ) {
}
}

$key = 0;
/*
* The Secrets API key is generated like the salts above, but written as base64-encoded
* random bytes, the form the Secrets API uses as raw key material.
*/
try {
$secrets_key = base64_encode( random_bytes( 32 ) );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other values fall back to wp_generate_password() if CSPRING isn't available. It might be worth doing so here too.

On the ticket I've suggested coordinating with meta to have WP_SECRETS_KEY added to the secrets API to allow this to fall back to the API if needs be.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 6c93419. Without a CSPRNG it now uses wp_generate_password( 64, true, true ), like the salts. With the fallback removed in #15258, a weaker key is better than none. I've replied on Trac about the API: it should be a separate endpoint, because people paste the /1.1/salt/ output to rotate their salts.

} catch ( Exception $ex ) {
$secrets_key = wp_generate_password( 64, true, true );
}

$key = 0;
$has_secrets_key = false;
foreach ( $config_file as $line_num => $line ) {
if ( str_starts_with( $line, '$table_prefix =' ) ) {
$config_file[ $line_num ] = '$table_prefix = \'' . addcslashes( $prefix, "\\'" ) . "';\r\n";
continue;
}

if ( isset( $nonce_salt_line ) && ! isset( $salts_end_line ) && '/**#@-*/' === trim( $line ) ) {
$salts_end_line = $line_num;
continue;
}

if ( ! preg_match( '/^define\(\s*\'([A-Z_]+)\',([ ]+)/', $line, $match ) ) {
continue;
}
Expand Down Expand Up @@ -426,10 +442,36 @@ function setup_config_display_header( $body_classes = array() ) {
case 'NONCE_SALT':
$config_file[ $line_num ] = "define( '" . $constant . "'," . $padding . "'" . $secret_keys[ $key++ ] . "' );\r\n";
break;
case 'WP_SECRETS_KEY':
$config_file[ $line_num ] = "define( '" . $constant . "'," . $padding . "'" . $secrets_key . "' );\r\n";
$has_secrets_key = true;
break;
}

if ( 'NONCE_SALT' === $constant ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little flakey if additional secrets are added to the sample config file.

I also think it a good idea to add this to the sample config file but let me know if there are reasons you don't wish to do this.

I suggest adding this above the /**#@-*/ but separated from the group by some line breaks, so the output becomes:

/**#@+
 * Authentication unique keys and salts.
 *
 * Change these to different unique phrases! You can generate these using
 * the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
 *
 * You can change these at any point in time to invalidate all existing cookies.
 * This will force all users to have to log in again.
 *
 * @since 2.6.0
 */
define( 'AUTH_KEY',         '...' );
define( 'SECURE_AUTH_KEY',  '...' );
define( 'LOGGED_IN_KEY',    '...' );
define( 'NONCE_KEY',        '...' );
define( 'AUTH_SALT',        '...' );
define( 'SECURE_AUTH_SALT', '...' );
define( 'LOGGED_IN_SALT',   '...' );
define( 'NONCE_SALT',       '...' );

/**
 * Encryption key for the Secrets API.
 *
 * Secrets stored by WordPress and plugins are encrypted with this key.
 * Unlike the keys and salts above, changing it makes every stored secret unreadable.
 *
 * @since 7.2.0
 */
define( 'WP_SECRETS_KEY', '...' );

/**#@-*/

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The sample now defines WP_SECRETS_KEY with the placeholder, and the loop that fills in the salts replaces it, so constants added to the sample later don't matter. A sample without the line still gets it added after the salts.

I put it after /**#@-*/ rather than inside the group. phpDocumentor applies the /**#@+ docblock to everything up to /**#@-*/, and that docblock says these values can be changed at any time, which is exactly what this key must not be. I'm happy to move it inside if you'd rather keep the keys together.

$nonce_salt_line = $line_num;
}
}
unset( $line );

// A custom or older localized wp-config-sample.php may not define the key: add it after the salts.
if ( ! $has_secrets_key && isset( $nonce_salt_line ) ) {
$secrets_key_lines = array(
"\r\n",
"/**\r\n",
" * Encryption key for the Secrets API.\r\n",
" *\r\n",
" * Secrets stored by WordPress and plugins are encrypted with this key.\r\n",
" * Unlike the keys and salts above, changing it makes every stored secret unreadable.\r\n",
" *\r\n",
" * @since 7.2.0\r\n",
" */\r\n",
"define( 'WP_SECRETS_KEY', '" . $secrets_key . "' );\r\n",
);

array_splice( $config_file, ( $salts_end_line ?? $nonce_salt_line ) + 1, 0, $secrets_key_lines );
}

if ( ! is_writable( ABSPATH ) ) :
setup_config_display_header();
?>
Expand Down
11 changes: 11 additions & 0 deletions wp-config-sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@

/**#@-*/

/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does phpDocumentor allow for multiple placeholders? If so, it might be useful but it's not a big problem if it doesn't.

Suggested change
/**
/**#@SK+

* Encryption key for the Secrets API.
*
* Secrets stored by WordPress and plugins are encrypted with this key. Change it to
* a unique phrase, different from the keys and salts above. Unlike them, it must not
* be changed once secrets are stored: that makes every stored secret unreadable.
*
* @since 7.2.0
*/
define( 'WP_SECRETS_KEY', 'put your unique phrase here' );

/**
* WordPress database table prefix.
*
Expand Down
Loading