PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
6.2.2 6.2.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 All 155 releases
mainwp / class / class-mainwp-encrypt-data-lib.php

class-mainwp-encrypt-data-lib.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.2.2, at class/class-mainwp-encrypt-data-lib.php

261 lines 8.5 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/Dashboard
7 */
8
9 namespace MainWP\Dashboard;
10
11 use phpseclib3\Crypt\RSA;
12 use phpseclib3\Crypt\AES;
13 use phpseclib3\Crypt\Random;
14
15
16 // Exit if accessed directly.
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 /**
22 * Class MainWP_Encrypt_Data_Lib
23 *
24 * @package MainWP/Dashboard
25 */
26 class MainWP_Encrypt_Data_Lib { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
27
28 /**
29 * Private static variable to hold the single instance of the class.
30 *
31 * @static
32 *
33 * @var mixed Default null
34 */
35 private static $instance = null;
36
37 /**
38 * Method instance()
39 *
40 * Create a public static instance.
41 *
42 * @static
43 * @return static class.
44 */
45 public static function instance() {
46 if ( null === static::$instance ) {
47 static::$instance = new self();
48 }
49 MainWP_Keys_Manager::auto_load_files();
50 return static::$instance;
51 }
52
53 /**
54 * Method get_class_name()
55 *
56 * Get Class Name.
57 *
58 * @return string Class name.
59 */
60 public static function get_class_name() {
61 return __CLASS__;
62 }
63
64
65 /**
66 * Get key filename.
67 *
68 * Returns the opaque HMAC-derived filename for the given site's encrypted
69 * private key. When called with $fullpath=true, also performs lazy migration:
70 * if the new opaque file does not yet exist but a legacy filename does, it
71 * is renamed in place. This covers the window between a plugin upgrade and
72 * the bulk migration on `mainwp_db_after_update`.
73 *
74 * @param int $site_id site id.
75 * @param bool $fullpath full path.
76 *
77 * @return string
78 */
79 public static function get_key_file( $site_id, $fullpath = false ) {
80 $file = MainWP_System_Utility::get_private_filename( 'pk', $site_id, 'priv_encrypt_keys' );
81 if ( $fullpath ) {
82 MainWP_Keys_Manager::init_keys_dir();
83 $key_dir = MainWP_Keys_Manager::get_keys_dir();
84 $new_path = $key_dir . $file;
85 if ( ! file_exists( $new_path ) ) {
86 $legacy_path = $key_dir . static::legacy_key_filename( $site_id );
87 if ( file_exists( $legacy_path ) ) {
88 if ( @rename( $legacy_path, $new_path ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors -- best-effort lazy migration.
89 return $new_path;
90 }
91 return $legacy_path; // rename failed; fall back to legacy until bulk migration runs.
92 }
93 }
94 return $new_path;
95 }
96 return $file;
97 }
98
99 /**
100 * Legacy (predictable) key filename.
101 *
102 * Returns the pre-migration filename pattern for a site's encrypted private
103 * key. Used by lazy migration in get_key_file() and by the bulk migration
104 * on plugin upgrade to locate files that need renaming. New code should
105 * never write a file at this name.
106 *
107 * @param int $site_id site id.
108 *
109 * @return string Legacy filename.
110 */
111 public static function legacy_key_filename( $site_id ) {
112 return 'mainwp_priv_encrypt_keys_' . $site_id;
113 }
114
115
116 /**
117 * Remove key file.
118 *
119 * Deletes both the new opaque filename and the legacy filename (if either
120 * exists) so that orphan legacy files left behind by an incomplete bulk
121 * migration are also cleaned up.
122 *
123 * @param int $site_id site id.
124 *
125 * @return void
126 */
127 public static function remove_key_file( $site_id ) {
128 MainWP_Keys_Manager::instance()->delete_key_file( static::get_key_file( $site_id ) );
129 MainWP_Keys_Manager::instance()->delete_key_file( static::legacy_key_filename( $site_id ) );
130 }
131
132 /**
133 * Encrypt data.
134 *
135 * @param mixed $value value.
136 * @param int $site_id site_id.
137 * @param bool $create_keys_file create_keys_file.
138 * @return mixed
139 */
140 public function encrypt_privkey( $value, $site_id = false, $create_keys_file = false ) {
141 $data = $this->encrypt_data( $value );
142 if ( is_array( $data ) && ! empty( $data['en_data'] ) ) {
143 if ( $create_keys_file && $site_id ) {
144 static::encrypt_save_keys( $site_id, $data );
145 }
146 return $data;
147 }
148 return array();
149 }
150
151 /**
152 * Decrypt data.
153 *
154 * @param mixed $encrypted encrypted.
155 * @param int $site_id site id.
156 * @return mixed
157 */
158 public function decrypt_privkey( $encrypted, $site_id = false ) {
159 $path = static::get_key_file( $site_id, true );
160 if ( ! file_exists( $path ) ) {
161 return '';
162 }
163 $values = file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- private key files.
164 $load_keys = explode( '-', $values );
165 if ( 3 <= count( $load_keys ) ) {
166 return $this->decrypt_data( $encrypted, $load_keys );
167 }
168 return '';
169 }
170
171 /**
172 * Encrypt save keys.
173 *
174 * @param int $site_id site id.
175 * @param array $encrypted_data encrypted data.
176 * @return mixed
177 */
178 public static function encrypt_save_keys( $site_id, $encrypted_data ) {
179 if ( is_array( $encrypted_data ) && ! empty( $encrypted_data['en_data'] ) ) {
180 $priv_file = static::get_key_file( $site_id );
181 $keys_encoded = base64_encode( $encrypted_data['priv_key'] ) . '-' . base64_encode( $encrypted_data['en_key'] ) . '-' . base64_encode( $encrypted_data['en_iv'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode trust.
182 return MainWP_Keys_Manager::instance()->save_key_file( $priv_file, $keys_encoded );
183 }
184 return false;
185 }
186
187 /**
188 * Encrypt data.
189 *
190 * @param mixed $data data.
191 * @return mixed
192 */
193 private function encrypt_data( $data ) {
194 try {
195 // Load or generate RSA keys.
196 $privateKey = RSA::createKey( 2048 );// Generates a new 2048-bit RSA key pair.
197 $publicKey = $privateKey->getPublicKey();
198
199 // Step 1: Generate a random AES key.
200 $aes = new AES( 'cbc' ); // Use AES in CBC mode.
201 $aesKey = Random::string( 32 ); // AES-256 key (32 bytes).
202 $aesIV = Random::string( 16 ); // 16-byte IV for CBC mode.
203 $aes->setKey( $aesKey );
204 $aes->setIV( $aesIV );
205
206 // Step 2: Encrypt the large data with AES.
207 $encryptedData = $aes->encrypt( $data );
208 // Step 3: Encrypt the AES key and IV with RSA.
209 $rsaEncryptedKey = $publicKey->encrypt( $aesKey );
210 $rsaEncryptedIV = $publicKey->encrypt( $aesIV );
211
212 $privateKeyString = $privateKey->toString( 'PKCS8' ); // Store in PKCS8 format for easy reuse.
213
214 return array(
215 'en_data' => $encryptedData,
216 'priv_key' => $privateKeyString,
217 'en_key' => $rsaEncryptedKey,
218 'en_iv' => $rsaEncryptedIV,
219 );
220
221 } catch ( \Exception $e ) {
222 MainWP_Logger::instance()->debug( 'Encrypt Data :: [error=' . $e->getMessage() . ']' );
223 return false;
224 }
225 }
226
227
228 /**
229 * Decrypt data.
230 *
231 * @param mixed $encryptedData encrypted data.
232 * @param array $load_keys load keys.
233 * @return mixed
234 */
235 public function decrypt_data( $encryptedData, $load_keys ) {
236 try {
237 //phpcs:disable WordPress.PHP.DiscouragedPHPFunctions -- base64_encode trust.
238 $loadPrivateKeyString = base64_decode( $load_keys[0] );
239 $rsaEncryptedKey = base64_decode( $load_keys[1] );
240 $rsaEncryptedIV = base64_decode( $load_keys[2] );
241 //phpcs:enable
242
243 // Load or generate RSA keys.
244 $privateKey = RSA::loadPrivateKey( $loadPrivateKeyString );
245 $aes = new AES( 'cbc' ); // Use AES in CBC mode.
246 // Decrypt the AES key and IV with RSA.
247 $decryptedKey = $privateKey->decrypt( $rsaEncryptedKey );
248 $decryptedIV = $privateKey->decrypt( $rsaEncryptedIV );
249
250 // Decrypt the data with AES.
251 $aes->setKey( $decryptedKey );
252 $aes->setIV( $decryptedIV );
253
254 return $aes->decrypt( $encryptedData );
255 } catch ( \Exception $e ) {
256 MainWP_Logger::instance()->debug( 'Decrypt Data :: [error=' . $e->getMessage() . ']' );
257 return false;
258 }
259 }
260 }
261