Bearsampp 2026.9.23
Loading...
Searching...
No Matches
OpenSsl Class Reference

Public Member Functions

 createCrt ($name, $destPath=null)
 delSslCertificate ()
 delSslCertificateHandler ($window, $id, $ctrl, $param1, $param2)
 ensureFirefoxTrustPrompt ()
 existsCrt ($name)
 genSslCertificate ()
 genSslCertificateHandler ($window, $id, $ctrl, $param1, $param2)
 getCrts ()
 isExpired ($name)
 makeRootCa ()
 rebuildAllCerts ()
 removeCrt ($name, $destPath=null)

Private Member Functions

 ensureMkcertExeExists ()
 ensureRootCaExists ($destPath)
 ensureSslDirExists ()
 getFirefoxProfileDirs ()
 isFirefoxEnterpriseRootsConfigured ()
 maybePromptTrustRootCa ($rootCaPath, $force)
 trustRootCaInFirefox ($rootCaPath)
 validateCertificateName ($name)

Private Attributes

 $firefoxTrustDeclinedFile = 'firefoxTrustDeclined'
 $rootCaName = 'BearsamppRootCA'
 $trustPromptShownThisRun = false
 $wbDelSslBtnCancel
 $wbDelSslBtnDelete
 $wbDelSslBtnDest
 $wbDelSslInputDest
 $wbDelSslListCerts
 $wbDelSslProgressBar
 $wbGenSslBtnCancel
 $wbGenSslBtnDest
 $wbGenSslBtnSave
 $wbGenSslInputDest
 $wbGenSslInputName
 $wbGenSslProgressBar

Detailed Description

Class OpenSsl

Provides SSL certificate management for the Bearsampp environment. Generates, removes, and rebuilds SSL certificates using mkcert and configures browser trust for the Bearsampp Root CA.

Definition at line 18 of file class.openssl.php.

Member Function Documentation

◆ createCrt()

createCrt ( $name,
$destPath = null )

Creates a certificate with the specified name and destination path.

Parameters
string$nameThe name of the certificate.
string | null$destPathThe destination path where the certificate files will be saved. If null, the default SSL path is used.
Returns
bool True if the certificate was created successfully, false otherwise.

Definition at line 218 of file class.openssl.php.

219 {
220 Log::trace('createCrt called for: ' . $name . ($destPath ? ' (dest: ' . $destPath . ')' : ''));
221
222 if (!$this->validateCertificateName($name)) {
223 Log::error('Invalid certificate name: ' . $name);
224
225 return false;
226 }
227
228 if (!$this->ensureMkcertExeExists()) {
229 return false;
230 }
231 if (empty($destPath)) {
232 $destPath = $this->ensureSslDirExists();
233 }
234 $mkcertExe = Path::getMkcertExe();
235
236 if (!$this->ensureRootCaExists($destPath)) {
237 Log::error('Failed to ensure Root CA exists for: ' . $name);
238
239 return false;
240 }
241
242 $crtPath = '"' . Path::formatWindowsPath($destPath . '/' . $name . '.crt') . '"';
243 $pubPath = '"' . Path::formatWindowsPath($destPath . '/' . $name . '.pub') . '"';
244 $keyPath = '"' . Path::formatWindowsPath($destPath . '/' . $name . '.ppk') . '"'; // Using .ppk as requested in previous tasks
245 $opensslExe = '"' . Path::formatWindowsPath(Path::getOpenSslExe()) . '"';
246
247 $batch = 'SET "CAROOT=' . Path::formatWindowsPath($destPath) . '"' . PHP_EOL;
248
249 $mkcertNames = '"' . $name . '"';
250 if ($name === 'localhost') {
251 $mkcertNames .= ' ' . APP_LOCALHOST . ' ::1';
252 } else {
253 $mkcertNames .= ' "*.' . $name . '" localhost ' . APP_LOCALHOST . ' ::1';
254 }
255
256 Log::trace('Executing mkcert for "' . $name . '"');
257 // Use -- to terminate flag parsing before hostname arguments as defense-in-depth
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;
262
263 Log::trace('Creating SSL Certificate for "' . $name . '" using mkcert. Batch content: ' . $batch);
264 $result = Batch::exec('createCertificateMkcert', $batch);
265
266 if ($result === false || !is_array($result)) {
267 Log::error('Batch execution failed for mkcert generation of "' . $name . '". Check logs for createCertificateMkcert.');
268
269 return false;
270 }
271
272 $success = false;
273 foreach ($result as $line) {
274 if (trim($line) === 'OK') {
275 $success = true;
276 break;
277 }
278 }
279
280 if (!$success) {
281 Log::error('mkcert generation for "' . $name . '" did not return OK. Output: ' . implode(' | ', $result));
282 }
283 Log::trace('mkcert generation for "' . $name . '": ' . ($success ? 'SUCCESS' : 'FAILURE'));
284
285 return $success;
286 }
$result
static exec($basename, $content, $timeout=true, $catchOutput=true, $standalone=false, $silent=true, $rebuild=true)
static trace($data, $file=null)
static error($data, $file=null)
ensureRootCaExists($destPath)
ensureMkcertExeExists()
validateCertificateName($name)
ensureSslDirExists()
static getOpenSslExe($aetrayPath=false)
static getMkcertExe($aetrayPath=false)
static formatWindowsPath($path)
const APP_LOCALHOST
Definition root.php:21

