diff --git a/app/Http/Controllers/Api/PublicCaController.php b/app/Http/Controllers/Api/PublicCaController.php index de4509c..26b3c1c 100644 --- a/app/Http/Controllers/Api/PublicCaController.php +++ b/app/Http/Controllers/Api/PublicCaController.php @@ -104,28 +104,15 @@ class PublicCaController extends Controller return redirect()->away(Storage::disk('r2-public')->url($cert->bat_path)); } - $store = $cert->ca_type === 'root' ? 'Root' : 'CA'; - $filename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $cert->common_name); - - // Convert CRLF to ensure batch file works - $certContent = str_replace("\n", "\r\n", str_replace("\r\n", "\n", $cert->cert_content)); + // Fallback: Generate via CaInstallerService + $installerService = app(\App\Services\CaInstallerService::class); + $content = $installerService->generateWindowsInstaller($cert, false); + $filename = 'install-trustlab-' . \Illuminate\Support\Str::slug($cert->common_name) . '.bat'; - $script = "@echo off\r\n"; - $script .= "echo Installing " . $cert->common_name . "...\r\n"; - $script .= "echo Please allow the security prompt to trust this certificate.\r\n"; - $script .= "set \"CERT_FILE=%TEMP%\\" . $filename . ".crt\"\r\n"; - $script .= "((\r\n"; - foreach(explode("\r\n", $certContent) as $line) { - if(!empty($line)) $script .= "echo " . $line . "\r\n"; - } - $script .= ")) > \"%CERT_FILE%\"\r\n"; - $script .= "certutil -addstore -f \"" . $store . "\" \"%CERT_FILE%\"\r\n"; - $script .= "del \"%CERT_FILE%\"\r\n"; - $script .= "pause\r\n"; - - return response($script) - ->header('Content-Type', 'application/x-bat') - ->header('Content-Disposition', 'attachment; filename="install-' . $filename . '.bat"'); + return response($content, 200, [ + 'Content-Type' => 'application/x-bat', + 'Content-Disposition' => 'attachment; filename="' . $filename . '"', + ]); } /** @@ -140,66 +127,16 @@ class PublicCaController extends Controller if ($cert->mac_path) { return redirect()->away(Storage::disk('r2-public')->url($cert->mac_path)); } - - // Extract Base64 payload - $pem = $cert->cert_content; - $lines = explode("\n", trim($pem)); - $payload = ''; - foreach ($lines as $line) { - if (!str_starts_with($line, '-----')) { - $payload .= trim($line); - } - } - $uuid = \Illuminate\Support\Str::uuid(); - $identifier = 'com.trustlab.cert.' . $serial; - $name = $cert->common_name; - - $xml = ' - - - - PayloadContent - - - PayloadCertificateFileName - ' . $name . '.cer - PayloadContent - - ' . $payload . ' - - PayloadDescription - Adds ' . $name . ' to Trusted Root Store - PayloadDisplayName - ' . $name . ' - PayloadIdentifier - ' . $identifier . '.cert - PayloadType - com.apple.security.pkcs1 - PayloadUUID - ' . \Illuminate\Support\Str::uuid() . ' - PayloadVersion - 1 - - - PayloadDisplayName - ' . $name . ' Installer - PayloadIdentifier - ' . $identifier . ' - PayloadRemovalDisallowed - - PayloadType - Configuration - PayloadUUID - ' . $uuid . ' - PayloadVersion - 1 - -'; + // Fallback: Generate via CaInstallerService + $installerService = app(\App\Services\CaInstallerService::class); + $content = $installerService->generateMacInstaller($cert); + $filename = 'trustlab-' . \Illuminate\Support\Str::slug($cert->common_name) . '.mobileconfig'; - return response($xml) - ->header('Content-Type', 'application/x-apple-aspen-config') - ->header('Content-Disposition', 'attachment; filename="' . $name . '.mobileconfig"'); + return response($content, 200, [ + 'Content-Type' => 'application/x-apple-aspen-config', + 'Content-Disposition' => 'attachment; filename="' . $filename . '"', + ]); } /** @@ -215,12 +152,14 @@ class PublicCaController extends Controller return redirect()->away(Storage::disk('r2-public')->url($cert->linux_path)); } - // Fallback or dynamic generation if needed (already in Service) - $sslService = app(\App\Services\OpenSslService::class); - $script = $sslService->generateLinuxInstaller($cert); + // Fallback: Generate via CaInstallerService + $installerService = app(\App\Services\CaInstallerService::class); + $content = $installerService->generateLinuxInstaller($cert, false); + $filename = 'install-trustlab-' . \Illuminate\Support\Str::slug($cert->common_name) . '.sh'; - return response($script) - ->header('Content-Type', 'application/x-sh') - ->header('Content-Disposition', 'attachment; filename="install-' . Str::slug($cert->common_name) . '.sh"'); + return response($content, 200, [ + 'Content-Type' => 'application/x-sh', + 'Content-Disposition' => 'attachment; filename="' . $filename . '"', + ]); } } diff --git a/app/Services/CaInstallerService.php b/app/Services/CaInstallerService.php new file mode 100644 index 0000000..9d89036 --- /dev/null +++ b/app/Services/CaInstallerService.php @@ -0,0 +1,525 @@ +common_name); + if ($isArchive) { + $cdnUrl = Storage::disk('r2-public')->url("ca/archives/{$cert->uuid}/{$slug}.crt"); + } else { + $cdnUrl = Storage::disk('r2-public')->url("ca/{$slug}.crt"); + } + + $typeLabel = $cert->ca_type === 'root' ? 'Root' : 'Intermediate'; + $store = $cert->ca_type === 'root' ? 'Root' : 'CA'; + $cleanName = $cert->common_name; + + // Hybrid Batch + PowerShell script for rich UI + return "@echo off\r\n" . + "setlocal\r\n" . + "title TrustLab CA Installer\r\n" . + "call :printHeader\r\n" . + "\r\n" . + "echo.\r\n" . + "call :printInfo \"Initiating installation for: {$cleanName} ({$typeLabel})\"\r\n" . + "\r\n" . + "set \"TEMP_CERT=%TEMP%\\trustlab-ca-{$cert->uuid}.crt\"\r\n" . + "\r\n" . + "call :printAction \"Downloading CA certificate...\"\r\n" . + "powershell -Command \"Invoke-WebRequest -Uri '{$cdnUrl}' -OutFile '%TEMP_CERT%'\"\r\n" . + "if %ERRORLEVEL% NEQ 0 (\r\n" . + " call :printError \"Failed to download certificate.\"\r\n" . + " pause\r\n" . + " exit /b 1\r\n" . + ")\r\n" . + "call :printSuccess \"Download complete.\"\r\n" . + "\r\n" . + "call :printAction \"Installing to Windows Certificate Store ({$store})...\"\r\n" . + "certutil -addstore -f \"{$store}\" \"%TEMP_CERT%\" >nul 2>&1\r\n" . + "if %ERRORLEVEL% NEQ 0 (\r\n" . + " call :printError \"Failed to install certificate to store.\"\r\n" . + " del \"%TEMP_CERT%\"\r\n" . + " pause\r\n" . + " exit /b 1\r\n" . + ")\r\n" . + "call :printSuccess \"Certificate installed successfully!\"\r\n" . + "\r\n" . + "del \"%TEMP_CERT%\"\r\n" . + "echo.\r\n" . + "call :printInfo \"Press any key to close...\"\r\n" . + "pause >nul\r\n" . + "exit /b\r\n" . + "\r\n" . + ":printHeader\r\n" . + "cls\r\n" . + "powershell -Command \"Write-Host ' _______ _____ _ _ _____ _______ _ _______ ______ ' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' |__ __|| __ \| | | |/ ____||__ __|| | |__ __|| _ |' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' | | | |__) || | | || (___ | | | | | | | |_) |' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' | | | _ / | | | | \___ \ | | | | | | | _ < ' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' | | | | \ \ | |__| | ____) | | | | |____ | | | |_) |' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' |_| |_| \_\| \____/ |_____/ |_| |______| |_| |______|' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' '\"\r\n" . + "exit /b\r\n" . + "\r\n" . + ":printInfo\r\n" . + "powershell -Command \"Write-Host ' [ INFO ] %~1' -ForegroundColor Cyan\"\r\n" . + "exit /b\r\n" . + "\r\n" . + ":printAction\r\n" . + "powershell -Command \"Write-Host ' [ .... ] %~1' -ForegroundColor Yellow\"\r\n" . + "exit /b\r\n" . + "\r\n" . + ":printSuccess\r\n" . + "powershell -Command \"Write-Host ' [ OK ] %~1' -ForegroundColor Green\"\r\n" . + "exit /b\r\n" . + "\r\n" . + ":printError\r\n" . + "powershell -Command \"Write-Host ' [ FAIL ] %~1' -ForegroundColor Red\"\r\n" . + "exit /b\r\n"; + } + + /** + * Generate macOS Configuration Profile (.mobileconfig) + */ + public function generateMacInstaller(CaCertificate $cert): string + { + $certBase64 = base64_encode($cert->cert_content); + $payloadId = "com.trustlab.ca." . Str::slug($cert->common_name); + $uuid1 = Str::uuid()->toString(); + $uuid2 = Str::uuid()->toString(); + + // Root CAs use 'com.apple.security.root', Intermediate CAs use 'com.apple.security.pkcs1' (intermediate) + $payloadType = $cert->ca_type === 'root' ? 'com.apple.security.root' : 'com.apple.security.pkcs1'; + + return "\n" . + "\n" . + "\n" . + "\n" . + " PayloadContent\n" . + " \n" . + " \n" . + " PayloadCertificateFileName\n" . + " {$cert->common_name}.crt\n" . + " PayloadContent\n" . + " {$certBase64}\n" . + " PayloadDescription\n" . + " TrustLab CA Certificate\n" . + " PayloadDisplayName\n" . + " {$cert->common_name}\n" . + " PayloadIdentifier\n" . + " {$payloadId}.cert\n" . + " PayloadType\n" . + " {$payloadType}\n" . + " PayloadUUID\n" . + " {$uuid2}\n" . + " PayloadVersion\n" . + " 1\n" . + " \n" . + " \n" . + " PayloadDescription\n" . + " TrustLab CA Installation\n" . + " PayloadDisplayName\n" . + " TrustLab CA: {$cert->common_name}\n" . + " PayloadIdentifier\n" . + " {$payloadId}\n" . + " PayloadRemovalDisallowed\n" . + " \n" . + " PayloadType\n" . + " Configuration\n" . + " PayloadUUID\n" . + " {$uuid1}\n" . + " PayloadVersion\n" . + " 1\n" . + "\n" . + ""; + } + + /** + * Generate Linux Installer (.sh) with Proxmox-style Aesthetics + */ + public function generateLinuxInstaller(CaCertificate $cert, bool $isArchive = false): string + { + $slug = Str::slug($cert->common_name); + if ($isArchive) { + $cdnUrl = Storage::disk('r2-public')->url("ca/archives/{$cert->uuid}/{$slug}.crt"); + } else { + $cdnUrl = Storage::disk('r2-public')->url("ca/{$slug}.crt"); + } + + $filename = "trustlab-" . $slug . ".crt"; + + return $this->getLinuxHeader() . + "header_info \"Installing CA: {$cert->common_name}\"\n" . + "\n" . + "check_root\n" . + "\n" . + "TEMP_CERT=\"/tmp/trustlab-{$cert->uuid}.crt\"\n" . + "\n" . + "msg_info \"Downloading certificate...\"\n" . + "if curl -sL \"{$cdnUrl}\" -o \"\$TEMP_CERT\"; then\n" . + " msg_ok \"Certificate downloaded.\"\n" . + "else\n" . + " msg_err \"Failed to download certificate from CDN.\"\n" . + " exit 1\n" . + "fi\n" . + "\n" . + "msg_info \"Detecting OS and checking ca-certificates package...\"\n" . + "if [ -f /etc/debian_version ]; then\n" . + " apt-get update -qq >/dev/null 2>&1 && apt-get install -y -qq ca-certificates >/dev/null 2>&1\n" . + " mkdir -p /usr/local/share/ca-certificates\n" . + " TARGET_DIR=\"/usr/local/share/ca-certificates\"\n" . + " UPDATE_CMD=\"update-ca-certificates\"\n" . + "elif [ -f /etc/redhat-release ]; then\n" . + " yum install -y -q ca-certificates >/dev/null 2>&1 || dnf install -y -q ca-certificates >/dev/null 2>&1\n" . + " mkdir -p /etc/pki/ca-trust/source/anchors\n" . + " TARGET_DIR=\"/etc/pki/ca-trust/source/anchors\"\n" . + " UPDATE_CMD=\"update-ca-trust extract\"\n" . + "elif [ -f /etc/arch-release ]; then\n" . + " pacman -Sy --noconfirm -q ca-certificates >/dev/null 2>&1\n" . + " mkdir -p /etc/ca-certificates/trust-source/anchors\n" . + " TARGET_DIR=\"/etc/ca-certificates/trust-source/anchors\"\n" . + " UPDATE_CMD=\"trust extract-compat\"\n" . + "else\n" . + " msg_err \"Unsupported Linux distribution.\"\n" . + " exit 1\n" . + "fi\n" . + "\n" . + "msg_info \"Installing certificate to \$TARGET_DIR...\"\n" . + "cp \"\$TEMP_CERT\" \"\$TARGET_DIR/{$filename}\"\n" . + "\n" . + "msg_info \"Updating certificate store...\"\n" . + "if \$UPDATE_CMD >/dev/null 2>&1; then\n" . + " msg_ok \"Store updated successfully.\"\n" . + "else\n" . + " msg_err \"Failed to update certificate store.\"\n" . + " exit 1\n" . + "fi\n" . + "\n" . + "rm \"\$TEMP_CERT\"\n" . + "echo -e \"\n${GN} Installation Complete! ${CL}\"\n" . + "echo -e \"${BL} Verify with: ${CL}ls \$TARGET_DIR/trustlab-*\"\n"; + } + + /** + * Common Linux Bash Header with Colors & Helpers + */ + private function getLinuxHeader(): string + { + return "#!/bin/bash\n" . + "# TrustLab CA Installer\n" . + "# Generated via CaInstallerService\n" . + "\n" . + "set -e\n" . + "\n" . + "YW=$(echo \"\\033[33m\")\n" . + "BL=$(echo \"\\033[36m\")\n" . + "RD=$(echo \"\\033[01;31m\")\n" . + "BGN=$(echo \"\\033[4;32m\")\n" . + "GN=$(echo \"\\033[1;92m\")\n" . + "DGN=$(echo \"\\033[32m\")\n" . + "CL=$(echo \"\\033[m\")\n" . + "CM=\"${GN}✓${CL}\"\n" . + "CROSS=\"${RD}✗${CL}\"\n" . + "BFR=\"\\\\r\\\\033[K\"\n" . + "HOLD=\"-\"\n" . + "\n" . + "header_info() {\n" . + " clear\n" . + " cat << \"EOF\"\n" . + "${BL}\n" . + " _______ _____ _ _ _____ _______ _ _______ ______ \n" . + " |__ __|| __ \| | | |/ ____||__ __|| | |__ __|| _ |\n" . + " | | | |__) || | | || (___ | | | | | | | |_) |\n" . + " | | | _ / | | | | \___ \ | | | | | | | _ < \n" . + " | | | | \ \ | |__| | ____) | | | | |____ | | | |_) |\n" . + " |_| |_| \_\| \____/ |_____/ |_| |______| |_| |______|${CL}\n" . + "\n" . + "EOF\n" . + "}\n" . + "\n" . + "msg_info() {\n" . + " local msg=\"$1\"\n" . + " echo -ne \" ${BL}[ INFO ]${CL} ${msg}...\"\n" . + "}\n" . + "\n" . + "msg_ok() {\n" . + " local msg=\"$1\"\n" . + " echo -e \"${BFR} ${GN}[ OK ]${CL} ${msg}\"\n" . + "}\n" . + "\n" . + "msg_err() {\n" . + " local msg=\"$1\"\n" . + " echo -e \"${BFR} ${RD}[ FAIL ]${CL} ${msg}\"\n" . + "}\n" . + "\n" . + "check_root() {\n" . + " if [ \"$(id -u)\" -ne 0 ]; then\n" . + " msg_err \"Please run as root (sudo).\"\n" . + " exit 1\n" . + " fi\n" . + "}\n" . + "\n"; + } + + /** + * Upload individual installers (SH, BAT, MAC) to CDN. + */ + public function uploadIndividualInstallersOnly(CaCertificate $cert, string $mode = 'both') + { + $slug = Str::slug($cert->common_name); + $cacheControl = 'no-cache, no-store, must-revalidate'; + + $syncs = []; + if ($mode === 'archive' || $mode === 'both') { + $syncs[] = ['base' => "ca/archives/{$cert->uuid}/installers/trustlab-{$slug}", 'isArchive' => true]; + } + if ($mode === 'latest' || $mode === 'both') { + $syncs[] = ['base' => "ca/installers/trustlab-{$slug}", 'isArchive' => false]; + } + + foreach ($syncs as $sync) { + $batPath = $sync['base'] . '.bat'; + $macPath = $sync['base'] . '.mobileconfig'; + $linuxPath = $sync['base'] . '.sh'; + + // 3. Generate and Upload Windows Installer (.bat) + $batContent = $this->generateWindowsInstaller($cert, $sync['isArchive']); + Storage::disk('r2-public')->put($batPath, $batContent, [ + 'visibility' => 'public', + 'ContentType' => 'text/plain', + 'CacheControl' => $cacheControl + ]); + + // 4. Generate and Upload macOS Profile (.mobileconfig) + $macContent = $this->generateMacInstaller($cert); // macOS profiles are self-contained + Storage::disk('r2-public')->put($macPath, $macContent, [ + 'visibility' => 'public', + 'ContentType' => 'application/x-apple-aspen-config', + 'CacheControl' => $cacheControl + ]); + + // 5. Generate and Upload Linux Script (.sh) + $linuxContent = $this->generateLinuxInstaller($cert, $sync['isArchive']); + Storage::disk('r2-public')->put($linuxPath, $linuxContent, [ + 'visibility' => 'public', + 'ContentType' => 'text/plain', + 'CacheControl' => $cacheControl + ]); + } + + $cert->update([ + 'bat_path' => "ca/installers/trustlab-{$slug}.bat", + 'mac_path' => "ca/installers/trustlab-{$slug}.mobileconfig", + 'linux_path' => "ca/installers/trustlab-{$slug}.sh", + 'last_synced_at' => now() + ]); + + return true; + } + + /** + * Generate Global Bundles (Installer Sapujagat) + */ + public function syncAllBundles() + { + $certificates = CaCertificate::all(); + if ($certificates->isEmpty()) return false; + + $cacheControl = 'no-cache, no-store, must-revalidate'; + + // 1. Linux Bundle (.sh) + // Note: Using the same Proxmox-style header + $now = now()->format('Y-m-d H:i:s'); + + $shContent = $this->getLinuxHeader() . + "header_info \"Bundle Installer (All CAs)\"\n" . + "\n" . + "check_root\n" . + "\n" . + "msg_info \"Detecting OS and checking ca-certificates package...\"\n" . + "if [ -f /etc/debian_version ]; then\n" . + " apt-get update -qq >/dev/null 2>&1 && apt-get install -y -qq ca-certificates >/dev/null 2>&1\n" . + " mkdir -p /usr/local/share/ca-certificates\n" . + " TARGET_DIR=\"/usr/local/share/ca-certificates\"\n" . + " UPDATE_CMD=\"update-ca-certificates\"\n" . + "elif [ -f /etc/redhat-release ]; then\n" . + " yum install -y -q ca-certificates >/dev/null 2>&1 || dnf install -y -q ca-certificates >/dev/null 2>&1\n" . + " mkdir -p /etc/pki/ca-trust/source/anchors\n" . + " TARGET_DIR=\"/etc/pki/ca-trust/source/anchors\"\n" . + " UPDATE_CMD=\"update-ca-trust extract\"\n" . + "elif [ -f /etc/arch-release ]; then\n" . + " pacman -Sy --noconfirm -q ca-certificates >/dev/null 2>&1\n" . + " mkdir -p /etc/ca-certificates/trust-source/anchors\n" . + " TARGET_DIR=\"/etc/ca-certificates/trust-source/anchors\"\n" . + " UPDATE_CMD=\"trust extract-compat\"\n" . + "else\n" . + " msg_err \"Unsupported Linux distribution.\"\n" . + " exit 1\n" . + "fi\n" . + "\n"; + + // Loop add certificate downloads to bundle + foreach ($certificates as $cert) { + $slug = Str::slug($cert->common_name); + // Use public URL for public accessibility + $cdnUrl = Storage::disk('r2-public')->url("ca/{$slug}.crt"); + $filename = "trustlab-" . $slug . ".crt"; + + $shContent .= "msg_info \"Processing: {$cert->common_name}\"\n"; + $shContent .= "curl -sL \"{$cdnUrl}\" -o \"\$TARGET_DIR/{$filename}\"\n"; + } + + $shContent .= "\nmsg_info \"Updating certificate store...\"\n" . + "if \$UPDATE_CMD >/dev/null 2>&1; then\n" . + " msg_ok \"All certificates installed & store updated.\"\n" . + "else\n" . + " msg_err \"Failed to update certificate store.\"\n" . + " exit 1\n" . + "fi\n" . + "\n" . + "echo -e \"\n${GN} Complete! Installed all trustlab certs.${CL}\"\n"; + + + Storage::disk('r2-public')->put('ca/bundles/trustlab-all.sh', $shContent, [ + 'visibility' => 'public', + 'ContentType' => 'text/plain', + 'CacheControl' => $cacheControl + ]); + + // 2. Windows Bundle (.bat) + // Hybrid Batch + PowerShell script for rich UI + $batContent = "@echo off\r\n" . + "setlocal\r\n" . + "title TrustLab All-in-One Installer\r\n" . + "call :printHeader\r\n" . + "\r\n" . + "echo.\r\n" . + "call :printInfo \"Starting Bundle Installation...\"\r\n" . + "\r\n"; + + foreach ($certificates as $cert) { + $slug = Str::slug($cert->common_name); + $cdnUrl = Storage::disk('r2-public')->url("ca/{$slug}.crt"); + $store = $cert->ca_type === 'root' ? 'Root' : 'CA'; + + $batContent .= "set \"TEMP_CERT=%TEMP%\\trustlab-{$slug}.crt\"\r\n" . + "call :printAction \"Installing {$cert->common_name}...\"\r\n" . + "powershell -Command \"Invoke-WebRequest -Uri '{$cdnUrl}' -OutFile '%TEMP_CERT%'\"\r\n" . + "certutil -addstore -f \"{$store}\" \"%TEMP_CERT%\" >nul 2>&1\r\n" . + "del \"%TEMP_CERT%\"\r\n"; + } + + $batContent .= "\r\n" . + "call :printSuccess \"All certificates processed.\"\r\n" . + "echo.\r\n" . + "call :printInfo \"Press any key to close...\"\r\n" . + "pause >nul\r\n" . + "exit /b\r\n" . + "\r\n" . + ":printHeader\r\n" . + "cls\r\n" . + "powershell -Command \"Write-Host ' _______ _____ _ _ _____ _______ _ _______ ______ ' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' |__ __|| __ \| | | |/ ____||__ __|| | |__ __|| _ |' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' | | | |__) || | | || (___ | | | | | | | |_) |' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' | | | _ / | | | | \___ \ | | | | | | | _ < ' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' | | | | \ \ | |__| | ____) | | | | |____ | | | |_) |' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' |_| |_| \_\| \____/ |_____/ |_| |______| |_| |______|' -ForegroundColor Cyan\"\r\n" . + "powershell -Command \"Write-Host ' '\"\r\n" . + "exit /b\r\n" . + "\r\n" . + ":printInfo\r\n" . + "powershell -Command \"Write-Host ' [ INFO ] %~1' -ForegroundColor Cyan\"\r\n" . + "exit /b\r\n" . + "\r\n" . + ":printAction\r\n" . + "powershell -Command \"Write-Host ' [ .... ] %~1' -ForegroundColor Yellow\"\r\n" . + "exit /b\r\n" . + "\r\n" . + ":printSuccess\r\n" . + "powershell -Command \"Write-Host ' [ OK ] %~1' -ForegroundColor Green\"\r\n" . + "exit /b\r\n" . + "\r\n" . + ":printError\r\n" . + "powershell -Command \"Write-Host ' [ FAIL ] %~1' -ForegroundColor Red\"\r\n" . + "exit /b\r\n"; + + Storage::disk('r2-public')->put('ca/bundles/trustlab-all.bat', $batContent, [ + 'visibility' => 'public', + 'ContentType' => 'text/plain', + 'CacheControl' => $cacheControl + ]); + + // 3. MacOS Bundle (Config Profile - logic kept as is) + $uuid1 = Str::uuid()->toString(); + $payloadContent = ""; + + foreach ($certificates as $cert) { + $certBase64 = base64_encode($cert->cert_content); + $uuidSub = Str::uuid()->toString(); + $payloadType = $cert->ca_type === 'root' ? 'com.apple.security.root' : 'com.apple.security.pkcs1'; + + $payloadContent .= " \n" . + " PayloadCertificateFileName\n" . + " {$cert->common_name}.crt\n" . + " PayloadContent\n" . + " {$certBase64}\n" . + " PayloadDescription\n" . + " TrustLab CA Certificate\n" . + " PayloadDisplayName\n" . + " {$cert->common_name}\n" . + " PayloadIdentifier\n" . + " com.trustlab.bundle.{$cert->uuid}\n" . + " PayloadType\n" . + " {$payloadType}\n" . + " PayloadUUID\n" . + " {$uuidSub}\n" . + " PayloadVersion\n" . + " 1\n" . + " \n"; + } + + $macContent = "\n" . + "\n" . + "\n" . + "\n" . + " PayloadContent\n" . + " \n" . $payloadContent . " \n" . + " PayloadDescription\n" . + " TrustLab All-in-One CA Bundle\n" . + " PayloadDisplayName\n" . + " TrustLab CA Bundle\n" . + " PayloadIdentifier\n" . + " com.trustlab.ca.bundle\n" . + " PayloadRemovalDisallowed\n" . + " \n" . + " PayloadType\n" . + " Configuration\n" . + " PayloadUUID\n" . + " {$uuid1}\n" . + " PayloadVersion\n" . + " 1\n" . + "\n" . + ""; + + Storage::disk('r2-public')->delete('ca/bundles/trustlab-all.mobileconfig'); + Storage::disk('r2-public')->put('ca/bundles/trustlab-all.mobileconfig', $macContent, [ + 'visibility' => 'public', + 'ContentType' => 'application/x-apple-aspen-config', + 'CacheControl' => $cacheControl + ]); + + return true; + } +} diff --git a/app/Services/OpenSslService.php b/app/Services/OpenSslService.php index 96fda63..3036673 100644 --- a/app/Services/OpenSslService.php +++ b/app/Services/OpenSslService.php @@ -508,148 +508,6 @@ class OpenSslService return $newCert; } - /** - * Generate Windows Installer (.bat) - */ - public function generateWindowsInstaller(CaCertificate $cert, bool $isArchive = false): string - { - $slug = Str::slug($cert->common_name); - if ($isArchive) { - $cdnUrl = Storage::disk('r2-public')->url("ca/archives/{$cert->uuid}/{$slug}.crt"); - } else { - $cdnUrl = Storage::disk('r2-public')->url("ca/{$slug}.crt"); - } - - $typeLabel = $cert->ca_type === 'root' ? 'Root' : 'Intermediate'; - $store = $cert->ca_type === 'root' ? 'Root' : 'CA'; - - return "@echo off\n" . - "echo TrustLab - Installing {$typeLabel} CA Certificate: {$cert->common_name}\n" . - "set \"TEMP_CERT=%TEMP%\\trustlab-ca-{$cert->uuid}.crt\"\n" . - "echo Downloading certificate...\n" . - "curl -L --progress-bar \"{$cdnUrl}\" -o \"%TEMP_CERT%\"\n" . - "if %ERRORLEVEL% NEQ 0 (\n" . - " echo Error: Failed to download certificate.\n" . - " pause\n" . - " exit /b 1\n" . - ")\n" . - "echo Installing to {$store} store...\n" . - "certutil -addstore -f \"{$store}\" \"%TEMP_CERT%\"\n" . - "del \"%TEMP_CERT%\"\n" . - "echo Installation Complete.\n" . - "pause"; - } - - /** - * Generate macOS Configuration Profile (.mobileconfig) - */ - public function generateMacInstaller(CaCertificate $cert): string - { - $certBase64 = base64_encode($cert->cert_content); - $payloadId = "com.trustlab.ca." . Str::slug($cert->common_name); - $uuid1 = Str::uuid()->toString(); - $uuid2 = Str::uuid()->toString(); - - // Root CAs use 'com.apple.security.root', Intermediate CAs use 'com.apple.security.pkcs1' (intermediate) - $payloadType = $cert->ca_type === 'root' ? 'com.apple.security.root' : 'com.apple.security.pkcs1'; - - return "\n" . - "\n" . - "\n" . - "\n" . - " PayloadContent\n" . - " \n" . - " \n" . - " PayloadCertificateFileName\n" . - " {$cert->common_name}.crt\n" . - " PayloadContent\n" . - " {$certBase64}\n" . - " PayloadDescription\n" . - " TrustLab CA Certificate\n" . - " PayloadDisplayName\n" . - " {$cert->common_name}\n" . - " PayloadIdentifier\n" . - " {$payloadId}.cert\n" . - " PayloadType\n" . - " {$payloadType}\n" . - " PayloadUUID\n" . - " {$uuid2}\n" . - " PayloadVersion\n" . - " 1\n" . - " \n" . - " \n" . - " PayloadDescription\n" . - " TrustLab CA Installation\n" . - " PayloadDisplayName\n" . - " TrustLab CA: {$cert->common_name}\n" . - " PayloadIdentifier\n" . - " {$payloadId}\n" . - " PayloadRemovalDisallowed\n" . - " \n" . - " PayloadType\n" . - " Configuration\n" . - " PayloadUUID\n" . - " {$uuid1}\n" . - " PayloadVersion\n" . - " 1\n" . - "\n" . - ""; - } - - /** - * Generate Linux Installer (.sh) - */ - public function generateLinuxInstaller(CaCertificate $cert, bool $isArchive = false): string - { - $slug = Str::slug($cert->common_name); - if ($isArchive) { - $cdnUrl = Storage::disk('r2-public')->url("ca/archives/{$cert->uuid}/{$slug}.crt"); - } else { - $cdnUrl = Storage::disk('r2-public')->url("ca/{$slug}.crt"); - } - - $filename = "trustlab-" . $slug . ".crt"; - - return "#!/bin/bash\n" . - "echo \"TrustLab - Installing CA Certificate: {$cert->common_name}\"\n" . - "if [ \"\$EUID\" -ne 0 ]; then echo \"Please run as root (sudo)\"; exit 1; fi\n" . - "TEMP_CERT=\"/tmp/trustlab-{$cert->uuid}.crt\"\n" . - "echo \"Downloading certificate...\"\n" . - "curl -L --progress-bar \"{$cdnUrl}\" -o \"\$TEMP_CERT\"\n" . - "if [ ! -f \"\$TEMP_CERT\" ]; then echo \"Failed to download cert\"; exit 1; fi\n\n" . - "echo \"Checking and installing ca-certificates package...\"\n" . - "if [ -d /etc/debian_version ]; then\n" . - " apt-get update -q && apt-get install -y -q ca-certificates\n" . - " mkdir -p /usr/local/share/ca-certificates\n" . - "elif [ -f /etc/redhat-release ]; then\n" . - " yum install -y -q ca-certificates || dnf install -y -q ca-certificates\n" . - " mkdir -p /etc/pki/ca-trust/source/anchors\n" . - "elif [ -f /etc/arch-release ]; then\n" . - " pacman -Sy --noconfirm -q ca-certificates\n" . - " mkdir -p /etc/ca-certificates/trust-source/anchors\n" . - "fi\n\n" . - "# Detection based on directories\n" . - "if [ -d /usr/local/share/ca-certificates ]; then\n" . - " cp \"\$TEMP_CERT\" \"/usr/local/share/ca-certificates/{$filename}\"\n" . - " update-ca-certificates\n" . - "# RHEL/CentOS/Fedora\n" . - "elif [ -d /etc/pki/ca-trust/source/anchors ]; then\n" . - " cp \"\$TEMP_CERT\" \"/etc/pki/ca-trust/source/anchors/{$filename}\"\n" . - " update-ca-trust extract\n" . - "# Arch Linux\n" . - "elif [ -d /etc/ca-certificates/trust-source/anchors ]; then\n" . - " cp \"\$TEMP_CERT\" \"/etc/ca-certificates/trust-source/anchors/{$filename}\"\n" . - " trust extract-compat\n" . - "else\n" . - " echo \"Unsupported Linux distribution for automatic install after package check.\"\n" . - " echo \"Please manually install \$TEMP_CERT\"\n" . - " exit 1\n" . - "fi\n" . - "rm \"\$TEMP_CERT\"\n" . - "echo \"Installation Complete.\"\n" . - "echo \"To verify, you can check: ls /usr/local/share/ca-certificates/trustlab-*\"\n"; - } - /** * Upload only PEM/DER (The CRT files) to CDN. */ @@ -703,62 +561,6 @@ class OpenSslService return true; } - /** - * Upload individual installers (SH, BAT, MAC) to CDN. - */ - public function uploadIndividualInstallersOnly(CaCertificate $cert, string $mode = 'both') - { - $slug = Str::slug($cert->common_name); - $cacheControl = 'no-cache, no-store, must-revalidate'; - - $syncs = []; - if ($mode === 'archive' || $mode === 'both') { - $syncs[] = ['base' => "ca/archives/{$cert->uuid}/installers/trustlab-{$slug}", 'isArchive' => true]; - } - if ($mode === 'latest' || $mode === 'both') { - $syncs[] = ['base' => "ca/installers/trustlab-{$slug}", 'isArchive' => false]; - } - - foreach ($syncs as $sync) { - $batPath = $sync['base'] . '.bat'; - $macPath = $sync['base'] . '.mobileconfig'; - $linuxPath = $sync['base'] . '.sh'; - - // 3. Generate and Upload Windows Installer (.bat) - $batContent = $this->generateWindowsInstaller($cert, $sync['isArchive']); - Storage::disk('r2-public')->put($batPath, $batContent, [ - 'visibility' => 'public', - 'ContentType' => 'text/plain', - 'CacheControl' => $cacheControl - ]); - - // 4. Generate and Upload macOS Profile (.mobileconfig) - $macContent = $this->generateMacInstaller($cert); // macOS profiles are self-contained - Storage::disk('r2-public')->put($macPath, $macContent, [ - 'visibility' => 'public', - 'ContentType' => 'application/x-apple-aspen-config', - 'CacheControl' => $cacheControl - ]); - - // 5. Generate and Upload Linux Script (.sh) - $linuxContent = $this->generateLinuxInstaller($cert, $sync['isArchive']); - Storage::disk('r2-public')->put($linuxPath, $linuxContent, [ - 'visibility' => 'public', - 'ContentType' => 'text/plain', - 'CacheControl' => $cacheControl - ]); - } - - $cert->update([ - 'bat_path' => "ca/installers/trustlab-{$slug}.bat", - 'mac_path' => "ca/installers/trustlab-{$slug}.mobileconfig", - 'linux_path' => "ca/installers/trustlab-{$slug}.sh", - 'last_synced_at' => now() - ]); - - return true; - } - /** * Promote an archived certificate version to 'Latest' (public root) */ @@ -766,167 +568,17 @@ class OpenSslService { // Simply re-sync this specific certificate version as 'latest' $this->uploadPublicCertsOnly($cert, 'latest'); - $this->uploadIndividualInstallersOnly($cert, 'latest'); + + // Delegate installer uploads to CaInstallerService + $installerService = app(\App\Services\CaInstallerService::class); + $installerService->uploadIndividualInstallersOnly($cert, 'latest'); // Also sync all bundles to ensure global installers are updated with this promoted version - $this->syncAllBundles(); + $installerService->syncAllBundles(); return true; } - /** - * Generate Global Bundles (Installer Sapujagat) - */ - public function syncAllBundles() - { - $certificates = CaCertificate::all(); - if ($certificates->isEmpty()) return false; - - $cacheControl = 'no-cache, no-store, must-revalidate'; - - // 1. Linux Bundle (.sh) - $now = now()->format('Y-m-d H:i:s'); - $shContent = "#!/bin/bash\n" . - "# Generated at: {$now}\n" . - "echo \"TrustLab - Installing all CA Certificates...\"\n" . - "if [ \"\$EUID\" -ne 0 ]; then echo \"Please run as root (sudo)\"; exit 1; fi\n\n" . - "echo \"Checking and installing ca-certificates package... (Please wait)\"\n" . - "if [ -d /etc/debian_version ]; then\n" . - " apt-get update -q && apt-get install -y -q ca-certificates\n" . - " mkdir -p /usr/local/share/ca-certificates\n" . - "elif [ -f /etc/redhat-release ]; then\n" . - " yum install -y -q ca-certificates || dnf install -y -q ca-certificates\n" . - " mkdir -p /etc/pki/ca-trust/source/anchors\n" . - "elif [ -f /etc/arch-release ]; then\n" . - " pacman -Sy --noconfirm -q ca-certificates\n" . - " mkdir -p /etc/ca-certificates/trust-source/anchors\n" . - "fi\n\n" . - "# OS Detection after package check\n" . - "TARGET_DIR=\"\"\n" . - "UPDATE_CMD=\"\"\n\n" . - "if [ -d /usr/local/share/ca-certificates ]; then\n" . - " TARGET_DIR=\"/usr/local/share/ca-certificates\"\n" . - " UPDATE_CMD=\"update-ca-certificates\"\n" . - "elif [ -d /etc/pki/ca-trust/source/anchors ]; then\n" . - " TARGET_DIR=\"/etc/pki/ca-trust/source/anchors\"\n" . - " UPDATE_CMD=\"update-ca-trust extract\"\n" . - "elif [ -d /etc/ca-certificates/trust-source/anchors ]; then\n" . - " TARGET_DIR=\"/etc/ca-certificates/trust-source/anchors\"\n" . - " UPDATE_CMD=\"trust extract-compat\"\n" . - "else\n" . - " echo \"Unsupported Linux distribution after package check.\"\n" . - " exit 1\n" . - "fi\n\n" . - "echo \"Cleaning up old TrustLab certificates...\"\n" . - "rm -f \"\$TARGET_DIR/trustlab-*.crt\"\n\n"; - - foreach ($certificates as $cert) { - $cdnUrl = $cert->cert_path ? Storage::disk('r2-public')->url($cert->cert_path) : null; - if (!$cdnUrl) continue; - - $filename = "trustlab-" . Str::slug($cert->common_name) . ".crt"; - $shContent .= "echo \"Downloading and deploying {$cert->common_name}...\"\n" . - "curl -L --progress-bar \"{$cdnUrl}\" -o \"\$TARGET_DIR/{$filename}\"\n"; - } - - $shContent .= "\necho \"Finalizing installation with: \$UPDATE_CMD\"\n" . - "\$UPDATE_CMD\n" . - "echo \"All certificates installed successfully.\"\n" . - "echo \"To verify, you can check: ls \$TARGET_DIR/trustlab-*\"\n"; - - Storage::disk('r2-public')->delete('ca/bundles/trustlab-all.sh'); - Storage::disk('r2-public')->put('ca/bundles/trustlab-all.sh', $shContent, [ - 'visibility' => 'public', - 'ContentType' => 'text/plain', - 'CacheControl' => $cacheControl - ]); - - // 2. Windows Bundle (.bat) - $batContent = "@echo off\n" . - "rem Generated at: {$now}\n" . - "echo TrustLab - Installing all CA Certificates...\n"; - - foreach ($certificates as $cert) { - $cdnUrl = $cert->cert_path ? Storage::disk('r2-public')->url($cert->cert_path) : null; - if (!$cdnUrl) continue; - - $store = $cert->ca_type === 'root' ? 'Root' : 'CA'; - $batContent .= "echo Installing {$cert->common_name} to {$store} store...\n" . - "curl -L --progress-bar \"{$cdnUrl}\" -o \"%TEMP%\\trustlab-{$cert->uuid}.crt\"\n" . - "certutil -addstore -f \"{$store}\" \"%TEMP%\\trustlab-{$cert->uuid}.crt\"\n" . - "del \"%TEMP%\\trustlab-{$cert->uuid}.crt\"\n"; - } - $batContent .= "echo Installation Complete.\npause"; - - Storage::disk('r2-public')->delete('ca/bundles/trustlab-all.bat'); - Storage::disk('r2-public')->put('ca/bundles/trustlab-all.bat', $batContent, [ - 'visibility' => 'public', - 'ContentType' => 'text/plain', - 'CacheControl' => $cacheControl - ]); - - // 3. macOS Bundle (.mobileconfig) - $uuid1 = Str::uuid()->toString(); - $payloadContent = ""; - - foreach ($certificates as $cert) { - $certBase64 = base64_encode($cert->cert_content); - $uuidSub = Str::uuid()->toString(); - $payloadType = $cert->ca_type === 'root' ? 'com.apple.security.root' : 'com.apple.security.pkcs1'; - - $payloadContent .= " \n" . - " PayloadCertificateFileName\n" . - " {$cert->common_name}.crt\n" . - " PayloadContent\n" . - " {$certBase64}\n" . - " PayloadDescription\n" . - " TrustLab CA Certificate\n" . - " PayloadDisplayName\n" . - " {$cert->common_name}\n" . - " PayloadIdentifier\n" . - " com.trustlab.bundle.{$cert->uuid}\n" . - " PayloadType\n" . - " {$payloadType}\n" . - " PayloadUUID\n" . - " {$uuidSub}\n" . - " PayloadVersion\n" . - " 1\n" . - " \n"; - } - - $macContent = "\n" . - "\n" . - "\n" . - "\n" . - " PayloadContent\n" . - " \n" . $payloadContent . " \n" . - " PayloadDescription\n" . - " TrustLab All-in-One CA Bundle\n" . - " PayloadDisplayName\n" . - " TrustLab CA Bundle\n" . - " PayloadIdentifier\n" . - " com.trustlab.ca.bundle\n" . - " PayloadRemovalDisallowed\n" . - " \n" . - " PayloadType\n" . - " Configuration\n" . - " PayloadUUID\n" . - " {$uuid1}\n" . - " PayloadVersion\n" . - " 1\n" . - "\n" . - ""; - - Storage::disk('r2-public')->delete('ca/bundles/trustlab-all.mobileconfig'); - Storage::disk('r2-public')->put('ca/bundles/trustlab-all.mobileconfig', $macContent, [ - 'visibility' => 'public', - 'ContentType' => 'application/x-apple-aspen-config', - 'CacheControl' => $cacheControl - ]); - - return true; - } - /** * Legacy/Full Upload (Uploads everything) */ @@ -934,14 +586,19 @@ class OpenSslService { try { $this->uploadPublicCertsOnly($cert); - $this->uploadIndividualInstallersOnly($cert); - $this->syncAllBundles(); + + // Delegate installer logic + $installerService = app(\App\Services\CaInstallerService::class); + $installerService->uploadIndividualInstallersOnly($cert); + $installerService->syncAllBundles(); + return true; } catch (\Exception $e) { \Log::error("Failed to upload CA to R2: " . $e->getMessage()); return false; } } + /** * Purge everything under the 'ca/' directory on the CDN. */