61 if (file_exists($mkcertExe)) {
65 Log::error(
'mkcert.exe missing at: ' . $mkcertExe .
'. It must be fetched during build time (prepareBase/buildFull/buildLite).');
78 if (!is_dir($sslPath)) {
79 Log::info(
'SSL directory missing, creating: ' . $sslPath);
80 if (mkdir($sslPath, 0700,
true)) {
81 @chmod($sslPath, 0700);
83 $gitignorePath = $sslPath .
'/.gitignore';
84 if (!file_exists($gitignorePath)) {
85 file_put_contents($gitignorePath,
'# git holder' . PHP_EOL);
88 Log::error(
'Failed to create SSL directory: ' . $sslPath);
92 $gitignorePath = $sslPath .
'/.gitignore';
93 if (!file_exists($gitignorePath)) {
94 file_put_contents($gitignorePath,
'# git holder' . PHP_EOL);
98 if (!is_readable($sslPath) || !is_writable($sslPath)) {
99 Log::warning(
'SSL directory is not fully accessible. Attempting to relax permissions: ' . $sslPath);
102 @chmod($sslPath, 0755);
103 clearstatcache(
true, $sslPath);
105 if (!is_readable($sslPath) || !is_writable($sslPath)) {
106 Log::error(
'SSL directory is still not readable/writable after permission adjustment: ' . $sslPath);
126 Log::info(
'Creating new Root CA and installing it...');
130 $batch .=
'"' . $mkcertExe .
'" -uninstall' . PHP_EOL;
131 $batch .=
'"' . $mkcertExe .
'" -install' . PHP_EOL;
137 if (!file_exists($rootCaPath)) {
138 Log::error(
'Failed to create Root CA file at: ' . $rootCaPath);
145 $batch .=
'"' . $mkcertExe .
'" -CAROOT' . PHP_EOL;
146 $caRootInfo =
Batch::exec(
'mkcertCaRootInfo', $batch);
147 if ($caRootInfo && isset($caRootInfo[0])) {
148 Log::info(
'mkcert CAROOT is set to: ' . $caRootInfo[0]);
154 Log::info(
'Root CA created. Rebuilding all existing certificates and ensuring localhost exists...');
172 foreach ($certs as $cert) {
173 Log::info(
'Rebuilding certificate: ' . $cert);
175 Log::error(
'Failed to rebuild certificate: ' . $cert);
203 if (!preg_match(
'/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/', $name)) {
220 Log::trace(
'createCrt called for: ' . $name . ($destPath ?
' (dest: ' . $destPath .
')' :
''));
223 Log::error(
'Invalid certificate name: ' . $name);
231 if (empty($destPath)) {
237 Log::error(
'Failed to ensure Root CA exists for: ' . $name);
249 $mkcertNames =
'"' . $name .
'"';
250 if ($name ===
'localhost') {
253 $mkcertNames .=
' "*.' . $name .
'" localhost ' .
APP_LOCALHOST .
' ::1';
256 Log::trace(
'Executing mkcert for "' . $name .
'"');
258 $batch .=
'"' . $mkcertExe .
'" -cert-file ' . $crtPath .
' -key-file ' . $keyPath .
' -- ' . $mkcertNames . PHP_EOL;
259 $batch .= $opensslExe .
" rsa -in " . $keyPath .
" -out " . $keyPath .
" -passin pass:" . PHP_EOL;
260 $batch .=
"COPY /Y " . $keyPath .
" " . $pubPath . PHP_EOL;
261 $batch .=
'IF EXIST ' . $crtPath .
' IF EXIST ' . $keyPath .
' ECHO OK' . PHP_EOL;
263 Log::trace(
'Creating SSL Certificate for "' . $name .
'" using mkcert. Batch content: ' . $batch);
267 Log::error(
'Batch execution failed for mkcert generation of "' . $name .
'". Check logs for createCertificateMkcert.');
274 if (trim($line) ===
'OK') {
281 Log::error(
'mkcert generation for "' . $name .
'" did not return OK. Output: ' . implode(
' | ',
$result));
283 Log::trace(
'mkcert generation for "' . $name .
'": ' . ($success ?
'SUCCESS' :
'FAILURE'));
303 if (!file_exists($rootCaPath)) {
304 Log::info(
'Root CA missing at ' . $rootCaPath .
'. Running mkcert -install');
306 $batch .=
'"' . $mkcertExe .
'" -install' . PHP_EOL;
311 Log::error(
'Batch execution failed for mkcert -install');
317 if (!file_exists($rootCaPath)) {
318 Log::error(
'Root CA still missing after mkcert -install at: ' . $rootCaPath);
322 Log::info(
'Root CA successfully created and verified.');
345 if (!file_exists($rootCaPath)) {
367 if ($this->trustPromptShownThisRun) {
375 @unlink($declinedFile);
381 if (file_exists($declinedFile)) {
382 Log::info(
'Skipping SSL Root CA trust prompt (previously declined).');
388 $this->trustPromptShownThisRun =
true;
396 if (is_object($bearsamppWinbinder) && method_exists($bearsamppWinbinder,
'messageBoxYesNo')) {
397 $trusted = (bool)$bearsamppWinbinder->messageBoxYesNo($message, $title);
399 Log::warning(
'WinBinder not available, cannot prompt to trust the SSL Root CA.');
403 Log::info(
'User declined to trust the SSL Root CA: ' . $rootCaPath);
404 file_put_contents($declinedFile, date(
'Y-m-d H:i:s') . PHP_EOL);
409 Log::info(
'User accepted to trust the SSL Root CA. Configuring browsers...');
411 @unlink($declinedFile);
413 if (is_object($bearsamppWinbinder) && method_exists($bearsamppWinbinder,
'messageBoxInfo')) {
414 $bearsamppWinbinder->messageBoxInfo(
432 $appData = getenv(
'APPDATA');
433 $localAppData = getenv(
'LOCALAPPDATA');
434 if (!empty($appData)) {
435 $bases[] = $appData .
'/Mozilla/Firefox/Profiles';
437 if (!empty($localAppData)) {
438 $bases[] = $localAppData .
'/Mozilla/Firefox/Profiles';
441 foreach ($bases as $base) {
442 if (!is_dir($base)) {
445 $entries = @scandir($base);
446 if ($entries ===
false) {
449 foreach ($entries as $entry) {
450 if ($entry ===
'.' || $entry ===
'..') {
453 $dir = $base .
'/' . $entry;
454 if (is_dir($dir) && file_exists($dir .
'/prefs.js')) {
460 return array_values(array_unique($dirs));
472 if (empty($profiles)) {
476 foreach ($profiles as $profile) {
477 $userJs = $profile .
'/user.js';
478 $content = file_exists($userJs) ? @file_get_contents($userJs) :
'';
479 if ($content ===
false) {
482 if (!preg_match(
'/user_pref\(\s*["\']security\.enterprise_roots\.enabled["\']\s*,\s*true\s*\)/i', $content)) {
501 if (empty($profiles)) {
502 Log::info(
'No Firefox profiles found. Skipping Firefox SSL Root CA configuration.');
507 foreach ($profiles as $profile) {
508 $userJs = $profile .
'/user.js';
509 $content = file_exists($userJs) ? @file_get_contents($userJs) :
'';
510 if ($content ===
false) {
513 $lines = $content ===
'' ? array() : explode(
"\n", str_replace(
"\r\n",
"\n", $content));
516 $lines = array_values(array_filter($lines,
function ($line) {
517 return preg_match(
'/security\.enterprise_roots\.enabled/i', trim($line)) ? false :
true;
520 $lines[] =
'user_pref("security.enterprise_roots.enabled", true);';
522 if (@file_put_contents($userJs, implode(
"\n", $lines) . PHP_EOL) !==
false) {
523 Log::info(
'Configured Firefox profile to trust system root certificates: ' . $profile);
525 Log::error(
'Failed to write Firefox user.js: ' . $userJs);
537 $initServerName =
'test.local';
540 $bearsamppWinbinder->reset();
543 $wbLabelName = $bearsamppWinbinder->createLabel($wbWindow,
$bearsamppLang->getValue(
Lang::NAME) .
' :', 15, 15, 85,
null, WBC_RIGHT);
544 $this->wbDelSslListCerts = $bearsamppWinbinder->createInputText($wbWindow, $initServerName, 105, 13, 150,
null);
546 $wbLabelDest = $bearsamppWinbinder->createLabel($wbWindow,
$bearsamppLang->getValue(
Lang::TARGET) .
' :', 15, 45, 85,
null, WBC_RIGHT);
547 $this->wbDelSslInputDest = $bearsamppWinbinder->createInputText($wbWindow, $initDocumentRoot, 105, 43, 190,
null,
null, WBC_READONLY);
550 $this->wbDelSslProgressBar = $bearsamppWinbinder->createProgressBar($wbWindow, 3, 15, 97, 275);
554 $bearsamppWinbinder->setHandler($wbWindow, $this,
'delSslCertificateHandler');
556 $bearsamppWinbinder->mainLoop();
557 $bearsamppWinbinder->reset();
577 if ($target && is_dir($target)) {
578 $bearsamppWinbinder->setText($this->wbDelSslInputDest[
WinBinder::CTRL_OBJ], $target .
'\\');
586 $existingCerts = $this->
getCrts();
587 if (!in_array($cert, $existingCerts)) {
588 $bearsamppWinbinder->messageBoxError(
596 $bearsamppWinbinder->setProgressBarMax($this->wbDelSslProgressBar, 3);
597 $bearsamppWinbinder->incrProgressBar($this->wbDelSslProgressBar);
600 if ($bearsamppOpenSsl->removeCrt($cert, $target)) {
601 $bearsamppWinbinder->incrProgressBar($this->wbDelSslProgressBar);
602 $bearsamppWinbinder->messageBoxInfo(
606 $bearsamppWinbinder->destroyWindow($window);
609 $bearsamppWinbinder->resetProgressBar($this->wbDelSslProgressBar);
615 $bearsamppWinbinder->destroyWindow($window);
632 return is_file($ppkPath) && is_file($crtPath);
642 $initServerName =
'test.local';
645 $bearsamppWinbinder->reset();
648 $wbLabelName = $bearsamppWinbinder->createLabel($wbWindow,
$bearsamppLang->getValue(
Lang::NAME) .
' :', 15, 15, 85,
null, WBC_RIGHT);
649 $this->wbGenSslInputName = $bearsamppWinbinder->createInputText($wbWindow, $initServerName, 105, 13, 150,
null);
651 $wbLabelDest = $bearsamppWinbinder->createLabel($wbWindow,
$bearsamppLang->getValue(
Lang::TARGET) .
' :', 15, 45, 85,
null, WBC_RIGHT);
652 $this->wbGenSslInputDest = $bearsamppWinbinder->createInputText($wbWindow, $initDocumentRoot, 105, 43, 190,
null,
null, WBC_READONLY);
655 $this->wbGenSslProgressBar = $bearsamppWinbinder->createProgressBar($wbWindow, 3, 15, 97, 275);
659 $bearsamppWinbinder->setHandler($wbWindow, $this,
'genSslCertificateHandler');
661 $bearsamppWinbinder->mainLoop();
662 $bearsamppWinbinder->reset();
682 if ($target && is_dir($target)) {
683 $bearsamppWinbinder->setText($this->wbGenSslInputDest[
WinBinder::CTRL_OBJ], $target .
'\\');
690 $bearsamppWinbinder->setProgressBarMax($this->wbGenSslProgressBar, 3);
691 $bearsamppWinbinder->incrProgressBar($this->wbGenSslProgressBar);
694 if ($bearsamppOpenSsl->createCrt($name, $target)) {
695 $bearsamppWinbinder->incrProgressBar($this->wbGenSslProgressBar);
696 $bearsamppWinbinder->messageBoxInfo(
700 $bearsamppWinbinder->destroyWindow($window);
703 $bearsamppWinbinder->resetProgressBar($this->wbGenSslProgressBar);
708 $bearsamppWinbinder->destroyWindow($window);
722 if (is_dir($sslPath)) {
723 $files = glob($sslPath .
'/*.crt');
724 if ($files !==
false) {
725 foreach ($files as $file) {
726 $certs[] = basename($file,
'.crt');
747 if (!is_file($crtPath)) {
748 Log::trace(
'SSL certificate file missing: ' . $crtPath);
753 if (!is_file($pubPath)) {
754 Log::trace(
'SSL public certificate file missing: ' . $pubPath);
759 if (!extension_loaded(
'openssl')) {
760 Log::warning(
'OpenSSL extension not loaded. Cannot parse certificate for expiry check. Assuming NOT expired if file exists.');
765 $crtContent = file_get_contents($crtPath);
766 if ($crtContent ===
false) {
767 Log::error(
'Could not read certificate file: ' . $crtPath);
772 $certInfo = openssl_x509_parse($crtContent);
773 if ($certInfo ===
false) {
774 Log::error(
'Could not parse certificate: ' . $crtPath .
'. OpenSSL error: ' . openssl_error_string());
779 if (isset($certInfo[
'validTo_time_t'])) {
780 $isExpired = $certInfo[
'validTo_time_t'] < time();
782 Log::trace(
'SSL certificate expired: ' . $name .
' (Expired on ' . date(
'Y-m-d H:i:s', $certInfo[
'validTo_time_t']) .
')');
788 Log::error(
'Could not find expiry date in certificate: ' . $crtPath);
803 if ($name ===
'localhost') {
804 Log::warning(
'Attempted to remove protected "localhost" certificate. Operation cancelled.');
811 if (!preg_match(
'/^[a-zA-Z0-9._-]+$/', $name)) {
812 Log::error(
'Invalid certificate name for removal: ' . $name);
817 $ppkPath = $destPath .
'/' . $name .
'.ppk';
818 $crtPath = $destPath .
'/' . $name .
'.crt';
819 $pubPath = $destPath .
'/' . $name .
'.pub';
821 Log::info(
'Removing SSL certificate: ' . $name .
' from ' . $destPath);
823 return @unlink($ppkPath) && @unlink($crtPath) && @unlink($pubPath);
static exec($basename, $content, $timeout=true, $catchOutput=true, $standalone=false, $silent=true, $rebuild=true)
const DELSSL_DELETED_ERROR
const SSL_TRUST_FIREFOX_MSG
const GENSSL_CREATED_ERROR
const SSL_TRUST_ROOT_CA_TITLE
const SSL_TRUST_ROOT_CA_DONE
const ERROR_FILE_NOT_FOUND
const SSL_TRUST_ROOT_CA_MSG
static info($data, $file=null)
static warning($data, $file=null)
static trace($data, $file=null)
static error($data, $file=null)
maybePromptTrustRootCa($rootCaPath, $force)
removeCrt($name, $destPath=null)
ensureRootCaExists($destPath)
ensureFirefoxTrustPrompt()
createCrt($name, $destPath=null)
delSslCertificateHandler($window, $id, $ctrl, $param1, $param2)
validateCertificateName($name)
isFirefoxEnterpriseRootsConfigured()
$firefoxTrustDeclinedFile
genSslCertificateHandler($window, $id, $ctrl, $param1, $param2)
trustRootCaInFirefox($rootCaPath)
static getSslPath($aetrayPath=false)
static getOpenSslExe($aetrayPath=false)
static getMkcertExe($aetrayPath=false)
static formatWindowsPath($path)
static formatUnixPath($path)
static getMkcertRootCaName()