References $result, APP_LOCALHOST, ensureMkcertExeExists(), ensureRootCaExists(), ensureSslDirExists(), Log\error(), Batch\exec(), Path\formatWindowsPath(), Path\getMkcertExe(), Path\getOpenSslExe(), Log\trace(), and validateCertificateName().

Referenced by makeRootCa(), and rebuildAllCerts().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ delSslCertificate()

delSslCertificate ( )

Displays a WinBinder GUI for deleting an SSL certificate.

Definition at line 533 of file class.openssl.php.

534 {
535 global $bearsamppLang, $bearsamppWinbinder;
536
537 $initServerName = 'test.local';
538 $initDocumentRoot = Path::formatWindowsPath(Path::getSslPath());
539
540 $bearsamppWinbinder->reset();
541 $wbWindow = $bearsamppWinbinder->createAppWindow($bearsamppLang->getValue(Lang::DELSSL_TITLE), 490, 160, WBC_NOTIFY, WBC_KEYDOWN | WBC_KEYUP);
542
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);
545
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);
548 $this->wbDelSslBtnDest = $bearsamppWinbinder->createButton($wbWindow, $bearsamppLang->getValue(Lang::BUTTON_BROWSE), 300, 43, 110);
549
550 $this->wbDelSslProgressBar = $bearsamppWinbinder->createProgressBar($wbWindow, 3, 15, 97, 275);
551 $this->wbDelSslBtnDelete = $bearsamppWinbinder->createButton($wbWindow, $bearsamppLang->getValue(Lang::BUTTON_DELETE), 300, 92);
552 $this->wbDelSslBtnCancel = $bearsamppWinbinder->createButton($wbWindow, $bearsamppLang->getValue(Lang::BUTTON_CANCEL), 387, 92);
553
554 $bearsamppWinbinder->setHandler($wbWindow, $this, 'delSslCertificateHandler');
555
556 $bearsamppWinbinder->mainLoop();
557 $bearsamppWinbinder->reset();
558 }
global $bearsamppLang
const NAME
const BUTTON_BROWSE
const BUTTON_CANCEL
const BUTTON_DELETE
const TARGET
const DELSSL_TITLE
static getSslPath($aetrayPath=false)

References $bearsamppLang, Lang\BUTTON_BROWSE, Lang\BUTTON_CANCEL, Lang\BUTTON_DELETE, Lang\DELSSL_TITLE, Path\formatWindowsPath(), Path\getSslPath(), Lang\NAME, and Lang\TARGET.

Here is the call graph for this function:

◆ delSslCertificateHandler()

delSslCertificateHandler ( $window,
$id,
$ctrl,
$param1,
$param2 )

Handler for the SSL certificate deletion WinBinder GUI.

Parameters
mixed$windowThe window object where the event occurred.
int$idThe ID of the event.
mixed$ctrlThe control that triggered the event.
mixed$param1The first parameter of the event.
mixed$param2The second parameter of the event.

Definition at line 569 of file class.openssl.php.

570 {
571 global $bearsamppLang, $bearsamppOpenSsl, $bearsamppWinbinder;
572
573 switch ($id) {
574 case $this->wbDelSslBtnDest[WinBinder::CTRL_ID]:
575 $target = $bearsamppWinbinder->getText($this->wbDelSslInputDest[WinBinder::CTRL_OBJ]);
576 $target = $bearsamppWinbinder->sysDlgPath($window, $bearsamppLang->getValue(Lang::GENSSL_PATH), $target);
577 if ($target && is_dir($target)) {
578 $bearsamppWinbinder->setText($this->wbDelSslInputDest[WinBinder::CTRL_OBJ], $target . '\\');
579 }
580 break;
581 case $this->wbDelSslBtnDelete[WinBinder::CTRL_ID]:
582 $cert = $bearsamppWinbinder->getText($this->wbDelSslListCerts[WinBinder::CTRL_OBJ]);
583 $target = $bearsamppWinbinder->getText($this->wbDelSslInputDest[WinBinder::CTRL_OBJ]);
584
585 if ($cert) {
586 $existingCerts = $this->getCrts();
587 if (!in_array($cert, $existingCerts)) {
588 $bearsamppWinbinder->messageBoxError(
589 sprintf($bearsamppLang->getValue(Lang::ERROR_FILE_NOT_FOUND), $cert, $target),
591 );
592
593 return;
594 }
595
596 $bearsamppWinbinder->setProgressBarMax($this->wbDelSslProgressBar, 3);
597 $bearsamppWinbinder->incrProgressBar($this->wbDelSslProgressBar);
598
599 $target = Path::formatUnixPath($target);
600 if ($bearsamppOpenSsl->removeCrt($cert, $target)) {
601 $bearsamppWinbinder->incrProgressBar($this->wbDelSslProgressBar);
602 $bearsamppWinbinder->messageBoxInfo(
603 sprintf($bearsamppLang->getValue(Lang::DELSSL_DELETED), $cert),
605 );
606 $bearsamppWinbinder->destroyWindow($window);
607 } else {
608 $bearsamppWinbinder->messageBoxError($bearsamppLang->getValue(Lang::DELSSL_DELETED_ERROR), $bearsamppLang->getValue(Lang::DELSSL_TITLE));
609 $bearsamppWinbinder->resetProgressBar($this->wbDelSslProgressBar);
610 }
611 }
612 break;
613 case IDCLOSE:
614 case $this->wbDelSslBtnCancel[WinBinder::CTRL_ID]:
615 $bearsamppWinbinder->destroyWindow($window);
616 break;
617 }
618 }
const GENSSL_PATH
const DELSSL_DELETED_ERROR
const DELSSL_DELETED
const ERROR_FILE_NOT_FOUND
static formatUnixPath($path)

