delete(); } public function test_legacy_3des_values_and_rotated_app_key_are_re_encrypted_with_current_app_key(): void { $legacyRsa = $this->rsaConfig(); [$oldAppKey, $newAppKey] = [$this->appKey(), $this->appKey()]; $this->useKeys($oldAppKey, legacyRsa: $legacyRsa); $vcenterId = DB::table('module_vcenter')->insertGetId(['password' => $this->legacyEncrypt('vc-secret', $legacyRsa)]); $infraId = DB::table('module_hosting')->insertGetId(['password' => SignRSA::EncryptFunc('hosting-secret')]); $dnsServerId = DB::table('dns_manager_servers')->insertGetId([ 'name' => 'ns1', 'ip_address' => '10.0.0.1', 'username' => 'admin', 'password' => Crypt::encryptString('plesk-secret'), ]); DB::table('setting')->insert(['key' => 'smtp', 'value' => json_encode(['host' => 'smtp', 'password' => $this->legacyEncrypt('smtp-secret', $legacyRsa)])]); $this->assertSame('vc-secret', SignRSA::DecryptFunc(DB::table('module_vcenter')->where('id', $vcenterId)->value('password'))); $this->useKeys($newAppKey, previousAppKey: $oldAppKey, legacyRsa: $legacyRsa); $this->artisan('secrets:reencrypt')->assertSuccessful(); $this->useKeys($newAppKey); $vcenterPassword = DB::table('module_vcenter')->where('id', $vcenterId)->value('password'); $this->assertSame('vc-secret', Crypt::decryptString($vcenterPassword)); $this->assertSame('vc-secret', SignRSA::DecryptFunc($vcenterPassword)); $this->assertSame('hosting-secret', SignRSA::DecryptFunc(DB::table('module_hosting')->where('id', $infraId)->value('password'))); $this->assertSame('plesk-secret', Crypt::decryptString(DB::table('dns_manager_servers')->where('id', $dnsServerId)->value('password'))); $smtp = json_decode(DB::table('setting')->where('key', 'smtp')->value('value'), true); $this->assertSame('smtp-secret', SignRSA::DecryptFunc($smtp['password'])); $this->assertSame('smtp', $smtp['host']); } public function test_dry_run_changes_nothing_and_undecryptable_rows_fail_the_command(): void { $this->useKeys($this->appKey()); $id = DB::table('module_vcenter')->insertGetId(['password' => SignRSA::EncryptFunc('vc-secret')]); $original = DB::table('module_vcenter')->where('id', $id)->value('password'); $this->artisan('secrets:reencrypt --dry-run')->assertSuccessful(); $this->assertSame($original, DB::table('module_vcenter')->where('id', $id)->value('password')); $this->useKeys($this->appKey(), legacyRsa: $this->rsaConfig()); $this->artisan('secrets:reencrypt')->assertFailed(); $this->assertSame($original, DB::table('module_vcenter')->where('id', $id)->value('password')); } private function useKeys(string $appKey, ?string $previousAppKey = null, ?array $legacyRsa = null): void { config([ 'rsa.key' => $legacyRsa['key'] ?? null, 'rsa.public' => $legacyRsa['public'] ?? null, 'rsa.previous_key' => null, 'rsa.previous_public' => null, 'app.key' => $appKey, 'app.previous_keys' => $previousAppKey ? [$previousAppKey] : [], ]); $this->app->forgetInstance('encrypter'); Crypt::clearResolvedInstance('encrypter'); } private function legacyEncrypt(string $plaintext, array $rsa): string { openssl_sign($plaintext, $signature, $rsa['private'], 'sha256WithRSAEncryption'); return base64_encode(openssl_encrypt($plaintext . '|' . base64_encode($signature), 'DES-EDE3', base64_decode($rsa['key']), OPENSSL_RAW_DATA)); } private function rsaConfig(): array { $pair = openssl_pkey_new(['private_key_bits' => 2048]); openssl_pkey_export($pair, $private); return [ 'key' => base64_encode(random_bytes(24)), 'public' => openssl_pkey_get_details($pair)['key'], 'private' => $private, ]; } private function appKey(): string { return 'base64:' . base64_encode(Encrypter::generateKey('AES-256-CBC')); } }