mirror of
https://github.com/twinpath/app.git
synced 2026-01-26 05:15:28 +07:00
Initial commit
This commit is contained in:
360
app/Http/Controllers/CertificateController.php
Normal file
360
app/Http/Controllers/CertificateController.php
Normal file
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use App\Models\Certificate;
|
||||
use App\Models\CaCertificate;
|
||||
use App\Services\OpenSslService;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use ZipArchive;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CertificateController extends Controller
|
||||
{
|
||||
protected $sslService;
|
||||
|
||||
public function __construct(OpenSslService $sslService)
|
||||
{
|
||||
$this->sslService = $sslService;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$caReady = CaCertificate::where('ca_type', 'root')->exists() &&
|
||||
CaCertificate::where('ca_type', 'intermediate_2048')->exists() &&
|
||||
CaCertificate::where('ca_type', 'intermediate_4096')->exists();
|
||||
|
||||
$perPage = $request->input('per_page', 10);
|
||||
$search = $request->input('search');
|
||||
|
||||
$query = Certificate::where('user_id', Auth::id());
|
||||
|
||||
if ($search) {
|
||||
$query->where(function($q) use ($search) {
|
||||
$q->where('common_name', 'like', "%{$search}%")
|
||||
->orWhere('serial_number', 'like', "%{$search}%")
|
||||
->orWhere('san', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
$certificates = $query->latest()->paginate($perPage)->withQueryString();
|
||||
|
||||
if ($request->ajax()) {
|
||||
return view('pages.certificate.partials.table', [
|
||||
'certificates' => $certificates,
|
||||
])->render();
|
||||
}
|
||||
|
||||
return view('pages.certificate.index', [
|
||||
'title' => 'Certificate Management',
|
||||
'caReady' => $caReady,
|
||||
'certificates' => $certificates,
|
||||
'perPage' => $perPage,
|
||||
'search' => $search,
|
||||
'defaults' => Config::get('openssl.ca_leaf_default')
|
||||
]);
|
||||
}
|
||||
|
||||
public function downloadCa($type)
|
||||
{
|
||||
// map legacy or simple type to specific ca_type
|
||||
$caType = match($type) {
|
||||
'root' => 'root',
|
||||
'intermediate', 'int_4096' => 'intermediate_4096',
|
||||
'int_2048' => 'intermediate_2048',
|
||||
default => $type
|
||||
};
|
||||
|
||||
$ca = CaCertificate::where('ca_type', $caType)->firstOrFail();
|
||||
|
||||
$configKey = match($caType) {
|
||||
'root' => 'openssl.ca_root.organizationName',
|
||||
'intermediate_4096' => 'openssl.ca_4096.organizationName',
|
||||
'intermediate_2048' => 'openssl.ca_2048.organizationName',
|
||||
default => 'app.name'
|
||||
};
|
||||
|
||||
$orgName = config($configKey);
|
||||
$brand = Str::slug($orgName, '_');
|
||||
$filename = "{$brand}_ca_{$type}.crt";
|
||||
|
||||
return response($ca->cert_content)
|
||||
->header('Content-Type', 'application/x-x509-ca-cert')
|
||||
->header('Content-Disposition', "attachment; filename={$filename}");
|
||||
}
|
||||
|
||||
public function downloadCaBundle()
|
||||
{
|
||||
$root = CaCertificate::where('ca_type', 'root')->firstOrFail();
|
||||
$int2048 = CaCertificate::where('ca_type', 'intermediate_2048')->firstOrFail();
|
||||
$int4096 = CaCertificate::where('ca_type', 'intermediate_4096')->firstOrFail();
|
||||
|
||||
// Bundle includes all for convenience
|
||||
$bundle = $int4096->cert_content . "\n" . $int2048->cert_content . "\n" . $root->cert_content;
|
||||
|
||||
$brand = Str::slug(config('openssl.ca_root.organizationName'), '_');
|
||||
$filename = "{$brand}_ca-bundle.crt";
|
||||
|
||||
return response($bundle)
|
||||
->header('Content-Type', 'application/x-x509-ca-cert')
|
||||
->header('Content-Disposition', "attachment; filename={$filename}");
|
||||
}
|
||||
|
||||
public function downloadCaAndroid()
|
||||
{
|
||||
$root = CaCertificate::where('ca_type', 'root')->firstOrFail();
|
||||
|
||||
// Convert PEM to DER
|
||||
$certPem = $root->cert_content;
|
||||
$begin = "-----BEGIN CERTIFICATE-----";
|
||||
$end = "-----END CERTIFICATE-----";
|
||||
$der = base64_decode($certPem);
|
||||
|
||||
$brand = Str::slug(config('openssl.ca_root.organizationName'), '_');
|
||||
$filename = "{$brand}_root-ca.der";
|
||||
|
||||
return response($der)
|
||||
->header('Content-Type', 'application/pkix-cert')
|
||||
->header('Content-Disposition', "attachment; filename={$filename}");
|
||||
}
|
||||
|
||||
public function downloadInstaller()
|
||||
{
|
||||
$appUrl = config('app.url');
|
||||
$brand = Str::slug(config('openssl.ca_root.organizationName'), '_');
|
||||
|
||||
$script = <<<BATCH
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
:: One-Click Certificate Installer for {$brand}
|
||||
:: Generated by {$appUrl}
|
||||
|
||||
set "SERVER_URL={$appUrl}"
|
||||
set "TEMP_DIR=%TEMP%\\{$brand}_installer"
|
||||
|
||||
if not exist "%TEMP_DIR%" mkdir "%TEMP_DIR%"
|
||||
cd /d "%TEMP_DIR%"
|
||||
|
||||
echo [1/5] Downloading Root CA...
|
||||
curl -s -f -o "root.crt" "%SERVER_URL%/certificate/download-ca/root"
|
||||
if %errorlevel% neq 0 (
|
||||
echo Failed to download Root CA.
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo [2/5] Downloading Intermediate CA 2048...
|
||||
curl -s -f -o "int_2048.crt" "%SERVER_URL%/certificate/download-ca/int_2048"
|
||||
if %errorlevel% neq 0 (
|
||||
echo Failed to download Intermediate CA 2048.
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo [3/5] Downloading Intermediate CA 4096...
|
||||
curl -s -f -o "int_4096.crt" "%SERVER_URL%/certificate/download-ca/int_4096"
|
||||
if %errorlevel% neq 0 (
|
||||
echo Failed to download Intermediate CA 4096.
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo [4/5] Installing Root CA to Trusted Root Certification Authorities...
|
||||
certutil -user -addstore "Root" "root.crt"
|
||||
|
||||
echo [5/5] Installing Intermediate CAs to Intermediate Certification Authorities...
|
||||
certutil -user -addstore "CA" "int_2048.crt"
|
||||
certutil -user -addstore "CA" "int_4096.crt"
|
||||
|
||||
echo.
|
||||
echo Cleanup...
|
||||
cd ..
|
||||
rmdir /s /q "%TEMP_DIR%"
|
||||
|
||||
echo.
|
||||
echo ========================================================
|
||||
echo Installation Complete!
|
||||
echo You may need to restart your browser dynamically.
|
||||
echo ========================================================
|
||||
pause
|
||||
BATCH;
|
||||
|
||||
return response($script)
|
||||
->header('Content-Type', 'application/x-msdos-program')
|
||||
->header('Content-Disposition', "attachment; filename={$brand}_install_certs.bat");
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('pages.certificate.create', [
|
||||
'title' => 'Generate Certificate',
|
||||
'defaults' => Config::get('openssl.ca_leaf_default')
|
||||
]);
|
||||
}
|
||||
|
||||
public function setupCa()
|
||||
{
|
||||
if (CaCertificate::count() > 0) {
|
||||
return redirect()->route('certificate.index')->with('error', 'CA already initialized.');
|
||||
}
|
||||
|
||||
if ($this->sslService->setupCa()) {
|
||||
return redirect()->route('certificate.index')->with('success', 'Root and Intermediate CA successfully initialized.');
|
||||
}
|
||||
|
||||
return redirect()->route('certificate.index')->with('error', 'Failed to initialize CA.');
|
||||
}
|
||||
|
||||
public function generate(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'common_name' => 'required|string|max:255',
|
||||
'config_mode' => 'required|in:default,manual',
|
||||
'organization' => 'nullable|required_if:config_mode,manual|string|max:255',
|
||||
'locality' => 'nullable|required_if:config_mode,manual|string|max:255',
|
||||
'state' => 'nullable|required_if:config_mode,manual|string|max:255',
|
||||
'country' => 'nullable|required_if:config_mode,manual|string|size:2',
|
||||
'san' => 'nullable|string',
|
||||
'key_bits' => 'required|in:2048,4096',
|
||||
]);
|
||||
|
||||
try {
|
||||
// Apply defaults if mode is 'default'
|
||||
if ($validated['config_mode'] === 'default') {
|
||||
$defaults = Config::get('openssl.ca_leaf_default');
|
||||
$validated['organization'] = $defaults['organizationName'];
|
||||
$validated['locality'] = $defaults['localityName'];
|
||||
$validated['state'] = $defaults['stateOrProvinceName'];
|
||||
$validated['country'] = $defaults['countryName'];
|
||||
}
|
||||
|
||||
$result = $this->sslService->generateLeaf($validated);
|
||||
|
||||
Certificate::create([
|
||||
'user_id' => Auth::id(),
|
||||
'common_name' => $validated['common_name'],
|
||||
'organization' => $validated['organization'],
|
||||
'locality' => $validated['locality'],
|
||||
'state' => $validated['state'],
|
||||
'country' => $validated['country'],
|
||||
'san' => $validated['san'],
|
||||
'key_bits' => $validated['key_bits'],
|
||||
'serial_number' => $this->sslService->formatSerialToHex($result['serial']),
|
||||
'cert_content' => $result['cert'],
|
||||
'key_content' => $result['key'],
|
||||
'csr_content' => $result['csr'],
|
||||
'valid_from' => $result['valid_from'] ?? null,
|
||||
'valid_to' => $result['valid_to'] ?? null,
|
||||
]);
|
||||
|
||||
return redirect()->route('certificate.index')->with('success', 'Certificate generated successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->withInput()->with('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
public function regenerate(Certificate $certificate)
|
||||
{
|
||||
$this->authorizeOwner($certificate);
|
||||
|
||||
try {
|
||||
$data = [
|
||||
'common_name' => $certificate->common_name,
|
||||
'organization' => $certificate->organization,
|
||||
'locality' => $certificate->locality,
|
||||
'state' => $certificate->state,
|
||||
'country' => $certificate->country,
|
||||
'san' => $certificate->san,
|
||||
'key_bits' => $certificate->key_bits,
|
||||
];
|
||||
|
||||
$result = $this->sslService->generateLeaf($data);
|
||||
|
||||
// Update existing record with new content
|
||||
$certificate->update([
|
||||
'serial_number' => $this->sslService->formatSerialToHex($result['serial']),
|
||||
'cert_content' => $result['cert'],
|
||||
'key_content' => $result['key'],
|
||||
'csr_content' => $result['csr'],
|
||||
'valid_from' => $result['valid_from'] ?? null,
|
||||
'valid_to' => $result['valid_to'] ?? null,
|
||||
'created_at' => now(), // Refresh timestamp
|
||||
]);
|
||||
|
||||
return redirect()->route('certificate.index')->with('success', 'Certificate regenerated successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('certificate.index')->with('error', 'Failed to regenerate: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function downloadZip(Certificate $certificate)
|
||||
{
|
||||
$this->authorizeOwner($certificate);
|
||||
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'cert_zip');
|
||||
$zip = new ZipArchive;
|
||||
|
||||
if ($zip->open($tempFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
|
||||
return redirect()->back()->with('error', 'Failed to create ZIP archive.');
|
||||
}
|
||||
|
||||
$filenameBase = preg_replace('/[^a-zA-Z0-9-_\.]/', '_', $certificate->common_name);
|
||||
|
||||
$zip->addFromString("{$filenameBase}.pem", $certificate->cert_content);
|
||||
$zip->addFromString("{$filenameBase}.key", $certificate->key_content);
|
||||
if ($certificate->csr_content) {
|
||||
$zip->addFromString("{$filenameBase}.csr", $certificate->csr_content);
|
||||
}
|
||||
$zip->close();
|
||||
|
||||
return response()->download($tempFile, "cert_{$filenameBase}.zip")->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
public function downloadP12(Certificate $certificate)
|
||||
{
|
||||
$this->authorizeOwner($certificate);
|
||||
|
||||
$password = '112133'; // Default password from user request
|
||||
if (openssl_pkcs12_export($certificate->cert_content, $p12, $certificate->key_content, $password)) {
|
||||
$filenameBase = preg_replace('/[^a-zA-Z0-9-_\.]/', '_', $certificate->common_name);
|
||||
return response($p12)
|
||||
->header('Content-Type', 'application/x-pkcs12')
|
||||
->header('Content-Disposition', "attachment; filename={$filenameBase}.p12");
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'Failed to generate P12 file.');
|
||||
}
|
||||
|
||||
public function viewFile(Certificate $certificate, $type)
|
||||
{
|
||||
$this->authorizeOwner($certificate);
|
||||
|
||||
$content = match($type) {
|
||||
'cert' => $certificate->cert_content,
|
||||
'key' => $certificate->key_content,
|
||||
'csr' => $certificate->csr_content,
|
||||
default => abort(404)
|
||||
};
|
||||
|
||||
return response($content)->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
public function delete(Certificate $certificate)
|
||||
{
|
||||
$this->authorizeOwner($certificate);
|
||||
$certificate->delete();
|
||||
return redirect()->route('certificate.index')->with('success', 'Certificate deleted successfully.');
|
||||
}
|
||||
|
||||
protected function authorizeOwner(Certificate $certificate)
|
||||
{
|
||||
if ($certificate->user_id !== Auth::id()) {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user