References $bearsamppLang, WinBinder\CTRL_ID, WinBinder\CTRL_OBJ, Lang\DELSSL_DELETED, Lang\DELSSL_DELETED_ERROR, Lang\DELSSL_TITLE, Lang\ERROR_FILE_NOT_FOUND, Path\formatUnixPath(), Lang\GENSSL_PATH, and getCrts().

Here is the call graph for this function:

◆ ensureFirefoxTrustPrompt()

ensureFirefoxTrustPrompt ( )

Checks whether the SSL Root CA should be trusted, asking the user if necessary. Called during startup so that an existing (previously untrusted) Root CA can be trusted without having to regenerate it.

Returns
bool True if the Root CA exists and the trust state was checked.

Definition at line 341 of file class.openssl.php.

342 {
343 $destPath = Path::getSslPath();
344 $rootCaPath = $destPath . '/' . Path::getMkcertRootCaName();
345 if (!file_exists($rootCaPath)) {
346 return false;
347 }
348
349 $this->maybePromptTrustRootCa($rootCaPath, false);
350
351 return true;
352 }
maybePromptTrustRootCa($rootCaPath, $force)
static getMkcertRootCaName()

References Path\getMkcertRootCaName(), Path\getSslPath(), and maybePromptTrustRootCa().

Here is the call graph for this function:

◆ ensureMkcertExeExists()

ensureMkcertExeExists ( )
private

Ensures that the mkcert executable exists, attempting to download it if missing.

Returns
bool True if mkcert exists or was successfully downloaded.

Definition at line 58 of file class.openssl.php.

59 {
60 $mkcertExe = Path::getMkcertExe();
61 if (file_exists($mkcertExe)) {
62 return true;
63 }
64
65 Log::error('mkcert.exe missing at: ' . $mkcertExe . '. It must be fetched during build time (prepareBase/buildFull/buildLite).');
66
67 return false;
68 }

References Log\error(), and Path\getMkcertExe().

Referenced by createCrt(), ensureRootCaExists(), and makeRootCa().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ensureRootCaExists()

ensureRootCaExists ( $destPath)
private

Ensures that the Root CA exists, creating it if necessary.

Parameters
string$destPathThe destination path.
Returns
bool True if the Root CA exists or was created successfully.

Definition at line 295 of file class.openssl.php.

296 {
297 if (!$this->ensureMkcertExeExists()) {
298 return false;
299 }
300 $mkcertExe = Path::getMkcertExe();
301
302 $rootCaPath = $destPath . '/' . Path::getMkcertRootCaName(); // mkcert default root CA name
303 if (!file_exists($rootCaPath)) {
304 Log::info('Root CA missing at ' . $rootCaPath . '. Running mkcert -install');
305 $batch = 'SET "CAROOT=' . Path::formatWindowsPath($destPath) . '"' . PHP_EOL;
306 $batch .= '"' . $mkcertExe . '" -install' . PHP_EOL;
307 $batch .= 'IF EXIST "' . Path::formatWindowsPath($rootCaPath) . '" (ECHO OK)' . PHP_EOL;
308 $result = Batch::exec('mkcertInstall', $batch);
309
310 if ($result === false) {
311 Log::error('Batch execution failed for mkcert -install');
312
313 return false;
314 }
315
316 // Re-check after installation
317 if (!file_exists($rootCaPath)) {
318 Log::error('Root CA still missing after mkcert -install at: ' . $rootCaPath);
319
320 return false;
321 }
322 Log::info('Root CA successfully created and verified.');
323
324 // A brand new Root CA was created: ask the user whether to trust it
325 $this->maybePromptTrustRootCa($rootCaPath, true);
326 } else {
327 // Root CA already exists: if Firefox does not trust it yet, offer to configure it
328 $this->maybePromptTrustRootCa($rootCaPath, false);
329 }
330
331 return true;
332 }
static info($data, $file=null)

