From 8846c128bdad9496186809d077577b0ac3f29da7 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Sun, 14 Feb 2021 20:04:57 -0800 Subject: [PATCH] Crypto - Define CIVICRM_SIGN_KEYS as way to register signing keys --- CRM/Upgrade/Incremental/php/FiveThirtySix.php | 9 ++++++ Civi/Crypto/CryptoRegistry.php | 7 +++++ .../GenerateSignKey.civi-setup.php | 31 +++++++++++++++++++ .../InstallSettingsFile.civi-setup.php | 1 + setup/src/Setup/Model.php | 7 +++++ .../CRM/common/civicrm.settings.php.template | 21 +++++++++++++ 6 files changed, 76 insertions(+) create mode 100644 setup/plugins/installFiles/GenerateSignKey.civi-setup.php diff --git a/CRM/Upgrade/Incremental/php/FiveThirtySix.php b/CRM/Upgrade/Incremental/php/FiveThirtySix.php index e006058e6f..61494ada18 100644 --- a/CRM/Upgrade/Incremental/php/FiveThirtySix.php +++ b/CRM/Upgrade/Incremental/php/FiveThirtySix.php @@ -29,6 +29,15 @@ class CRM_Upgrade_Incremental_php_FiveThirtySix extends CRM_Upgrade_Incremental_ // if ($rev == '5.12.34') { // $preUpgradeMessage .= '

' . ts('A new permission, "%1", has been added. This permission is now used to control access to the Manage Tags screen.', array(1 => ts('manage tags'))) . '

'; // } + if ($rev === '5.36.alpha1') { + if (empty(CRM_Utils_Constant::value('CIVICRM_SIGN_KEYS'))) { + // NOTE: We don't re-encrypt automatically because the old "civicrm.settings.php" lacks a good key, and we don't keep the old encryption because the format is ambiguous. + // The admin may forget to re-enable. That's OK -- this only affects 1 field, this is a secondary defense, and (in the future) we can remind the admin via status-checks. + $preUpgradeMessage .= '

' . ts('CiviCRM v5.36 introduces a new configuration option to support digital signatures. You may setup CIVICRM_SIGN_KEYS before or after upgrading. The option is not critical in v5.36, but it may be required for extensions or future upgrades.', [ + 1 => 'https://docs.civicrm.org/sysadmin/en/latest/upgrade/version-specific/#sign-key', + ]) . '

