Current File : /home/igihango/public_html/ecrire/src/Chiffrer/SpipCles.php |
<?php
/***************************************************************************\
* SPIP, Système de publication pour l'internet *
* *
* Copyright © avec tendresse depuis 2001 *
* Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James *
* *
* Ce programme est un logiciel libre distribué sous licence GNU/GPL. *
\***************************************************************************/
namespace Spip\Chiffrer;
/** Gestion des clés d’authentification / chiffrement de SPIP */
final class SpipCles {
private static array $instances = [];
private string $file = _DIR_ETC . 'cles.php';
private Cles $cles;
public static function instance(string $file = ''): self {
if (empty(self::$instances[$file])) {
self::$instances[$file] = new self($file);
}
return self::$instances[$file];
}
/**
* Retourne le secret du site (shorthand)
* @uses self::getSecretSite()
*/
public static function secret_du_site(): ?string {
return (self::instance())->getSecretSite();
}
private function __construct(string $file = '') {
if ($file) {
$this->file = $file;
}
$this->cles = new Cles($this->read());
}
/**
* Renvoyer le secret du site
*
* Le secret du site doit rester aussi secret que possible, et est eternel
* On ne doit pas l'exporter
*
* Le secret est partagé entre une clé disque et une clé bdd
*
* @return string
*/
public function getSecretSite(bool $autoInit = true): ?string {
$key = $this->getKey('secret_du_site', $autoInit);
$meta = $this->getMetaKey('secret_du_site', $autoInit);
// conserve la même longeur.
return $key ^ $meta;
}
/** Renvoyer le secret des authentifications */
public function getSecretAuth(bool $autoInit = false): ?string {
return $this->getKey('secret_des_auth', $autoInit);
}
public function save(): bool {
return ecrire_fichier_securise($this->file, $this->cles->toJson());
}
/**
* Fournir une sauvegarde chiffree des cles (a l'aide d'une autre clé, comme le pass d'un auteur)
*
* @param string $withKey Clé de chiffrage de la sauvegarde
* @return string Contenu de la sauvegarde chiffrée générée
*/
public function backup(
#[\SensitiveParameter]
string $withKey
): string {
if (count($this->cles)) {
return Chiffrement::chiffrer($this->cles->toJson(), $withKey);
}
return '';
}
/**
* Restaurer les cles manquantes depuis une sauvegarde chiffree des cles
* (si la sauvegarde est bien valide)
*
* @param string $backup Sauvegarde chiffrée (générée par backup())
* @param int $id_auteur
* @param string $pass
* @return void
*/
public function restore(
string $backup,
#[\SensitiveParameter]
string $password_clair,
#[\SensitiveParameter]
string $password_hash,
int $id_auteur
): bool {
if (empty($backup)) {
return false;
}
$sauvegarde = Chiffrement::dechiffrer($backup, $password_clair);
$json = json_decode($sauvegarde, true);
if (!$json) {
return false;
}
// cela semble une sauvegarde valide
$cles_potentielles = array_map('base64_decode', $json);
// il faut faire une double verif sur secret_des_auth
// pour s'assurer qu'elle permet bien de decrypter le pass de l'auteur qui fournit la sauvegarde
// et par extension tous les passwords
if (!empty($cles_potentielles['secret_des_auth'])) {
if (!Password::verifier($password_clair, $password_hash, $cles_potentielles['secret_des_auth'])) {
spip_log("Restauration de la cle `secret_des_auth` par id_auteur $id_auteur erronnee, on ignore", 'chiffrer' . _LOG_INFO_IMPORTANTE);
unset($cles_potentielles['secret_des_auth']);
}
}
// on merge les cles pour recuperer les cles manquantes
$restauration = false;
foreach ($cles_potentielles as $name => $key) {
if (!$this->cles->has($name)) {
$this->cles->set($name, $key);
spip_log("Restauration de la cle $name par id_auteur $id_auteur", 'chiffrer' . _LOG_INFO_IMPORTANTE);
$restauration = true;
}
}
return $restauration;
}
private function getKey(string $name, bool $autoInit): ?string {
if ($this->cles->has($name)) {
return $this->cles->get($name);
}
if ($autoInit) {
$this->cles->generate($name);
// si l'ecriture de fichier a bien marche on peut utiliser la cle
if ($this->save()) {
return $this->cles->get($name);
}
// sinon loger et annule la cle generee car il ne faut pas l'utiliser
spip_log('Echec ecriture du fichier cle ' . $this->file . " ; impossible de generer une cle $name", 'chiffrer' . _LOG_ERREUR);
$this->cles->delete($name);
}
return null;
}
private function getMetaKey(string $name, bool $autoInit = true): ?string {
if (!isset($GLOBALS['meta'][$name])) {
include_spip('base/abstract_sql');
$GLOBALS['meta'][$name] = sql_getfetsel('valeur', 'spip_meta', 'nom = ' . sql_quote($name, '', 'string'));
}
$key = base64_decode($GLOBALS['meta'][$name] ?? '');
if (strlen($key) === \SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
return $key;
}
if (!$autoInit) {
return null;
}
$key = Chiffrement::keygen();
ecrire_meta($name, base64_encode($key), 'non');
lire_metas(); // au cas ou ecrire_meta() ne fonctionne pas
return $key;
}
private function read(): array {
lire_fichier_securise($this->file, $json);
if (
$json
and $json = \json_decode($json, true)
and is_array($json)
) {
return array_map('base64_decode', $json);
}
return [];
}
}