References $result, ensureMkcertExeExists(), Log\error(), Batch\exec(), Path\formatWindowsPath(), Path\getMkcertExe(), Path\getMkcertRootCaName(), Log\info(), and maybePromptTrustRootCa().

Referenced by createCrt().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ensureSslDirExists()

ensureSslDirExists ( )
private

Ensures that the SSL directory exists, creating it if necessary.

Returns
string The SSL path.

Definition at line 75 of file class.openssl.php.

76 {
77 $sslPath = Path::getSslPath();
78 if (!is_dir($sslPath)) {
79 Log::info('SSL directory missing, creating: ' . $sslPath);
80 if (mkdir($sslPath, 0700, true)) {
81 @chmod($sslPath, 0700);
82 // Create .gitignore if the directory was just created
83 $gitignorePath = $sslPath . '/.gitignore';
84 if (!file_exists($gitignorePath)) {
85 file_put_contents($gitignorePath, '# git holder' . PHP_EOL);
86 }
87 } else {
88 Log::error('Failed to create SSL directory: ' . $sslPath);
89 }
90 } else {
91 // Even if directory exists, ensure .gitignore is present
92 $gitignorePath = $sslPath . '/.gitignore';
93 if (!file_exists($gitignorePath)) {
94 file_put_contents($gitignorePath, '# git holder' . PHP_EOL);
95 }
96 }
97
98 if (!is_readable($sslPath) || !is_writable($sslPath)) {
99 Log::warning('SSL directory is not fully accessible. Attempting to relax permissions: ' . $sslPath);
100
101 // Set permissive permissions for local dev environment (0755 allows owner RWX, group/others RX)
102 @chmod($sslPath, 0755);
103 clearstatcache(true, $sslPath);
104
105 if (!is_readable($sslPath) || !is_writable($sslPath)) {
106 Log::error('SSL directory is still not readable/writable after permission adjustment: ' . $sslPath);
107 }
108 }
109
110 return $sslPath;
111 }
static warning($data, $file=null)

References Log\error(), Path\getSslPath(), Log\info(), and Log\warning().

Referenced by createCrt(), getCrts(), and removeCrt().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ existsCrt()

existsCrt ( $name)

Checks if a certificate with the specified name exists.

Parameters
string$nameThe name of the certificate.
Returns
bool True if the certificate exists, false otherwise.

Definition at line 627 of file class.openssl.php.

628 {
629 $ppkPath = Path::getSslPath() . '/' . $name . '.ppk';
630 $crtPath = Path::getSslPath() . '/' . $name . '.crt';
631
632 return is_file($ppkPath) && is_file($crtPath);
633 }

References Path\getSslPath().

Here is the call graph for this function:

◆ genSslCertificate()

genSslCertificate ( )

Displays a WinBinder GUI for generating an SSL certificate.

Definition at line 638 of file class.openssl.php.

639 {
640 global $bearsamppLang, $bearsamppWinbinder;
641
642 $initServerName = 'test.local';
643 $initDocumentRoot = Path::formatWindowsPath(Path::getSslPath());
644
645 $bearsamppWinbinder->reset();
646 $wbWindow = $bearsamppWinbinder->createAppWindow($bearsamppLang->getValue(Lang::GENSSL_TITLE), 490, 160, WBC_NOTIFY, WBC_KEYDOWN | WBC_KEYUP);
647
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);
650
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);
653 $this->wbGenSslBtnDest = $bearsamppWinbinder->createButton($wbWindow, $bearsamppLang->getValue(Lang::BUTTON_BROWSE), 300, 43, 110);
654
655 $this->wbGenSslProgressBar = $bearsamppWinbinder->createProgressBar($wbWindow, 3, 15, 97, 275);
656 $this->wbGenSslBtnSave = $bearsamppWinbinder->createButton($wbWindow, $bearsamppLang->getValue(Lang::BUTTON_SAVE), 300, 92);
657 $this->wbGenSslBtnCancel = $bearsamppWinbinder->createButton($wbWindow, $bearsamppLang->getValue(Lang::BUTTON_CANCEL), 387, 92);
658
659 $bearsamppWinbinder->setHandler($wbWindow, $this, 'genSslCertificateHandler');
660
661 $bearsamppWinbinder->mainLoop();
662 $bearsamppWinbinder->reset();
663 }
const GENSSL_TITLE
const BUTTON_SAVE

References $bearsamppLang, Lang\BUTTON_BROWSE, Lang\BUTTON_CANCEL, Lang\BUTTON_SAVE, Path\formatWindowsPath(), Lang\GENSSL_TITLE, Path\getSslPath(), Lang\NAME, and Lang\TARGET.

Here is the call graph for this function:

◆ genSslCertificateHandler()

genSslCertificateHandler ( $window,
$id,
$ctrl,
$param1,
$param2 )

Handler for the SSL certificate generation WinBinder GUI.

