Completed
Push — master ( fdca64...241762 )
by cam
02:05
created
ecrire/src/Chiffrer/SpipCles.php 1 patch
Indentation   +165 added lines, -165 removed lines patch added patch discarded remove patch
@@ -13,169 +13,169 @@
 block discarded – undo
13 13
 
14 14
 /** Gestion des clés d’authentification / chiffrement de SPIP */
15 15
 final class SpipCles {
16
-	private static array $instances = [];
17
-
18
-	private string $file = _DIR_ETC . 'cles.php';
19
-	private readonly Cles $cles;
20
-
21
-	public static function instance(string $file = ''): self {
22
-		if (empty(self::$instances[$file])) {
23
-			self::$instances[$file] = new self($file);
24
-		}
25
-		return self::$instances[$file];
26
-	}
27
-
28
-	/**
29
-	 * Retourne le secret du site (shorthand)
30
-	 * @uses self::getSecretSite()
31
-	 */
32
-	public static function secret_du_site(): ?string {
33
-		return (self::instance())->getSecretSite();
34
-	}
35
-
36
-	private function __construct(string $file = '') {
37
-		if ($file) {
38
-			$this->file = $file;
39
-		}
40
-		$this->cles = new Cles($this->read());
41
-	}
42
-
43
-	/**
44
-	 * Renvoyer le secret du site
45
-	 *
46
-	 * Le secret du site doit rester aussi secret que possible, et est eternel
47
-	 * On ne doit pas l'exporter
48
-	 *
49
-	 * Le secret est partagé entre une clé disque et une clé bdd
50
-	 *
51
-	 * @return string
52
-	 */
53
-	public function getSecretSite(bool $autoInit = true): ?string {
54
-		$key = $this->getKey('secret_du_site', $autoInit);
55
-		$meta = $this->getMetaKey('secret_du_site', $autoInit);
56
-		// conserve la même longeur.
57
-		return $key ^ $meta;
58
-	}
59
-
60
-	/** Renvoyer le secret des authentifications */
61
-	public function getSecretAuth(bool $autoInit = false): ?string {
62
-		return $this->getKey('secret_des_auth', $autoInit);
63
-	}
64
-	public function save(): bool {
65
-		return ecrire_fichier_securise($this->file, $this->cles->toJson());
66
-	}
67
-
68
-	/**
69
-	 * Fournir une sauvegarde chiffree des cles (a l'aide d'une autre clé, comme le pass d'un auteur)
70
-	 *
71
-	 * @param string $withKey Clé de chiffrage de la sauvegarde
72
-	 * @return string Contenu de la sauvegarde chiffrée générée
73
-	 */
74
-	public function backup(
75
-		#[\SensitiveParameter]
76
-		string $withKey
77
-	): string {
78
-		if (count($this->cles)) {
79
-			return Chiffrement::chiffrer($this->cles->toJson(), $withKey);
80
-		}
81
-		return '';
82
-	}
83
-
84
-	/**
85
-	 * Restaurer les cles manquantes depuis une sauvegarde chiffree des cles
86
-	 * (si la sauvegarde est bien valide)
87
-	 */
88
-	public function restore(
89
-		/** Sauvegarde chiffrée (générée par backup()) */
90
-		string $backup,
91
-		#[\SensitiveParameter]
92
-		string $password_clair,
93
-		#[\SensitiveParameter]
94
-		string $password_hash,
95
-		int $id_auteur
96
-	): bool {
97
-		if (empty($backup)) {
98
-			return false;
99
-		}
100
-
101
-		$sauvegarde = Chiffrement::dechiffrer($backup, $password_clair);
102
-		$json = json_decode($sauvegarde, true, 512, JSON_THROW_ON_ERROR);
103
-		if (!$json) {
104
-			return false;
105
-		}
106
-
107
-		// cela semble une sauvegarde valide
108
-		$cles_potentielles = array_map('base64_decode', $json);
109
-
110
-		// il faut faire une double verif sur secret_des_auth
111
-		// pour s'assurer qu'elle permet bien de decrypter le pass de l'auteur qui fournit la sauvegarde
112
-		// et par extension tous les passwords
113
-		if (
114
-			!empty($cles_potentielles['secret_des_auth'])
115
-			&& !Password::verifier($password_clair, $password_hash, $cles_potentielles['secret_des_auth'])
116
-		) {
117
-			spip_logger('chiffrer')->notice("Restauration de la cle `secret_des_auth` par id_auteur $id_auteur erronnee, on ignore");
118
-			unset($cles_potentielles['secret_des_auth']);
119
-		}
120
-
121
-		// on merge les cles pour recuperer les cles manquantes
122
-		$restauration = false;
123
-		foreach ($cles_potentielles as $name => $key) {
124
-			if (!$this->cles->has($name)) {
125
-				$this->cles->set($name, $key);
126
-				spip_logger('chiffrer')->notice("Restauration de la cle $name par id_auteur $id_auteur");
127
-				$restauration = true;
128
-			}
129
-		}
130
-		return $restauration;
131
-	}
132
-
133
-	private function getKey(string $name, bool $autoInit): ?string {
134
-		if ($this->cles->has($name)) {
135
-			return $this->cles->get($name);
136
-		}
137
-		if ($autoInit) {
138
-			$this->cles->generate($name);
139
-			// si l'ecriture de fichier a bien marche on peut utiliser la cle
140
-			if ($this->save()) {
141
-				return $this->cles->get($name);
142
-			}
143
-			// sinon loger et annule la cle generee car il ne faut pas l'utiliser
144
-			spip_logger('chiffrer')->error('Echec ecriture du fichier cle ' . $this->file . " ; impossible de generer une cle $name");
145
-			$this->cles->delete($name);
146
-		}
147
-		return null;
148
-	}
149
-
150
-	private function getMetaKey(string $name, bool $autoInit = true): ?string {
151
-		if (!isset($GLOBALS['meta'][$name])) {
152
-			include_spip('base/abstract_sql');
153
-			$GLOBALS['meta'][$name] = sql_getfetsel('valeur', 'spip_meta', 'nom = ' . sql_quote($name, '', 'string'));
154
-		}
155
-		$key = base64_decode($GLOBALS['meta'][$name] ?? '');
156
-		if (strlen($key) === \SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
157
-			return $key;
158
-		}
159
-		if (!$autoInit) {
160
-			return null;
161
-		}
162
-		$key = Chiffrement::keygen();
163
-		ecrire_meta($name, base64_encode($key), 'non');
164
-		lire_metas(); // au cas ou ecrire_meta() ne fonctionne pas
165
-
166
-		return $key;
167
-	}
168
-
169
-	private function read(): array {
170
-		$json = null;
171
-		lire_fichier_securise($this->file, $json);
172
-		if (
173
-			$json
174
-			&& ($json = \json_decode($json, true, 512, JSON_THROW_ON_ERROR))
175
-			&& is_array($json)
176
-		) {
177
-			return array_map('base64_decode', $json);
178
-		}
179
-		return [];
180
-	}
16
+    private static array $instances = [];
17
+
18
+    private string $file = _DIR_ETC . 'cles.php';
19
+    private readonly Cles $cles;
20
+
21
+    public static function instance(string $file = ''): self {
22
+        if (empty(self::$instances[$file])) {
23
+            self::$instances[$file] = new self($file);
24
+        }
25
+        return self::$instances[$file];
26
+    }
27
+
28
+    /**
29
+     * Retourne le secret du site (shorthand)
30
+     * @uses self::getSecretSite()
31
+     */
32
+    public static function secret_du_site(): ?string {
33
+        return (self::instance())->getSecretSite();
34
+    }
35
+
36
+    private function __construct(string $file = '') {
37
+        if ($file) {
38
+            $this->file = $file;
39
+        }
40
+        $this->cles = new Cles($this->read());
41
+    }
42
+
43
+    /**
44
+     * Renvoyer le secret du site
45
+     *
46
+     * Le secret du site doit rester aussi secret que possible, et est eternel
47
+     * On ne doit pas l'exporter
48
+     *
49
+     * Le secret est partagé entre une clé disque et une clé bdd
50
+     *
51
+     * @return string
52
+     */
53
+    public function getSecretSite(bool $autoInit = true): ?string {
54
+        $key = $this->getKey('secret_du_site', $autoInit);
55
+        $meta = $this->getMetaKey('secret_du_site', $autoInit);
56
+        // conserve la même longeur.
57
+        return $key ^ $meta;
58
+    }
59
+
60
+    /** Renvoyer le secret des authentifications */
61
+    public function getSecretAuth(bool $autoInit = false): ?string {
62
+        return $this->getKey('secret_des_auth', $autoInit);
63
+    }
64
+    public function save(): bool {
65
+        return ecrire_fichier_securise($this->file, $this->cles->toJson());
66
+    }
67
+
68
+    /**
69
+     * Fournir une sauvegarde chiffree des cles (a l'aide d'une autre clé, comme le pass d'un auteur)
70
+     *
71
+     * @param string $withKey Clé de chiffrage de la sauvegarde
72
+     * @return string Contenu de la sauvegarde chiffrée générée
73
+     */
74
+    public function backup(
75
+        #[\SensitiveParameter]
76
+        string $withKey
77
+    ): string {
78
+        if (count($this->cles)) {
79
+            return Chiffrement::chiffrer($this->cles->toJson(), $withKey);
80
+        }
81
+        return '';
82
+    }
83
+
84
+    /**
85
+     * Restaurer les cles manquantes depuis une sauvegarde chiffree des cles
86
+     * (si la sauvegarde est bien valide)
87
+     */
88
+    public function restore(
89
+        /** Sauvegarde chiffrée (générée par backup()) */
90
+        string $backup,
91
+        #[\SensitiveParameter]
92
+        string $password_clair,
93
+        #[\SensitiveParameter]
94
+        string $password_hash,
95
+        int $id_auteur
96
+    ): bool {
97
+        if (empty($backup)) {
98
+            return false;
99
+        }
100
+
101
+        $sauvegarde = Chiffrement::dechiffrer($backup, $password_clair);
102
+        $json = json_decode($sauvegarde, true, 512, JSON_THROW_ON_ERROR);
103
+        if (!$json) {
104
+            return false;
105
+        }
106
+
107
+        // cela semble une sauvegarde valide
108
+        $cles_potentielles = array_map('base64_decode', $json);
109
+
110
+        // il faut faire une double verif sur secret_des_auth
111
+        // pour s'assurer qu'elle permet bien de decrypter le pass de l'auteur qui fournit la sauvegarde
112
+        // et par extension tous les passwords
113
+        if (
114
+            !empty($cles_potentielles['secret_des_auth'])
115
+            && !Password::verifier($password_clair, $password_hash, $cles_potentielles['secret_des_auth'])
116
+        ) {
117
+            spip_logger('chiffrer')->notice("Restauration de la cle `secret_des_auth` par id_auteur $id_auteur erronnee, on ignore");
118
+            unset($cles_potentielles['secret_des_auth']);
119
+        }
120
+
121
+        // on merge les cles pour recuperer les cles manquantes
122
+        $restauration = false;
123
+        foreach ($cles_potentielles as $name => $key) {
124
+            if (!$this->cles->has($name)) {
125
+                $this->cles->set($name, $key);
126
+                spip_logger('chiffrer')->notice("Restauration de la cle $name par id_auteur $id_auteur");
127
+                $restauration = true;
128
+            }
129
+        }
130
+        return $restauration;
131
+    }
132
+
133
+    private function getKey(string $name, bool $autoInit): ?string {
134
+        if ($this->cles->has($name)) {
135
+            return $this->cles->get($name);
136
+        }
137
+        if ($autoInit) {
138
+            $this->cles->generate($name);
139
+            // si l'ecriture de fichier a bien marche on peut utiliser la cle
140
+            if ($this->save()) {
141
+                return $this->cles->get($name);
142
+            }
143
+            // sinon loger et annule la cle generee car il ne faut pas l'utiliser
144
+            spip_logger('chiffrer')->error('Echec ecriture du fichier cle ' . $this->file . " ; impossible de generer une cle $name");
145
+            $this->cles->delete($name);
146
+        }
147
+        return null;
148
+    }
149
+
150
+    private function getMetaKey(string $name, bool $autoInit = true): ?string {
151
+        if (!isset($GLOBALS['meta'][$name])) {
152
+            include_spip('base/abstract_sql');
153
+            $GLOBALS['meta'][$name] = sql_getfetsel('valeur', 'spip_meta', 'nom = ' . sql_quote($name, '', 'string'));
154
+        }
155
+        $key = base64_decode($GLOBALS['meta'][$name] ?? '');
156
+        if (strlen($key) === \SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
157
+            return $key;
158
+        }
159
+        if (!$autoInit) {
160
+            return null;
161
+        }
162
+        $key = Chiffrement::keygen();
163
+        ecrire_meta($name, base64_encode($key), 'non');
164
+        lire_metas(); // au cas ou ecrire_meta() ne fonctionne pas
165
+
166
+        return $key;
167
+    }
168
+
169
+    private function read(): array {
170
+        $json = null;
171
+        lire_fichier_securise($this->file, $json);
172
+        if (
173
+            $json
174
+            && ($json = \json_decode($json, true, 512, JSON_THROW_ON_ERROR))
175
+            && is_array($json)
176
+        ) {
177
+            return array_map('base64_decode', $json);
178
+        }
179
+        return [];
180
+    }
181 181
 }
