PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 5.0
MainWP Dashboard: Self-hosted WordPress Management for Agencies v5.0
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.0, at class/class-mainwp-keys-manager.php

516 lines 12.3 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 {
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 === self::$instance ) {
45 self::$instance = new self();
46 }
47 self::auto_load_files(); // to fix.
48 return self::$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';
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 self::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 = self::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' );
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 self::init_keys_dir();
237 $key_dir = self::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 = self::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 $encodedValue = base64_encode( $encryptedValue ); //phpcs:ignore
295
296 return $encodedValue;
297 }
298
299 /**
300 * Method decrypt_with_key()
301 *
302 * Handle decrypt value.
303 *
304 * @param mixed $encodedValue The string to decrypt.
305 * @param mixed $key Key to decrypt.
306 *
307 * @return string Decrypt value.
308 */
309 private function decrypt_with_key( $encodedValue, $key ) {
310 if ( empty( $encodedValue ) ) {
311 return '';
312 }
313 try {
314 // Decode the base64 encoded value.
315 $encryptedValue = base64_decode( $encodedValue ); //phpcs:ignore
316
317 // Extract the IV, ciphertext, and tag.
318 $iv = substr( $encryptedValue, 0, 16 );
319 $ciphertext = substr( $encryptedValue, 16, -16 );
320 $tag = substr( $encryptedValue, -16 );
321
322 // Create AES instance.
323 $aes = new AES( 'gcm' ); // MODE_GCM.
324 $aes->setKey( $key );
325
326 $aes->setNonce( $iv ); // Nonces are only used in GCM mode.
327 $aes->setAAD( 'authentication_data' );
328
329 // Set the authentication tag.
330 $aes->setTag( $tag );
331
332 // Decrypt the value.
333 $keypass = $aes->decrypt( $ciphertext );
334
335 return $keypass;
336 } catch ( \Exception $ex ) {
337 // error.
338 }
339 return '';
340 }
341
342 /**
343 * Method init_keys_dir()
344 *
345 * Check for keys directory and create it if it doesn't already exist,
346 * set the file permissions and update htaccess.
347 *
348 * @param mixed $keysDir Keys directory.
349 *
350 * @return void
351 */
352 public static function init_keys_dir( $keysDir = '' ) {
353
354 if ( '' === $keysDir ) {
355 $keysDir = self::get_keys_dir();
356 }
357
358 if ( ! is_string( $keysDir ) || stristr( $keysDir, '..' ) ) {
359 return;
360 }
361
362 $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
363
364 /**
365 * WordPress files system object.
366 *
367 * @global object
368 */
369 global $wp_filesystem;
370
371 if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
372
373 if ( ! $wp_filesystem->is_dir( $keysDir ) ) {
374 $wp_filesystem->mkdir( $keysDir, 0777 );
375 }
376
377 if ( ! file_exists( $keysDir . '.htaccess' ) ) {
378 $file_htaccess = $keysDir . '.htaccess';
379 $wp_filesystem->put_contents( $file_htaccess, 'deny from all' );
380 }
381
382 if ( ! file_exists( $keysDir . 'index.php' ) ) {
383 $file_index = $keysDir . 'index.php';
384 $wp_filesystem->touch( $file_index );
385 }
386 } else {
387
388 //phpcs:disable
389 if ( ! file_exists( $keysDir ) ) {
390 mkdir( $keysDir, 0777, true );
391 }
392
393 if ( ! file_exists( $keysDir . '.htaccess' ) ) {
394 $file_htaccess = @fopen( $keysDir . '.htaccess', 'w+' );
395 fwrite( $file_htaccess, 'deny from all' );
396 fclose( $file_htaccess );
397 }
398
399 if ( ! file_exists( $keysDir . 'index.php' ) ) {
400 $file_index = @fopen( $keysDir . 'index.php', 'w+' );
401 fclose( $file_index );
402 }
403 // phpcs:enable
404 }
405 }
406
407 /**
408 * Method get_keys_dir().
409 *
410 * Check for keys directory and create it if it doesn't already exist.
411 * set the file permissions and update htaccess.
412 *
413 * @return string Keys dir.
414 */
415 public static function get_keys_dir() {
416 $dirs = MainWP_System_Utility::get_mainwp_dir();
417 return $dirs[0] . 'pk' . DIRECTORY_SEPARATOR;
418 }
419
420
421 /**
422 * Method encrypt_keys_data()
423 *
424 * Handle encrypt value.
425 *
426 * @param mixed $data The value to encrypt.
427 * @param string $prefix prefix key file name.
428 * @param string $key_file key file name.
429 *
430 * @return string Encrypted value.
431 */
432 public function encrypt_keys_data( $data, $prefix, $key_file = false ) {
433
434 if ( empty( $data ) ) {
435 if ( ! empty( $key_file ) ) {
436 $this->delete_key_file( $key_file );
437 }
438 return $data;
439 }
440
441 if ( '_' !== substr( $prefix, -1 ) ) {
442 $prefix .= '_';
443 }
444
445 if ( ! function_exists( '\wp_rand' ) ) {
446 include_once ABSPATH . WPINC . '/pluggable.php';
447 }
448
449 if ( ! empty( $key_file ) && is_string( $key_file ) ) {
450 $file_name = $key_file;
451 } elseif ( ! empty( $data ) && is_array( $data ) && ! empty( $data['file_key'] ) ) {
452 $file_name = $data['file_key'];
453 } else {
454 $ran = wp_rand( 0, 9990 ); // to fix repeat value.
455 $file_name = $prefix . sha1( sha1( $prefix . time() . $ran ) . 'key_files' );
456 }
457
458 MainWP_Logger::instance()->debug( 'encrypt :: K file[' . $file_name . ']' );
459
460 try {
461 $key = Random::string( 32 ); // supported key length: 16, 24, 32.
462 $encrypted = $this->encrypt_with_key( $data, $key );
463 $result = array(
464 'key' => $key,
465 'file_key' => $file_name,
466 'encrypted_value' => $encrypted,
467 );
468 } catch ( \Exception $ex ) {
469 $err = $ex->getMessage();
470 if ( is_string( $err ) ) {
471 MainWP_Logger::instance()->debug( 'encrypt :: error[' . $err . ']' );
472 }
473 return false;
474 }
475
476 if ( is_array( $result ) && ! empty( $result['encrypted_value'] ) ) {
477 $key = $result['key'];
478 $file = $result['file_key'];
479 $pw = $result['encrypted_value'];
480 if ( $this->save_key_file( $file, $key ) ) {
481 return array(
482 'encrypted_val' => $pw,
483 'file_key' => $file,
484 );
485 }
486 }
487 return false;
488 }
489
490
491 /**
492 * Method decrypt_keys_data()
493 *
494 * Get decrypt value.
495 *
496 * @param string $encrypted Name of key.
497 * @param mixed $default_value Default value.
498 *
499 * @return string Decrypt value.
500 */
501 public function decrypt_keys_data( $encrypted, $default_value = false ) {
502 if ( is_array( $encrypted ) && ! empty( $encrypted['file_key'] ) && ! empty( $encrypted['encrypted_val'] ) ) {
503 try {
504 return $this->get_decrypt_values( $encrypted['encrypted_val'], $encrypted['file_key'], $default_value );
505 } catch ( \Exception $ex ) {
506 $err = $ex->getMessage();
507 if ( is_string( $err ) ) {
508 MainWP_Logger::instance()->debug( 'decrypt :: error[' . $err . ']' );
509 }
510 return false;
511 }
512 }
513 return $default_value;
514 }
515 }
516