Parameters
mixed$windowThe window object where the event occurred.
int$idThe ID of the event.
mixed$ctrlThe control that triggered the event.
mixed$param1The first parameter of the event.
mixed$param2The second parameter of the event.

Definition at line 674 of file class.openssl.php.

675 {
676 global $bearsamppLang, $bearsamppOpenSsl, $bearsamppWinbinder;
677
678 switch ($id) {
679 case $this->wbGenSslBtnDest[WinBinder::CTRL_ID]:
680 $target = $bearsamppWinbinder->getText($this->wbGenSslInputDest[WinBinder::CTRL_OBJ]);
681 $target = $bearsamppWinbinder->sysDlgPath($window, $bearsamppLang->getValue(Lang::GENSSL_PATH), $target);
682 if ($target && is_dir($target)) {
683 $bearsamppWinbinder->setText($this->wbGenSslInputDest[WinBinder::CTRL_OBJ], $target . '\\');
684 }
685 break;
686 case $this->wbGenSslBtnSave[WinBinder::CTRL_ID]:
687 $name = $bearsamppWinbinder->getText($this->wbGenSslInputName[WinBinder::CTRL_OBJ]);
688 $target = $bearsamppWinbinder->getText($this->wbGenSslInputDest[WinBinder::CTRL_OBJ]);
689
690 $bearsamppWinbinder->setProgressBarMax($this->wbGenSslProgressBar, 3);
691 $bearsamppWinbinder->incrProgressBar($this->wbGenSslProgressBar);
692
693 $target = Path::formatUnixPath($target);
694 if ($bearsamppOpenSsl->createCrt($name, $target)) {
695 $bearsamppWinbinder->incrProgressBar($this->wbGenSslProgressBar);
696 $bearsamppWinbinder->messageBoxInfo(
697 sprintf($bearsamppLang->getValue(Lang::GENSSL_CREATED), $name),
699 );
700 $bearsamppWinbinder->destroyWindow($window);
701 } else {
702 $bearsamppWinbinder->messageBoxError($bearsamppLang->getValue(Lang::GENSSL_CREATED_ERROR), $bearsamppLang->getValue(Lang::GENSSL_TITLE));
703 $bearsamppWinbinder->resetProgressBar($this->wbGenSslProgressBar);
704 }
705 break;
706 case IDCLOSE:
707 case $this->wbGenSslBtnCancel[WinBinder::CTRL_ID]:
708 $bearsamppWinbinder->destroyWindow($window);
709 break;
710 }
711 }
const GENSSL_CREATED
const GENSSL_CREATED_ERROR

References $bearsamppLang, WinBinder\CTRL_ID, WinBinder\CTRL_OBJ, Path\formatUnixPath(), Lang\GENSSL_CREATED, Lang\GENSSL_CREATED_ERROR, Lang\GENSSL_PATH, and Lang\GENSSL_TITLE.

Here is the call graph for this function:

◆ getCrts()

getCrts ( )

Retrieves existing certificates from the SSL directory.

Returns
array List of certificate names.

Definition at line 718 of file class.openssl.php.

719 {
720 $sslPath = $this->ensureSslDirExists();
721 $certs = [];
722 if (is_dir($sslPath)) {
723 $files = glob($sslPath . '/*.crt');
724 if ($files !== false) {
725 foreach ($files as $file) {
726 $certs[] = basename($file, '.crt');
727 }
728 }
729 }
730 sort($certs);
731
732 return $certs;
733 }

References ensureSslDirExists().

Referenced by delSslCertificateHandler(), and rebuildAllCerts().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ getFirefoxProfileDirs()

getFirefoxProfileDirs ( )
private

Finds the Firefox profile directories that contain a prefs.js file.

Returns
array List of absolute paths to Firefox profiles.

Definition at line 428 of file class.openssl.php.

429 {
430 $dirs = array();
431 $bases = array();
432 $appData = getenv('APPDATA');
433 $localAppData = getenv('LOCALAPPDATA');
434 if (!empty($appData)) {
435 $bases[] = $appData . '/Mozilla/Firefox/Profiles';
436 }
437 if (!empty($localAppData)) {
438 $bases[] = $localAppData . '/Mozilla/Firefox/Profiles';
439 }
440
441 foreach ($bases as $base) {
442 if (!is_dir($base)) {
443 continue;
444 }
445 $entries = @scandir($base);
446 if ($entries === false) {
447 continue;
448 }
449 foreach ($entries as $entry) {
450 if ($entry === '.' || $entry === '..') {
451 continue;
452 }
453 $dir = $base . '/' . $entry;
454 if (is_dir($dir) && file_exists($dir . '/prefs.js')) {
455 $dirs[] = $dir;
456 }
457 }
458 }
459
460 return array_values(array_unique($dirs));
461 }

Referenced by isFirefoxEnterpriseRootsConfigured(), and trustRootCaInFirefox().

Here is the caller graph for this function:

◆ isExpired()

isExpired ( $name)

Checks if a certificate with the specified name is expired or about to expire.

Parameters
string$nameThe name of the certificate.
Returns
bool True if the certificate is expired or missing, false otherwise.