Please login to merge, or discard this patch.
ecrire/src/Chiffrer/Chiffrement.php 1 patch
Indentation   +68 added lines, -68 removed lines patch added patch discarded remove patch
@@ -18,76 +18,76 @@
 block discarded – undo
18 18
  * @link https://www.php.net/manual/fr/book.sodium.php
19 19
  */
20 20
 class Chiffrement {
21
-	/** Chiffre un message en utilisant une clé ou un mot de passe */
22
-	public static function chiffrer(
23
-		string $message,
24
-		#[\SensitiveParameter]
25
-		string $key
26
-	): ?string {
27
-		// create a random salt for key derivation
28
-		$salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);
29
-		$key = self::deriveKeyFromPassword($key, $salt);
30
-		$nonce = random_bytes(\SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
31
-		$padded_message = sodium_pad($message, 16);
32
-		$encrypted = sodium_crypto_secretbox($padded_message, $nonce, $key);
33
-		$encoded = base64_encode($salt . $nonce . $encrypted);
34
-		sodium_memzero($key);
35
-		sodium_memzero($nonce);
36
-		sodium_memzero($salt);
37
-		#spip_logger('chiffrer')->debug("chiffrer($message)=$encoded");
38
-		return $encoded;
39
-	}
21
+    /** Chiffre un message en utilisant une clé ou un mot de passe */
22
+    public static function chiffrer(
23
+        string $message,
24
+        #[\SensitiveParameter]
25
+        string $key
26
+    ): ?string {
27
+        // create a random salt for key derivation
28
+        $salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);
29
+        $key = self::deriveKeyFromPassword($key, $salt);
30
+        $nonce = random_bytes(\SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
31
+        $padded_message = sodium_pad($message, 16);
32
+        $encrypted = sodium_crypto_secretbox($padded_message, $nonce, $key);
33
+        $encoded = base64_encode($salt . $nonce . $encrypted);
34
+        sodium_memzero($key);
35
+        sodium_memzero($nonce);
36
+        sodium_memzero($salt);
37
+        #spip_logger('chiffrer')->debug("chiffrer($message)=$encoded");
38
+        return $encoded;
39
+    }
40 40
 
41
-	/** Déchiffre un message en utilisant une clé ou un mot de passe */
42
-	public static function dechiffrer(
43
-		string $encoded,
44
-		#[\SensitiveParameter]
45
-		string $key
46
-	): ?string {
47
-		$decoded = base64_decode($encoded);
48
-		$salt = substr($decoded, 0, \SODIUM_CRYPTO_PWHASH_SALTBYTES);
49
-		$nonce = substr($decoded, \SODIUM_CRYPTO_PWHASH_SALTBYTES, \SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
50
-		$encrypted = substr($decoded, \SODIUM_CRYPTO_PWHASH_SALTBYTES + \SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
51
-		$key = self::deriveKeyFromPassword($key, $salt);
52
-		$padded_message = sodium_crypto_secretbox_open($encrypted, $nonce, $key);
53
-		sodium_memzero($key);
54
-		sodium_memzero($nonce);
55
-		sodium_memzero($salt);
56
-		if ($padded_message === false) {
57
-			spip_logger('chiffrer')->debug("dechiffrer() chiffre corrompu `$encoded`");
58
-			return null;
59
-		}
60
-		return sodium_unpad($padded_message, 16);
61
-	}
41
+    /** Déchiffre un message en utilisant une clé ou un mot de passe */
42
+    public static function dechiffrer(
43
+        string $encoded,
44
+        #[\SensitiveParameter]
45
+        string $key
46
+    ): ?string {
47
+        $decoded = base64_decode($encoded);
48
+        $salt = substr($decoded, 0, \SODIUM_CRYPTO_PWHASH_SALTBYTES);
49
+        $nonce = substr($decoded, \SODIUM_CRYPTO_PWHASH_SALTBYTES, \SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
50
+        $encrypted = substr($decoded, \SODIUM_CRYPTO_PWHASH_SALTBYTES + \SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
51
+        $key = self::deriveKeyFromPassword($key, $salt);
52
+        $padded_message = sodium_crypto_secretbox_open($encrypted, $nonce, $key);
53
+        sodium_memzero($key);
54
+        sodium_memzero($nonce);
55
+        sodium_memzero($salt);
56
+        if ($padded_message === false) {
57
+            spip_logger('chiffrer')->debug("dechiffrer() chiffre corrompu `$encoded`");
58
+            return null;
59
+        }
60
+        return sodium_unpad($padded_message, 16);
61
+    }
62 62
 
63
-	/** Génère une clé de la taille attendue pour le chiffrement */
64
-	public static function keygen(): string {
65
-		return sodium_crypto_secretbox_keygen();
66
-	}
63
+    /** Génère une clé de la taille attendue pour le chiffrement */
64
+    public static function keygen(): string {
65
+        return sodium_crypto_secretbox_keygen();
66
+    }
67 67
 
68
-	/**
69
-	 * Retourne une clé de la taille attendue pour le chiffrement
70
-	 *
71
-	 * Notamment si on utilise un mot de passe comme clé, il faut le hacher
72
-	 * pour servir de clé à la taille correspondante.
73
-	 */
74
-	private static function deriveKeyFromPassword(
75
-		#[\SensitiveParameter]
76
-		string $password,
77
-		string $salt
78
-	): string {
79
-		if (strlen($password) === \SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
80
-			return $password;
81
-		}
82
-		$key = sodium_crypto_pwhash(
83
-			\SODIUM_CRYPTO_SECRETBOX_KEYBYTES,
84
-			$password,
85
-			$salt,
86
-			\SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
87
-			\SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
88
-		);
89
-		sodium_memzero($password);
68
+    /**
69
+     * Retourne une clé de la taille attendue pour le chiffrement
70
+     *
71
+     * Notamment si on utilise un mot de passe comme clé, il faut le hacher
72
+     * pour servir de clé à la taille correspondante.
73
+     */
74
+    private static function deriveKeyFromPassword(
75
+        #[\SensitiveParameter]
76
+        string $password,
77
+        string $salt
78
+    ): string {
79
+        if (strlen($password) === \SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
80
+            return $password;
81
+        }
82
+        $key = sodium_crypto_pwhash(
83
+            \SODIUM_CRYPTO_SECRETBOX_KEYBYTES,
84
+            $password,
85
+            $salt,
86
+            \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
87
+            \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
88
+        );
89
+        sodium_memzero($password);
90 90
 
91
-		return $key;
92
-	}
91
+        return $key;
92
+    }
93 93
 }
Please login to merge, or discard this patch.
ecrire/src/Chiffrer/Password.php 1 patch
Indentation   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -13,49 +13,49 @@
 block discarded – undo
13 13
 
14 14
 /** Vérification et hachage de mot de passe */
15 15
 class Password {
16
-	/**
17
-	 * verifier qu'un mot de passe en clair est correct a l'aide de son hash
18
-	 *
19
-	 * Le mot de passe est poivre via la cle secret_des_auth
20
-	 */
21
-	public static function verifier(
22
-		#[\SensitiveParameter]
23
-		string $password_clair,
24
-		#[\SensitiveParameter]
25
-		string $password_hash,
26
-		#[\SensitiveParameter]
27
-		?string $key = null
28
-	): bool {
29
-		$key ??= self::getDefaultKey();
30
-		if ($key) {
31
-			$pass_poivre = hash_hmac('sha256', $password_clair, $key);
32
-			return password_verify($pass_poivre, $password_hash);
33
-		}
34
-		spip_logger('chiffrer')->notice('Aucune clé pour vérifier le mot de passe');
35
-		return false;
36
-	}
16
+    /**
17
+     * verifier qu'un mot de passe en clair est correct a l'aide de son hash
18
+     *
19
+     * Le mot de passe est poivre via la cle secret_des_auth
20
+     */
21
+    public static function verifier(
22
+        #[\SensitiveParameter]
23
+        string $password_clair,
24
+        #[\SensitiveParameter]
25
+        string $password_hash,
26
+        #[\SensitiveParameter]
27
+        ?string $key = null
28
+    ): bool {
29
+        $key ??= self::getDefaultKey();
30
+        if ($key) {
31
+            $pass_poivre = hash_hmac('sha256', $password_clair, $key);
32
+            return password_verify($pass_poivre, $password_hash);
33
+        }
34
+        spip_logger('chiffrer')->notice('Aucune clé pour vérifier le mot de passe');
35
+        return false;
36
+    }
37 37
 
38
-	/**
39
-	 * Calculer un hash salé du mot de passe
40
-	 */
41
-	public static function hacher(
42
-		#[\SensitiveParameter]
43
-		string $password_clair,
44
-		#[\SensitiveParameter]
45
-		?string $key = null
46
-	): ?string {
47
-		$key ??= self::getDefaultKey();
48
-		// ne pas fournir un hash errone si la cle nous manque
49
-		if ($key) {
50
-			$pass_poivre = hash_hmac('sha256', $password_clair, $key);
51
-			return password_hash($pass_poivre, PASSWORD_DEFAULT);
52
-		}
53
-		spip_logger('chiffrer')->notice('Aucune clé pour chiffrer le mot de passe');
54
-		return null;
55
-	}
38
+    /**
39
+     * Calculer un hash salé du mot de passe
40
+     */
41
+    public static function hacher(
42
+        #[\SensitiveParameter]
43
+        string $password_clair,
44
+        #[\SensitiveParameter]
45
+        ?string $key = null
46
+    ): ?string {
47
+        $key ??= self::getDefaultKey();
48
+        // ne pas fournir un hash errone si la cle nous manque
49
+        if ($key) {
50
+            $pass_poivre = hash_hmac('sha256', $password_clair, $key);
51
+            return password_hash($pass_poivre, PASSWORD_DEFAULT);
52
+        }
53
+        spip_logger('chiffrer')->notice('Aucune clé pour chiffrer le mot de passe');
54
+        return null;
55
+    }
56 56
 
57
-	private static function getDefaultKey(): ?string {
58
-		$keys = SpipCles::instance();
59
-		return $keys->getSecretAuth();
60
-	}
57
+    private static function getDefaultKey(): ?string {
58
+        $keys = SpipCles::instance();
59
+        return $keys->getSecretAuth();
60
+    }
61 61
 }
Please login to merge, or discard this patch.