'; + } + } } /** diff --git a/Civi/Crypto/CryptoRegistry.php b/Civi/Crypto/CryptoRegistry.php index a26bd4359c..393e234b97 100644 --- a/Civi/Crypto/CryptoRegistry.php +++ b/Civi/Crypto/CryptoRegistry.php @@ -78,6 +78,13 @@ class CryptoRegistry { } } + if (defined('CIVICRM_SIGN_KEYS') && CIVICRM_SIGN_KEYS !== '') { + foreach (explode(' ', CIVICRM_SIGN_KEYS) as $n => $keyExpr) { + $key = ['tags' => ['SIGN'], 'weight' => $n]; + $registry->addSymmetricKey($registry->parseKey($keyExpr) + $key); + } + } + //if (isset($_COOKIE['CIVICRM_FORM_KEY'])) { // $crypto->addSymmetricKey([ // 'key' => base64_decode($_COOKIE['CIVICRM_FORM_KEY']), diff --git a/setup/plugins/installFiles/GenerateSignKey.civi-setup.php b/setup/plugins/installFiles/GenerateSignKey.civi-setup.php new file mode 100644 index 0000000000..6847076aaf --- /dev/null +++ b/setup/plugins/installFiles/GenerateSignKey.civi-setup.php @@ -0,0 +1,31 @@ +addListener('civi.setup.installFiles', function (\Civi\Setup\Event\InstallFilesEvent $e) { + \Civi\Setup::log()->info(sprintf('[%s] Handle %s', basename(__FILE__), 'installFiles')); + + $toAlphanum = function($bits) { + return preg_replace(';[^a-zA-Z0-9];', '', base64_encode($bits)); + }; + + if (empty($e->getModel()->signKeys)) { + $e->getModel()->signKeys = ['jwt-hs256:hkdf-sha256:' . $toAlphanum(random_bytes(40))]; + // toAlpanum() occasionally loses a few bits of entropy, but random_bytes() has significant excess, so it's still more than ample for 256 bit hkdf. + } + + if (is_string($e->getModel()->signKeys)) { + $e->getModel()->signKeys = [$e->getModel()->signKeys]; + } + + \Civi\Setup::log()->info(sprintf('[%s] Done %s', basename(__FILE__), 'installFiles')); + + }, \Civi\Setup::PRIORITY_PREPARE); diff --git a/setup/plugins/installFiles/InstallSettingsFile.civi-setup.php b/setup/plugins/installFiles/InstallSettingsFile.civi-setup.php index 62c3c81419..447889a873 100644 --- a/setup/plugins/installFiles/InstallSettingsFile.civi-setup.php +++ b/setup/plugins/installFiles/InstallSettingsFile.civi-setup.php @@ -99,6 +99,7 @@ if (!defined('CIVI_SETUP')) { $params['CMSdbSSL'] = empty($m->cmsDb['ssl_params']) ? '' : addslashes('&' . http_build_query($m->cmsDb['ssl_params'], '', '&', PHP_QUERY_RFC3986)); $params['siteKey'] = addslashes($m->siteKey); $params['credKeys'] = addslashes(implode(' ', $m->credKeys)); + $params['signKeys'] = addslashes(implode(' ', $m->signKeys)); $extraSettings = array(); diff --git a/setup/src/Setup/Model.php b/setup/src/Setup/Model.php index 710c3dcbbf..3b740edc18 100644 --- a/setup/src/Setup/Model.php +++ b/setup/src/Setup/Model.php @@ -30,6 +30,8 @@ namespace Civi\Setup; * Ex: 'abcd1234ABCD9876'. * @property string[] $credKeys * Ex: ['::abcd1234ABCD9876']. + * @property string[] $signKeys + * Ex: ['jwt-hs256::abcd1234ABCD9876']. * @property string|NULL $lang * The language of the default dataset. * Ex: 'fr_FR'. @@ -116,6 +118,11 @@ class Model { 'name' => 'credKeys', 'type' => 'array', )); + $this->addField(array( + 'description' => 'Signing keys', + 'name' => 'signKeys', + 'type' => 'array', + )); $this->addField(array( 'description' => 'Load example data', 'name' => 'loadGenerated', diff --git a/templates/CRM/common/civicrm.settings.php.template b/templates/CRM/common/civicrm.settings.php.template index 1250b81ff0..29c97b69c5 100644 --- a/templates/CRM/common/civicrm.settings.php.template +++ b/templates/CRM/common/civicrm.settings.php.template @@ -324,6 +324,27 @@ if (!defined('CIVICRM_CRED_KEYS') ) { // Feel free to simplify post-install. } +/** + * The signing key is used to generate and verify shareable tokens. + * + * This is a space-delimited list of keys (ordered by priority). Put the preferred + * key first. Any old/deprecated keys may be listed after. + * + * Each key is in format "::", as in: + * + * Ex: define('CIVICRM_SIGN_KEYS', 'jwt-hs256:hkdf-sha256:RANDOM_1') + * Ex: define('CIVICRM_SIGN_KEYS', 'jwt-hs256::RANDOM_2 jwt-hs256::RANDOM_3') + * Ex: define('CIVICRM_SIGN_KEYS', 'jwt-hs256:b64:RANDOM_4 jwt-hs256:b64:RANDOM_5') + * + * If key-encoding is blank, it will default to "hkdf-sha256". + */ +if (!defined('CIVICRM_SIGN_KEYS') ) { + define( '_CIVICRM_SIGN_KEYS', '%%signKeys%%'); + define( 'CIVICRM_SIGN_KEYS', _CIVICRM_SIGN_KEYS === '%%' . 'signKeys' . '%%' ? '' : _CIVICRM_SIGN_KEYS ); + // Some old installers may not set a decent value, and this extra complexity is a failsafe. + // Feel free to simplify post-install. +} + /** * Enable this constant, if you want to send your email through the smarty * templating engine(allows you to do conditional and more complex logic) -- 2.25.1