Definition at line 742 of file class.openssl.php.

743 {
744 $crtPath = Path::getSslPath() . '/' . $name . '.crt';
745 $pubPath = Path::getSslPath() . '/' . $name . '.pub';
746
747 if (!is_file($crtPath)) {
748 Log::trace('SSL certificate file missing: ' . $crtPath);
749
750 return true;
751 }
752
753 if (!is_file($pubPath)) {
754 Log::trace('SSL public certificate file missing: ' . $pubPath);
755
756 return true;
757 }
758
759 if (!extension_loaded('openssl')) {
760 Log::warning('OpenSSL extension not loaded. Cannot parse certificate for expiry check. Assuming NOT expired if file exists.');
761
762 return false;
763 }
764
765 $crtContent = file_get_contents($crtPath);
766 if ($crtContent === false) {
767 Log::error('Could not read certificate file: ' . $crtPath);
768
769 return true;
770 }
771
772 $certInfo = openssl_x509_parse($crtContent);
773 if ($certInfo === false) {
774 Log::error('Could not parse certificate: ' . $crtPath . '. OpenSSL error: ' . openssl_error_string());
775
776 return true;
777 }
778
779 if (isset($certInfo['validTo_time_t'])) {
780 $isExpired = $certInfo['validTo_time_t'] < time();
781 if ($isExpired) {
782 Log::trace('SSL certificate expired: ' . $name . ' (Expired on ' . date('Y-m-d H:i:s', $certInfo['validTo_time_t']) . ')');
783 }
784
785 return $isExpired;
786 }
787
788 Log::error('Could not find expiry date in certificate: ' . $crtPath);
789
790 return true;
791 }

References Log\error(), Path\getSslPath(), Log\trace(), and Log\warning().

Here is the call graph for this function:

◆ isFirefoxEnterpriseRootsConfigured()

isFirefoxEnterpriseRootsConfigured ( )
private

Checks whether all found Firefox profiles are configured to trust the system root certificates (security.enterprise_roots.enabled).

Returns
bool True if all profiles are configured, or if no Firefox profiles were found.

Definition at line 469 of file class.openssl.php.

470 {
471 $profiles = $this->getFirefoxProfileDirs();
472 if (empty($profiles)) {
473 return true;
474 }
475
476 foreach ($profiles as $profile) {
477 $userJs = $profile . '/user.js';
478 $content = file_exists($userJs) ? @file_get_contents($userJs) : '';
479 if ($content === false) {
480 $content = '';
481 }
482 if (!preg_match('/user_pref\‍(\s*["\']security\.enterprise_roots\.enabled["\']\s*,\s*true\s*\‍)/i', $content)) {
483 return false;
484 }
485 }
486
487 return true;
488 }
getFirefoxProfileDirs()

References getFirefoxProfileDirs().

Referenced by maybePromptTrustRootCa().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ makeRootCa()

makeRootCa ( )

Creates a new Root CA and reinstalls it, then rebuilds all certificates.

Returns
bool True if successful.

Definition at line 118 of file class.openssl.php.

119 {
120 if (!$this->ensureMkcertExeExists()) {
121 return false;
122 }
123 $destPath = Path::getSslPath();
124 $mkcertExe = Path::getMkcertExe();
125
126 Log::info('Creating new Root CA and installing it...');
127
128 $rootCaPath = Path::getSslPath() . '/' . Path::getMkcertRootCaName();
129 $batch = 'SET "CAROOT=' . Path::formatWindowsPath(Path::getSslPath()) . '"' . PHP_EOL;
130 $batch .= '"' . $mkcertExe . '" -uninstall' . PHP_EOL;
131 $batch .= '"' . $mkcertExe . '" -install' . PHP_EOL;
132 $batch .= 'IF EXIST "' . Path::formatWindowsPath($rootCaPath) . '" (ECHO OK)' . PHP_EOL;
133
134 // Wait for the Root CA file to appear or timeout
135 $result = Batch::exec('mkcertMakeRootCa', $batch);
136
137 if (!file_exists($rootCaPath)) {
138 Log::error('Failed to create Root CA file at: ' . $rootCaPath);
139
140 return false;
141 }
142
143 // Display info about the new Root CA
144 $batch = 'SET "CAROOT=' . Path::formatWindowsPath(Path::getSslPath()) . '"' . PHP_EOL;
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]);
149 }
150
151 // A brand new Root CA was created: ask the user whether to trust it
152 $this->maybePromptTrustRootCa($rootCaPath, true);
153
154 Log::info('Root CA created. Rebuilding all existing certificates and ensuring localhost exists...');
155
156 // Ensure localhost is created/rebuilt
157 $this->createCrt('localhost');
158
159 return $this->rebuildAllCerts();
160 }
createCrt($name, $destPath=null)

References $result, createCrt(), ensureMkcertExeExists(), Log\error(), Batch\exec(), Path\formatWindowsPath(), Path\getMkcertExe(), Path\getMkcertRootCaName(), Path\getSslPath(), Log\info(), maybePromptTrustRootCa(), and rebuildAllCerts().

