Skip to content
101 changes: 101 additions & 0 deletions phpunit/functional/GLPIKeyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -538,4 +538,105 @@ public function testIsConfigSecured()
$this->assertFalse($is_myplugin_href_secured);
$this->assertFalse($is_someplugin_conf_secured);
}

public function testGetKeyFileReadErrorsWithMissingFile(): void
{
// arrange : create directory structure without key file
vfsStream::setup('glpi', null, ['config' => []]);
$glpikey = new \GLPIKey(vfsStream::url('glpi/config'));

// act
$errors = $glpikey->getKeyFileReadErrors();

// assert
$this->assertStringContainsString('create a security key', implode(" ", $errors));
}

public function testGetKeyFileReadErrorsWithUnreadableFile(): void
{
// arrange : create unreadable key file
$structure = vfsStream::setup('glpi', null, ['config' => ['glpicrypt.key' => 'unreadable file']]);
$structure->getChild('config/glpicrypt.key')->chmod(0222);

$glpikey = new \GLPIKey(vfsStream::url('glpi/config'));

// act
$errors = $glpikey->getKeyFileReadErrors();

// assert
$this->assertStringContainsString('Unable to get security key file contents', implode(" ", $errors));
}

public function testGetKeyFileReadErrorsWithInvalidKey(): void
{
// arrange : key file exists but has invalid contents/length
vfsStream::setup('glpi', null, ['config' => ['glpicrypt.key' => 'not a valid key']]);
$glpikey = new \GLPIKey(vfsStream::url('glpi/config'));

// act
$errors = $glpikey->getKeyFileReadErrors();

// assert
$this->assertStringContainsString('Invalid security key file contents', implode(" ", $errors));
}

public function testGetKeyFileReadErrorsWithValidKey(): void
{
// arrange : key file exists and is valid => no errors
$valid_key = 'abcdefghijklmnopqrstuvwxyz123456';
vfsStream::setup('glpi', null, ['config' => ['glpicrypt.key' => $valid_key]]);

$glpikey = new \GLPIKey(vfsStream::url('glpi/config'));

// act
$errors = $glpikey->getKeyFileReadErrors();

// assert
$this->assertEmpty($errors);
}

public function testHasKeyFileReadErrorsWithMissingFile(): void
{
// arrange : create directory structure without key file
vfsStream::setup('glpi', null, ['config' => []]);
$glpikey = new \GLPIKey(vfsStream::url('glpi/config'));

// act + assert
$this->assertTrue($glpikey->hasReadErrors());
}

public function testHasKeyFileReadErrorsWithUnreadableFile(): void
{
// arrange : create unreadable key file
$structure = vfsStream::setup('glpi', null, ['config' => ['glpicrypt.key' => 'unreadable file']]);
$structure->getChild('config/glpicrypt.key')->chmod(0222);

$glpikey = new \GLPIKey(vfsStream::url('glpi/config'));

// act + assert
$this->assertTrue($glpikey->hasReadErrors());

}

public function testHasKeyFileReadErrorsWithInvalidKey(): void
{
// arrange : key file exists but has invalid contents/length
vfsStream::setup('glpi', null, ['config' => ['glpicrypt.key' => 'not a valid key']]);
$glpikey = new \GLPIKey(vfsStream::url('glpi/config'));

// act + assert
$this->assertTrue($glpikey->hasReadErrors());
}

public function testHasKeyFileReadErrorsWithValidKey(): void
{
// arrange : key file exists and is valid => no errors
$valid_key = 'abcdefghijklmnopqrstuvwxyz123456';
vfsStream::setup('glpi', null, ['config' => ['glpicrypt.key' => $valid_key]]);

$glpikey = new \GLPIKey(vfsStream::url('glpi/config'));

// act + assert
$this->assertFalse($glpikey->hasReadErrors());
}
}
16 changes: 16 additions & 0 deletions src/AuthLDAP.php
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,15 @@ public function showForm($ID, array $options = [])
if (!Config::canUpdate()) {
return false;
}

// warning and no form if can't read keyfile
$glpi_encryption_key = new GLPIKey();
if ($glpi_encryption_key->hasReadErrors()) {
$glpi_encryption_key->showReadErrors();

return false;
}

