PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 5.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v5.1
6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 5.2.2 5.3 All 153 releases
mainwp / class / class-mainwp-keys-manager.php

class-mainwp-keys-manager.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 5.1, at class/class-mainwp-keys-manager.php

512 lines 14.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 *
4 * Encrypts & Decrypts API Keys.
5 *
6 * @package MainWP/MainWP_Keys_Manager
7 */
8
9 namespace MainWP\Dashboard;
10
11 use phpseclib3\Crypt\AES;
12 use phpseclib3\Crypt\Random;
13
14 // Exit if accessed directly.
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 /**
20 * Class MainWP_Keys_Manager
21 *
22 * @package MainWP/MainWP_Keys_Manager
23 */
24 class MainWP_Keys_Manager { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
25
26 /**
27 * Private static variable to hold the single instance of the class.
28 *
29 * @static
30 *
31 * @var mixed Default null
32 */
33 private static $instance = null;
34
35 /**
36 * Method instance()
37 *
38 * Create a public static instance.
39 *
40 * @static
41 * @return Instance class.
42 */
43 public static function instance() {
44 if ( null === static::$instance ) {
45 static::$instance = new self();
46 }
47 static::auto_load_files(); // to fix.
48 return static::$instance;
49 }
50
51 /**
52 * Method get_class_name()
53 *
54 * Get Class Name.
55 *
56 * @return object Class name.
57 */
58 public static function get_class_name() {
59 return __CLASS__;
60 }
61
62 /**
63 * Method auto_load_files()
64 *
65 * Handle autoload files.
66 */
67 public static function auto_load_files() {
68 require_once MAINWP_PLUGIN_DIR . 'libs' . DIRECTORY_SEPARATOR . 'phpseclib' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; // NOSONAR -- WP compatible.
69 }
70
71 /**
72 * Method get_keys_value()
73 *
74 * Get decrypt value.
75 *
76 * @param string $name Name of key.
77 * @param mixed $default_value Default value.
78 *
79 * @return string Decrypt value.
80 */
81 public function get_keys_value( $name, $default_value = false ) {
82 $opt = get_option( $name );
83 if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
84 return $this->decrypt_keys_data( $opt, $default_value );
85 }
86 return $default_value;
87 }
88
89 /**
90 * Method update_key_value()
91 *
92 * Get decrypt value.
93 *
94 * @param mixed $option_name option name.
95 * @param mixed $value The option value.
96 * @param mixed $prefix The prefix value.
97 *
98 * @return string Decrypt value.
99 */
100 public function update_key_value( $option_name, $value = false, $prefix = 'dash_' ) {
101 static::init_keys_dir();
102
103 if ( false === $value || '' === $value ) {
104 $opt = get_option( $option_name );
105 if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
106 $this->delete_key_file( $opt['file_key'] );
107 }
108 return delete_option( $option_name );
109 }
110
111 try {
112 $result = $this->encrypt_value( $value, $option_name, $prefix );
113 } catch ( \Exception $ex ) {
114 $err = $ex->getMessage();
115 if ( is_string( $err ) ) {
116 MainWP_Logger::instance()->debug( 'encrypt :: name[' . $option_name . '] :: error[' . $err . ']' );
117 }
118 return false;
119 }
120
121 if ( is_array( $result ) && ! empty( $result['encrypted_value'] ) ) {
122 $key = $result['key'];
123 $file = $result['file_key'];
124 $pw = $result['encrypted_value'];
125 if ( $this->save_key_file( $file, $key ) ) {
126 $update = array(
127 'encrypted_val' => $pw,
128 'file_key' => $file,
129 );
130 update_option( $option_name, $update );
131 return true;
132 }
133 }
134 return false;
135 }
136
137 /**
138 * Method delete_key_file()
139 *
140 * Delete key file.
141 *
142 * @param string $file_key Name of key file.
143 *
144 * @return string Deleted.
145 */
146 public function delete_key_file( $file_key ) {
147 $key_dir = static::get_keys_dir();
148 $file_path = $key_dir . $file_key;
149 MainWP_Utility::delete_file( $file_path ); // delete file content key.
150 return true;
151 }
152
153 /**
154 * Method get_decrypt_values()
155 *
156 * Get decrypt value.
157 *
158 * @param mixed $encodedValue Encoded The value to decrypt.
159 * @param mixed $key_file The value key.
160 * @param mixed $default_value The default value.
161 *
162 * @return string Decrypt value.
163 */
164 private function get_decrypt_values( $encodedValue, $key_file, $default_value = '' ) {
165 // find the key file, and get saved key.
166 $key = $this->get_key_val( $key_file );
167 if ( ! empty( $key ) ) {
168 return $this->decrypt_value( $encodedValue, $key );
169 }
170 return $default_value;
171 }
172
173 /**
174 * Method encrypt_value()
175 *
176 * Handle encrypt value.
177 *
178 * @param mixed $keypass The value to encrypt.
179 * @param string $name Option name of encrypted data.
180 * @param string $prefix using for prefix key file name.
181 *
182 * @return string Encrypted value.
183 */
184 private function encrypt_value( $keypass, $name, $prefix ) {
185
186 if ( '_' !== substr( $prefix, -1 ) ) {
187 $prefix .= '_';
188 }
189
190 $opt = get_option( $name );
191
192 if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
193 $file_name = $opt['file_key'];
194 } else {
195 $file_name = $prefix . sha1( sha1( $prefix . $name . time() ) . 'key_files' ); // NOSONAR - safe for salt file name.
196 }
197
198 MainWP_Logger::instance()->debug( 'encrypt :: option name[' . $name . '] :: K file[' . $file_name . ']' );
199
200 $key = Random::string( 32 ); // supported key length: 16, 24, 32.
201
202 $encrypted = $this->encrypt_with_key( $keypass, $key );
203
204 return array(
205 'key' => $key,
206 'file_key' => $file_name,
207 'encrypted_value' => $encrypted,
208 );
209 }
210
211 /**
212 * Method decrypt_value()
213 *
214 * Handle decrypt value.
215 *
216 * @param mixed $encodedValue The value to decrypt.
217 * @param mixed $key Key to decrypt.
218 *
219 * @return string Decrypt value.
220 */
221 private function decrypt_value( $encodedValue, $key ) {
222 return $this->decrypt_with_key( $encodedValue, $key );
223 }
224
225 /**
226 * Method save_key_file()
227 *
228 * Handle save key passwd.
229 *
230 * @param mixed $key_file The value key.
231 * @param mixed $key_val The value.
232 *
233 * @return mixed Result.
234 */
235 private function save_key_file( $key_file, $key_val ) {
236 static::init_keys_dir();
237 $key_dir = static::get_keys_dir();
238 $file_path = $key_dir . $key_file;
239 $saved = file_put_contents( $file_path, $key_val ); //phpcs:ignore
240 return false === $saved ? false : true;
241 }
242
243 /**
244 * Method get_key_val()
245 *
246 * Get decrypt value.
247 *
248 * @param mixed $key_file The value key.
249 *
250 * @return string Decrypt value.
251 */
252 public function get_key_val( $key_file ) {
253 $key_dir = static::get_keys_dir();
254 $path = $key_dir . $key_file;
255 if ( file_exists( $path ) ) {
256 return file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- private key files.
257 }
258 return '';
259 }
260
261
262 /**
263 * Method encrypt_with_key()
264 *
265 * Handle encrypt value.
266 *
267 * @param mixed $keypass The value to encrypt.
268 * @param mixed $key Key to encrypt.
269 *
270 * @return string Encrypted value.
271 */
272 private function encrypt_with_key( $keypass, $key ) {
273
274 // Generate a random IV (Initialization Vector).
275 $iv = Random::string( 16 );
276
277 // Create AES instance.
278 $aes = new AES( 'gcm' ); // MODE_GCM.
279 $aes->setKey( $key );
280
281 $aes->setNonce( $iv ); // Nonces are only used in GCM mode.
282 $aes->setAAD( 'authentication_data' );
283
284 // Encrypt the value.
285 $ciphertext = $aes->encrypt( $keypass );
286
287 // Get the authentication tag.
288 $tag = $aes->getTag();
289
290 // Combine IV, ciphertext, and tag.
291 $encryptedValue = $iv . $ciphertext . $tag;
292
293 // Encode the encrypted value using base64 for storage.
294 return base64_encode( $encryptedValue ); //phpcs:ignore
295 }
296
297 /**
298 * Method decrypt_with_key()
299 *
300 * Handle decrypt value.
301 *
302 * @param mixed $encodedValue The string to decrypt.
303 * @param mixed $key Key to decrypt.
304 *
305 * @return string Decrypt value.
306 */
307 private function decrypt_with_key( $encodedValue, $key ) {
308 if ( empty( $encodedValue ) ) {
309 return '';
310 }
311 try {
312 // Decode the base64 encoded value.
313 $encryptedValue = base64_decode( $encodedValue ); //phpcs:ignore
314
315 // Extract the IV, ciphertext, and tag.
316 $iv = substr( $encryptedValue, 0, 16 );
317 $ciphertext = substr( $encryptedValue, 16, -16 );
318 $tag = substr( $encryptedValue, -16 );
319
320 // Create AES instance.
321 $aes = new AES( 'gcm' ); // MODE_GCM.
322 $aes->setKey( $key );
323
324 $aes->setNonce( $iv ); // Nonces are only used in GCM mode.
325 $aes->setAAD( 'authentication_data' );
326
327 // Set the authentication tag.
328 $aes->setTag( $tag );
329
330 // Decrypt the value.
331 return $aes->decrypt( $ciphertext );
332 } catch ( \Exception $ex ) {
333 // error.
334 }
335 return '';
336 }
337
338 /**
339 * Method init_keys_dir()
340 *
341 * Check for keys directory and create it if it doesn't already exist,
342 * set the file permissions and update htaccess.
343 *
344 * @param mixed $keysDir Keys directory.
345 *
346 * @return void
347 */
348 public static function init_keys_dir( $keysDir = '' ) { //phpcs:ignore -- NOSONAR - complex.
349
350 if ( '' === $keysDir ) {
351 $keysDir = static::get_keys_dir();
352 }
353
354 if ( ! is_string( $keysDir ) || stristr( $keysDir, '..' ) ) {
355 return;
356 }
357
358 $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
359
360 /**
361 * WordPress files system object.
362 *
363 * @global object
364 */
365 global $wp_filesystem;
366
367 if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
368
369 if ( ! $wp_filesystem->is_dir( $keysDir ) ) {
370 $wp_filesystem->mkdir( $keysDir, 0777 );
371 }
372
373 if ( ! file_exists( $keysDir . '.htaccess' ) ) {
374 $file_htaccess = $keysDir . '.htaccess';
375 $wp_filesystem->put_contents( $file_htaccess, 'deny from all' );
376 }
377
378 if ( ! file_exists( $keysDir . 'index.php' ) ) {
379 $file_index = $keysDir . 'index.php';
380 $wp_filesystem->touch( $file_index );
381 }
382 } else {
383
384 //phpcs:disable
385 if ( ! file_exists( $keysDir ) ) {
386 mkdir( $keysDir, 0777, true );
387 }
388
389 if ( ! file_exists( $keysDir . '.htaccess' ) ) {
390 $file_htaccess = @fopen( $keysDir . '.htaccess', 'w+' );
391 fwrite( $file_htaccess, 'deny from all' );
392 fclose( $file_htaccess );
393 }
394
395 if ( ! file_exists( $keysDir . 'index.php' ) ) {
396 $file_index = @fopen( $keysDir . 'index.php', 'w+' );
397 fclose( $file_index );
398 }
399 // phpcs:enable
400 }
401 }
402
403 /**
404 * Method get_keys_dir().
405 *
406 * Check for keys directory and create it if it doesn't already exist.
407 * set the file permissions and update htaccess.
408 *
409 * @return string Keys dir.
410 */
411 public static function get_keys_dir() {
412 $dirs = MainWP_System_Utility::get_mainwp_dir();
413 return $dirs[0] . 'pk' . DIRECTORY_SEPARATOR;
414 }
415
416
417 /**
418 * Method encrypt_keys_data()
419 *
420 * Handle encrypt value.
421 *
422 * @param mixed $data The value to encrypt.
423 * @param string $prefix prefix key file name.
424 * @param string $key_file key file name.
425 *
426 * @return string Encrypted value.
427 */
428 public function encrypt_keys_data( $data, $prefix, $key_file = false ) { //phpcs:ignore -- NOSONAR - complex.
429
430 if ( empty( $data ) ) {
431 if ( ! empty( $key_file ) ) {
432 $this->delete_key_file( $key_file );
433 }
434 return $data;
435 }
436
437 if ( '_' !== substr( $prefix, -1 ) ) {
438 $prefix .= '_';
439 }
440
441 if ( ! function_exists( '\wp_rand' ) ) {
442 include_once ABSPATH . WPINC . '/pluggable.php'; // NOSONAR - WP compatible.
443 }
444
445 if ( ! empty( $key_file ) && is_string( $key_file ) ) {
446 $file_name = $key_file;
447 } elseif ( ! empty( $data ) && is_array( $data ) && ! empty( $data['file_key'] ) ) {
448 $file_name = $data['file_key'];
449 } else {
450 $ran = wp_rand( 0, 9990 ); // to fix repeat value.
451 $file_name = $prefix . sha1( sha1( $prefix . time() . $ran ) . 'key_files' ); // NOSONAR - safe for salt file name.
452 }
453
454 MainWP_Logger::instance()->debug( 'encrypt :: K file[' . $file_name . ']' );
455
456 try {
457 $key = Random::string( 32 ); // supported key length: 16, 24, 32.
458 $encrypted = $this->encrypt_with_key( $data, $key );
459 $result = array(
460 'key' => $key,
461 'file_key' => $file_name,
462 'encrypted_value' => $encrypted,
463 );
464 } catch ( \Exception $ex ) {
465 $err = $ex->getMessage();
466 if ( is_string( $err ) ) {
467 MainWP_Logger::instance()->debug( 'encrypt :: error[' . $err . ']' );
468 }
469 return false;
470 }
471
472 if ( is_array( $result ) && ! empty( $result['encrypted_value'] ) ) {
473 $key = $result['key'];
474 $file = $result['file_key'];
475 $pw = $result['encrypted_value'];
476 if ( $this->save_key_file( $file, $key ) ) {
477 return array(
478 'encrypted_val' => $pw,
479 'file_key' => $file,
480 );
481 }
482 }
483 return false;
484 }
485
486
487 /**
488 * Method decrypt_keys_data()
489 *
490 * Get decrypt value.
491 *
492 * @param string $encrypted Name of key.
493 * @param mixed $default_value Default value.
494 *
495 * @return string Decrypt value.
496 */
497 public function decrypt_keys_data( $encrypted, $default_value = false ) {
498 if ( is_array( $encrypted ) && ! empty( $encrypted['file_key'] ) && ! empty( $encrypted['encrypted_val'] ) ) {
499 try {
500 return $this->get_decrypt_values( $encrypted['encrypted_val'], $encrypted['file_key'], $default_value );
501 } catch ( \Exception $ex ) {
502 $err = $ex->getMessage();
503 if ( is_string( $err ) ) {
504 MainWP_Logger::instance()->debug( 'decrypt :: error[' . $err . ']' );
505 }
506 return false;
507 }
508 }
509 return $default_value;
510 }
511 }
512