Here is the call graph for this function:

◆ maybePromptTrustRootCa()

maybePromptTrustRootCa ( $rootCaPath,
$force )
private

Asks the user whether to trust the SSL Root CA and, if accepted, configures the browsers to trust it. The prompt is shown at most once per run.

Parameters
string$rootCaPathThe path to the Root CA certificate file.
bool$forceTrue when a brand new Root CA was just created, false when checking an existing one.
Returns
bool True if the user accepted and the trust was configured, false otherwise.

Definition at line 363 of file class.openssl.php.

364 {
365 global $bearsamppLang, $bearsamppWinbinder;
366
367 if ($this->trustPromptShownThisRun) {
368 return false;
369 }
370
371 $declinedFile = Path::getSslPath() . '/' . $this->firefoxTrustDeclinedFile;
372
373 if ($force) {
374 // A brand new Root CA was created: forget any previous decline and offer trust again
375 @unlink($declinedFile);
376 } else {
377 // Existing Root CA: only offer to configure Firefox if it is not configured yet
379 return false;
380 }
381 if (file_exists($declinedFile)) {
382 Log::info('Skipping SSL Root CA trust prompt (previously declined).');
383
384 return false;
385 }
386 }
387
388 $this->trustPromptShownThisRun = true;
389
390 $message = $force
394
395 $trusted = false;
396 if (is_object($bearsamppWinbinder) && method_exists($bearsamppWinbinder, 'messageBoxYesNo')) {
397 $trusted = (bool)$bearsamppWinbinder->messageBoxYesNo($message, $title);
398 } else {
399 Log::warning('WinBinder not available, cannot prompt to trust the SSL Root CA.');
400 }
401
402 if (!$trusted) {
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);
405
406 return false;
407 }
408
409 Log::info('User accepted to trust the SSL Root CA. Configuring browsers...');
410 $this->trustRootCaInFirefox($rootCaPath);
411 @unlink($declinedFile);
412
413 if (is_object($bearsamppWinbinder) && method_exists($bearsamppWinbinder, 'messageBoxInfo')) {
414 $bearsamppWinbinder->messageBoxInfo(
416 $title
417 );
418 }
419
420 return true;
421 }
const SSL_TRUST_FIREFOX_MSG
const SSL_TRUST_ROOT_CA_TITLE
const SSL_TRUST_ROOT_CA_DONE
const SSL_TRUST_ROOT_CA_MSG
isFirefoxEnterpriseRootsConfigured()
$firefoxTrustDeclinedFile
trustRootCaInFirefox($rootCaPath)

References $bearsamppLang, $firefoxTrustDeclinedFile, Path\getSslPath(), Log\info(), isFirefoxEnterpriseRootsConfigured(), Lang\SSL_TRUST_FIREFOX_MSG, Lang\SSL_TRUST_ROOT_CA_DONE, Lang\SSL_TRUST_ROOT_CA_MSG, Lang\SSL_TRUST_ROOT_CA_TITLE, trustRootCaInFirefox(), and Log\warning().

Referenced by ensureFirefoxTrustPrompt(), ensureRootCaExists(), and makeRootCa().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ rebuildAllCerts()

rebuildAllCerts ( )

Rebuilds all existing certificates in the SSL directory.

Returns
bool True if all certificates were rebuilt successfully.

Definition at line 167 of file class.openssl.php.

168 {
169 $certs = $this->getCrts();
170 $success = true;
171
172 foreach ($certs as $cert) {
173 Log::info('Rebuilding certificate: ' . $cert);
174 if (!$this->createCrt($cert)) {
175 Log::error('Failed to rebuild certificate: ' . $cert);
176 $success = false;
177 }
178 }
179
180 return $success;
181 }

References createCrt(), Log\error(), getCrts(), and Log\info().

Referenced by makeRootCa().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ removeCrt()

removeCrt ( $name,
$destPath = null )

Removes a certificate with the specified name.

Parameters
string$nameThe name of the certificate.
string | null$destPathThe destination path where the certificate files are saved. If null, the default SSL path is used.
Returns
bool True if the certificate was removed successfully, false otherwise.

Definition at line 801 of file class.openssl.php.

802 {
803 if ($name === 'localhost') {
804 Log::warning('Attempted to remove protected "localhost" certificate. Operation cancelled.');
805
806 return false;
807 }
808 $destPath = empty($destPath) ? $this->ensureSslDirExists() : $destPath;
809
810 // Basic validation for name to prevent arbitrary file deletion
811 if (!preg_match('/^[a-zA-Z0-9._-]+$/', $name)) {
812 Log::error('Invalid certificate name for removal: ' . $name);
813
814 return false;
815 }
816
817 $ppkPath = $destPath . '/' . $name . '.ppk';
818 $crtPath = $destPath . '/' . $name . '.crt';
819 $pubPath = $destPath . '/' . $name . '.pub';
820
821 Log::info('Removing SSL certificate: ' . $name . ' from ' . $destPath);
822
823 return @unlink($ppkPath) && @unlink($crtPath) && @unlink($pubPath);
824 }