if (empty($ID)) {
$this->getEmpty();
if (isset($options['preconfig'])) {
Expand Down Expand Up @@ -593,6 +602,13 @@ public function showForm($ID, array $options = [])
*/
public function showFormAdvancedConfig()
{
// warning and no form if can't read keyfile
$glpi_encryption_key = new GLPIKey();
if ($glpi_encryption_key->hasReadErrors()) {
$glpi_encryption_key->showReadErrors();

return;
}

$ID = $this->getField('id');
$hidden = '';
Expand Down
3 changes: 3 additions & 0 deletions src/Central.php
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,9 @@ private static function getMessages(): array
. sprintf(__('Run the "%1$s" command to migrate them.'), 'php bin/console migration:unsigned_keys');
}

// encrypt/decrypt key problems
$messages['errors'] = (new GLPIKey())->getKeyFileReadErrors();

$security_requirements = [
new PhpSupportedVersion(),
new SafeDocumentRoot(),
Expand Down
45 changes: 45 additions & 0 deletions src/GLPIKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,51 @@ public function keyExists()
return file_exists($this->keyfile);
}

/**
* Check if key is valid
*
* @return string[]
*/
public function getKeyFileReadErrors(): array
{
$errors = [];
if (!file_exists($this->keyfile)) {
$errors[] = __s('You must create a security key, use `./bin/console security:change_key` command.');

return $errors; // early return, as, if file does not exist, no need to check further
}
if (false === ($key = @file_get_contents($this->keyfile))) {
$errors[] = __s("Unable to get security key file contents. Fix file permissions of $this->keyfile.");

return $errors; // early return, as, if file does not exist, no need to check further
}
if (strlen($key) !== SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES) {
$errors[] = __s('Invalid security key file contents. Regenerate a key using `./bin/console security:change_key` command.');
}

return $errors;
}

public function hasReadErrors(): bool
{
return !empty($this->getKeyFileReadErrors());
}

public function showReadErrors(): void
{
$glpi_key_read_errors = $this->getKeyFileReadErrors();
if (!empty($glpi_key_read_errors)) {
\Glpi\Application\View\TemplateRenderer::getInstance()->display(
'/central/messages.html.twig',
[
'messages' => [
'errors' => $glpi_key_read_errors,
],
]
);
}
}

/**
* Get GLPI security key used for decryptable passwords
*
Expand Down
13 changes: 10 additions & 3 deletions src/GLPINetwork.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0)
{
if ($item->getType() == 'Config') {
$glpiNetwork = new self();
$glpiNetwork->showForConfig();
self::showForConfig();
}
return true;
}
Expand All @@ -58,9 +57,17 @@ public static function showForConfig()
return;
}

$registration_key = self::getRegistrationKey();
// warning and no form if can't read keyfile
$glpi_encryption_key = new GLPIKey();
if ($glpi_encryption_key->hasReadErrors()) {
$glpi_encryption_key->showReadErrors();

return;
}

$canedit = Config::canUpdate();
$registration_key = self::getRegistrationKey();

if ($canedit) {
echo "<form name='form' action=\"" . Toolbox::getItemTypeFormURL(Config::class) . "\" method='post'>";
}
Expand Down
8 changes: 8 additions & 0 deletions src/MailCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,14 @@ public function showForm($ID, array $options = [])
/** @var array $CFG_GLPI */
global $CFG_GLPI;

// warning and no form if can't read keyfile
$glpi_encryption_key = new GLPIKey();
if ($glpi_encryption_key->hasReadErrors()) {
$glpi_encryption_key->showReadErrors();

return false;
}

$this->initForm($ID, $options);
$options['colspan'] = 1;
$this->showFormHeader($options);
Expand Down
10 changes: 10 additions & 0 deletions src/NotificationMailingSetting.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ public function showFormConfig($options = [])
/** @var array $CFG_GLPI */
global $CFG_GLPI;

// warning and no form if can't read keyfile
// always display no matter what $options['display']
// see comment at the end of this function
$glpi_encryption_key = new GLPIKey();
if ($glpi_encryption_key->hasReadErrors()) {
$glpi_encryption_key->showReadErrors();

return;
}

if (!isset($options['display'])) {
$options['display'] = true;
}
Expand Down
9 changes: 9 additions & 0 deletions src/SNMPCredential.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ public function defineTabs($options = [])

public function showForm($ID, array $options = [])
{
// warning and no form if can't read keyfile,
// only version 3 is impacted but it's better to always show the warning & forbid form display
$glpi_encryption_key = new GLPIKey();
if ($glpi_encryption_key->hasReadErrors()) {
$glpi_encryption_key->showReadErrors();

return false;
}

$this->initForm($ID, $options);
TemplateRenderer::getInstance()->display('components/form/snmpcredential.html.twig', [
'item' => $this,
Expand Down