@@ -325,20 +325,14 @@ public function getUID(): string {
325325 return str_replace (' ' , '+ ' , $ this ->UID );
326326 }
327327
328- public function expirity (): int {
329- $ expirity = $ this ->appConfig ->getValueInt (Application::APP_ID , 'expiry_in_days ' , 365 );
330- if ($ expirity < 0 ) {
331- return 365 ;
332- }
333- return $ expirity ;
334- }
335-
336- public function isSetupOk (): bool {
337- return strlen ($ this ->appConfig ->getValueString (Application::APP_ID , 'authkey ' , '' )) > 0 ;
328+ public function getLeafExpiryInDays (): int {
329+ $ exp = $ this ->appConfig ->getValueInt (Application::APP_ID , 'expiry_in_days ' , 365 );
330+ return $ exp > 0 ? $ exp : 365 ;
338331 }
339332
340- public function configureCheck (): array {
341- throw new \Exception ('Necessary to implement configureCheck method ' );
333+ public function getCaExpiryInDays (): int {
334+ $ exp = $ this ->appConfig ->getValueInt (Application::APP_ID , 'ca_expiry_in_days ' , 3650 ); // 10 years
335+ return $ exp > 0 ? $ exp : 3650 ;
342336 }
343337
344338 private function getCertificatePolicy (): array {
@@ -354,6 +348,136 @@ private function getCertificatePolicy(): array {
354348 return $ return ;
355349 }
356350
351+ abstract protected function getConfigureCheckResourceName (): string ;
352+
353+ abstract protected function getCertificateRegenerationTip (): string ;
354+
355+ abstract protected function getEngineSpecificChecks (): array ;
356+
357+ abstract protected function getSetupSuccessMessage (): string ;
358+
359+ abstract protected function getSetupErrorMessage (): string ;
360+
361+ abstract protected function getSetupErrorTip (): string ;
362+
363+ public function configureCheck (): array {
364+ $ checks = $ this ->getEngineSpecificChecks ();
365+
366+ if (!$ this ->isSetupOk ()) {
367+ return array_merge ($ checks , [
368+ (new ConfigureCheckHelper ())
369+ ->setErrorMessage ($ this ->getSetupErrorMessage ())
370+ ->setResource ($ this ->getConfigureCheckResourceName ())
371+ ->setTip ($ this ->getSetupErrorTip ())
372+ ]);
373+ }
374+
375+ $ checks [] = (new ConfigureCheckHelper ())
376+ ->setSuccessMessage ($ this ->getSetupSuccessMessage ())
377+ ->setResource ($ this ->getConfigureCheckResourceName ());
378+
379+ $ modernFeaturesCheck = $ this ->checkRootCertificateModernFeatures ();
380+ if ($ modernFeaturesCheck ) {
381+ $ checks [] = $ modernFeaturesCheck ;
382+ }
383+
384+ return $ checks ;
385+ }
386+
387+ protected function checkRootCertificateModernFeatures (): ?ConfigureCheckHelper {
388+ $ configPath = $ this ->getConfigPath ();
389+ $ caCertPath = $ configPath . DIRECTORY_SEPARATOR . 'ca.pem ' ;
390+
391+ try {
392+ $ certContent = file_get_contents ($ caCertPath );
393+ if (!$ certContent ) {
394+ return (new ConfigureCheckHelper ())
395+ ->setErrorMessage ('Failed to read root certificate file ' )
396+ ->setResource ($ this ->getConfigureCheckResourceName ())
397+ ->setTip ('Check file permissions and disk space ' );
398+ }
399+
400+ $ x509Resource = openssl_x509_read ($ certContent );
401+ if (!$ x509Resource ) {
402+ return (new ConfigureCheckHelper ())
403+ ->setErrorMessage ('Failed to parse root certificate ' )
404+ ->setResource ($ this ->getConfigureCheckResourceName ())
405+ ->setTip ('Root certificate file may be corrupted or invalid ' );
406+ }
407+
408+ $ parsed = openssl_x509_parse ($ x509Resource );
409+ if (!$ parsed ) {
410+ return (new ConfigureCheckHelper ())
411+ ->setErrorMessage ('Failed to extract root certificate information ' )
412+ ->setResource ($ this ->getConfigureCheckResourceName ())
413+ ->setTip ('Root certificate may be in an unsupported format ' );
414+ }
415+
416+ $ criticalIssues = [];
417+ $ minorIssues = [];
418+
419+ if (isset ($ parsed ['serialNumber ' ])) {
420+ $ serialNumber = $ parsed ['serialNumber ' ];
421+ $ serialDecimal = hexdec ($ serialNumber );
422+ if ($ serialDecimal <= 1 ) {
423+ $ minorIssues [] = 'Serial number is simple (zero or one) ' ;
424+ }
425+ } else {
426+ $ criticalIssues [] = 'Serial number is missing ' ;
427+ }
428+
429+ $ missingExtensions = [];
430+ if (!isset ($ parsed ['extensions ' ]['subjectKeyIdentifier ' ])) {
431+ $ missingExtensions [] = 'Subject Key Identifier (SKI) ' ;
432+ }
433+
434+ $ isSelfSigned = (isset ($ parsed ['issuer ' ]) && isset ($ parsed ['subject ' ])
435+ && $ parsed ['issuer ' ] === $ parsed ['subject ' ]);
436+
437+ /**
438+ * @todo workarround for missing AKI at certificates generated by CFSSL.
439+ *
440+ * CFSSL does not add Authority Key Identifier (AKI) to self-signed root certificates.
441+ */
442+ if (!$ isSelfSigned && !isset ($ parsed ['extensions ' ]['authorityKeyIdentifier ' ])) {
443+ $ missingExtensions [] = 'Authority Key Identifier (AKI) ' ;
444+ }
445+
446+ if (!isset ($ parsed ['extensions ' ]['crlDistributionPoints ' ])) {
447+ $ missingExtensions [] = 'CRL Distribution Points ' ;
448+ }
449+
450+ if (!empty ($ missingExtensions )) {
451+ $ extensionsList = implode (', ' , $ missingExtensions );
452+ $ minorIssues [] = "Missing modern extensions: {$ extensionsList }" ;
453+ }
454+
455+ if (!empty ($ criticalIssues )) {
456+ $ issuesList = implode (', ' , $ criticalIssues );
457+ return (new ConfigureCheckHelper ())
458+ ->setErrorMessage ("Root certificate has critical issues: {$ issuesList }" )
459+ ->setResource ($ this ->getConfigureCheckResourceName ())
460+ ->setTip ($ this ->getCertificateRegenerationTip ());
461+ }
462+
463+ if (!empty ($ minorIssues )) {
464+ $ issuesList = implode (', ' , $ minorIssues );
465+ return (new ConfigureCheckHelper ())
466+ ->setInfoMessage ("Root certificate could benefit from modern features: {$ issuesList }" )
467+ ->setResource ($ this ->getConfigureCheckResourceName ())
468+ ->setTip ($ this ->getCertificateRegenerationTip () . ' (recommended but not required) ' );
469+ }
470+
471+ return null ;
472+
473+ } catch (\Exception $ e ) {
474+ return (new ConfigureCheckHelper ())
475+ ->setErrorMessage ('Failed to analyze root certificate: ' . $ e ->getMessage ())
476+ ->setResource ($ this ->getConfigureCheckResourceName ())
477+ ->setTip ('Check if the root certificate file is valid ' );
478+ }
479+ }
480+
357481 public function toArray (): array {
358482 $ return = [
359483 'configPath ' => $ this ->getConfigPath (),
0 commit comments