References ensureSslDirExists(), Log\error(), Log\info(), and Log\warning().

Here is the call graph for this function:

◆ trustRootCaInFirefox()

trustRootCaInFirefox ( $rootCaPath)
private

Configures all found Firefox profiles to trust the system root certificates, which makes HTTPS sites signed by the Bearsampp Root CA display the secure lock.

Parameters
string$rootCaPathThe path to the Root CA certificate file.
Returns
void

Definition at line 498 of file class.openssl.php.

499 {
500 $profiles = $this->getFirefoxProfileDirs();
501 if (empty($profiles)) {
502 Log::info('No Firefox profiles found. Skipping Firefox SSL Root CA configuration.');
503
504 return;
505 }
506
507 foreach ($profiles as $profile) {
508 $userJs = $profile . '/user.js';
509 $content = file_exists($userJs) ? @file_get_contents($userJs) : '';
510 if ($content === false) {
511 $content = '';
512 }
513 $lines = $content === '' ? array() : explode("\n", str_replace("\r\n", "\n", $content));
514
515 // Remove any existing enterprise_roots pref so we only keep the trusted one
516 $lines = array_values(array_filter($lines, function ($line) {
517 return preg_match('/security\.enterprise_roots\.enabled/i', trim($line)) ? false : true;
518 }));
519
520 $lines[] = 'user_pref("security.enterprise_roots.enabled", true);';
521
522 if (@file_put_contents($userJs, implode("\n", $lines) . PHP_EOL) !== false) {
523 Log::info('Configured Firefox profile to trust system root certificates: ' . $profile);
524 } else {
525 Log::error('Failed to write Firefox user.js: ' . $userJs);
526 }
527 }
528 }

References Log\error(), getFirefoxProfileDirs(), and Log\info().

Referenced by maybePromptTrustRootCa().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ validateCertificateName()

validateCertificateName ( $name)
private

Validates the certificate name to prevent command injection attacks. Requires names to start with alphanumeric character to prevent mkcert flag injection. Uses filesystem-safe whitelist (same as removeCrt) to support existing cert names and prevent CMD metacharacter injection. Allows: alphanumeric, dots, dashes, underscores.

Parameters
string$nameThe certificate name to validate.
Returns
bool True if the name is valid, false otherwise.

Definition at line 193 of file class.openssl.php.

194 {
195 if (empty($name)) {
196 return false;
197 }
198
199 // Filesystem-safe whitelist with mandatory alphanumeric first character:
200 // - Prevents CLI flag injection (leading `-`)
201 // - Prevents relative path traversal (leading `.`)
202 // - Allows remaining chars to be alphanumeric, dots, dashes, underscores
203 if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/', $name)) {
204 return false;
205 }
206
207 return true;
208 }

Referenced by createCrt().

Here is the caller graph for this function:

Field Documentation

◆ $firefoxTrustDeclinedFile

$firefoxTrustDeclinedFile = 'firefoxTrustDeclined'
private

Definition at line 50 of file class.openssl.php.

Referenced by maybePromptTrustRootCa().

◆ $rootCaName

$rootCaName = 'BearsamppRootCA'
private

Definition at line 46 of file class.openssl.php.

◆ $trustPromptShownThisRun

$trustPromptShownThisRun = false
private

Definition at line 48 of file class.openssl.php.

◆ $wbDelSslBtnCancel

$wbDelSslBtnCancel
private

Definition at line 44 of file class.openssl.php.

◆ $wbDelSslBtnDelete

$wbDelSslBtnDelete
private

Definition at line 42 of file class.openssl.php.

◆ $wbDelSslBtnDest

$wbDelSslBtnDest
private

Definition at line 38 of file class.openssl.php.

◆ $wbDelSslInputDest

$wbDelSslInputDest
private

Definition at line 36 of file class.openssl.php.

◆ $wbDelSslListCerts

$wbDelSslListCerts
private

Definition at line 34 of file class.openssl.php.

◆ $wbDelSslProgressBar

$wbDelSslProgressBar
private

Definition at line 40 of file class.openssl.php.

◆ $wbGenSslBtnCancel

$wbGenSslBtnCancel
private

Definition at line 31 of file class.openssl.php.

◆ $wbGenSslBtnDest

$wbGenSslBtnDest
private

Definition at line 25 of file class.openssl.php.

◆ $wbGenSslBtnSave

$wbGenSslBtnSave
private

Definition at line 29 of file class.openssl.php.

◆ $wbGenSslInputDest

$wbGenSslInputDest
private

Definition at line 23 of file class.openssl.php.

◆ $wbGenSslInputName

$wbGenSslInputName
private

Definition at line 21 of file class.openssl.php.

◆ $wbGenSslProgressBar

$wbGenSslProgressBar
private

Definition at line 27 of file class.openssl.php.


The documentation for this class was generated from the following file: