@@ -666,50 +666,76 @@ public function get_certificate_list() {
666666 return $ certificates ;
667667 }
668668
669- private $ _deny_list = null ;
669+ /** @var array|null */
670+ private $ _deny_list_domains = null ;
671+ /** @var array|null */
672+ private $ _deny_list_serials = null ;
670673
671674 private function get_deny_list () {
672675 global $ app , $ conf ;
673676
674- if (is_null ($ this ->_deny_list )) {
677+ if (is_null ($ this ->_deny_list_domains )) {
675678 $ server_db_record = $ app ->db ->queryOneRecord ("SELECT * FROM server WHERE server_id = ? " , $ conf ['server_id ' ]);
676679 $ app ->uses ('getconf ' );
677680 $ web_config = $ app ->getconf ->get_server_config ($ conf ['server_id ' ], 'web ' );
678681
679- $ this ->_deny_list = empty ($ web_config ['le_auto_cleanup_denylist ' ]) ? [] : array_filter (array_map (function ($ pattern ) use ($ server_db_record ) {
682+ $ this ->_deny_list_domains = empty ($ web_config ['le_auto_cleanup_denylist ' ]) ? [] : array_filter (array_map (function ($ pattern ) use ($ server_db_record ) {
680683 $ pattern = trim ($ pattern );
681684 if ($ server_db_record && $ pattern == '[server_name] ' ) {
682685 return $ server_db_record ['server_name ' ];
683686 }
684687
685688 return $ pattern ;
686689 }, explode (', ' , $ web_config ['le_auto_cleanup_denylist ' ])));
690+
691+ $ this ->_deny_list_domains = array_values (array_unique ($ this ->_deny_list_domains ));
692+
693+ // search certificates the installer creates and automatically add their serial numbers to deny list
694+ $ this ->_deny_list_serials = [];
695+ foreach ([
696+ '/usr/local/ispconfig/interface/ssl/ispserver.crt ' ,
697+ '/etc/postfix/smtpd.cert ' ,
698+ '/etc/ssl/private/pure-ftpd.pem '
699+ ] as $ possible_cert_file ) {
700+ $ cert = $ this ->extract_first_certificate ($ possible_cert_file );
701+ if ($ cert ) {
702+ $ info = $ this ->extract_x509 ($ cert );
703+ if ($ info ) {
704+ $ app ->log ('add serial number ' . $ info ['serial_number ' ] . ' from ' . $ possible_cert_file . ' to deny list ' , LOGLEVEL_DEBUG );
705+ $ this ->_deny_list_serials [] = $ info ['serial_number ' ];
706+ }
707+ }
708+ }
709+ $ this ->_deny_list_serials = array_values (array_unique ($ this ->_deny_list_serials ));
687710 }
688- return $ this ->_deny_list ;
711+ return [ $ this ->_deny_list_domains , $ this -> _deny_list_serials ] ;
689712 }
690713
691714 /**
692715 * Checks if $certificate is on the deny list or has a wildcard domain.
693- * Returns an array of the deny list patterns that matched the certificate.
716+ * Returns an array of the deny list patterns and serials numbers that matched the certificate.
694717 * An empty array means that the $certificate is not on the deny list.
695718 *
696719 * @param array $certificate
697720 * @return array
698721 */
699722 public function check_deny_list ($ certificate ) {
700- $ deny_list = $ this ->get_deny_list ();
723+ list ( $ deny_list_domains , $ deny_list_serials ) = $ this ->get_deny_list ();
701724 $ on_deny_list = [];
702725 foreach ($ certificate ['domains ' ] as $ cert_domain ) {
703726 if (substr ($ cert_domain , 0 , 2 ) == '*. ' ) {
704727 // wildcard domains are always on the deny list
705728 $ on_deny_list [] = $ cert_domain ;
706729 } else {
707- $ on_deny_list = array_merge ($ on_deny_list , array_filter ($ deny_list , function ($ deny_pattern ) use ($ cert_domain ) {
730+ $ on_deny_list = array_merge ($ on_deny_list , array_filter ($ deny_list_domains , function ($ deny_pattern ) use ($ cert_domain ) {
708731 return mb_strtolower ($ deny_pattern ) == mb_strtolower ($ cert_domain ) || fnmatch ($ deny_pattern , $ cert_domain , FNM_CASEFOLD );
709732 }));
710733 }
711734 }
712- return array_values (array_unique ($ on_deny_list ));
735+ if (in_array ($ certificate ['serial_number ' ], $ deny_list_serials , true )) {
736+ $ on_deny_list [] = $ certificate ['serial_number ' ];
737+ }
738+ return $ on_deny_list ;
713739 }
714740
715741 /**
@@ -728,6 +754,11 @@ public function remove_certificate($certificate, $revoke_before_delete = null, $
728754 $ revoke_before_delete = !empty ($ web_config ['le_revoke_before_delete ' ]) && $ web_config ['le_revoke_before_delete ' ] == 'y ' ;
729755 }
730756
757+ if ($ certificate ['is_revoked ' ] && $ revoke_before_delete ) {
758+ $ revoke_before_delete = false ;
759+ $ app ->log ('remove_certificate: skip revokation of ' . $ certificate ['id ' ] . ' because it already is revoked ' , LOGLEVEL_DEBUG );
760+ }
761+
731762 if ($ check_deny_list ) {
732763 $ on_deny_list = $ this ->check_deny_list ($ certificate );
733764 if (!empty ($ on_deny_list )) {
@@ -805,15 +836,20 @@ public function remove_certificate($certificate, $revoke_before_delete = null, $
805836 return true ;
806837 }
807838
808- public function extract_x509 ($ cert_file , $ chain_file = null ) {
839+ public function extract_x509 ($ cert_file_or_contents , $ chain_file = null ) {
809840 global $ app ;
810841 if (!function_exists ('openssl_x509_parse ' )) {
811842 $ app ->log ('extract_x509: openssl extension missing ' , LOGLEVEL_ERROR );
812843 return false ;
813844 }
814- $ info = openssl_x509_parse (file_get_contents ($ cert_file ), true );
845+ $ cert_file = false ;
846+ if (strpos ($ cert_file_or_contents , '-----BEGIN CERTIFICATE----- ' ) === false ) {
847+ $ cert_file = $ cert_file_or_contents ;
848+ $ cert_file_or_contents = file_get_contents ($ cert_file_or_contents );
849+ }
850+ $ info = openssl_x509_parse ($ cert_file_or_contents , true );
815851 if (!$ info ) {
816- $ app ->log ('extract_x509: ' . $ cert_file . ' could not be parsed ' , LOGLEVEL_ERROR );
852+ $ app ->log ('extract_x509: ' . ( $ cert_file ?: ' inline certificate ' ) . ' could not be parsed ' , LOGLEVEL_ERROR );
817853 return false ;
818854 }
819855 if (empty ($ info ['subject ' ]['CN ' ]) || !$ this ->is_domain_name_or_wildcard ($ info ['subject ' ]['CN ' ])) {
@@ -848,7 +884,7 @@ public function extract_x509($cert_file, $chain_file = null) {
848884 $ is_valid = $ valid_from <= $ now && $ now <= $ valid_to ;
849885 $ is_revoked = null ;
850886 // only do online revokation check when cert is valid and we got the required chain
851- if ($ is_valid && $ this ->is_readable_link_or_file ($ chain_file )) {
887+ if ($ is_valid && $ cert_file && $ this ->is_readable_link_or_file ($ chain_file )) {
852888 $ ocsp_uri = $ app ->system ->exec_safe ('openssl x509 -noout -ocsp_uri -in ? 2>&1 ' , $ cert_file );
853889 $ ocsp_host = parse_url ($ ocsp_uri ?: '' , PHP_URL_HOST );
854890 if ($ ocsp_uri && $ ocsp_host ) {
@@ -881,6 +917,21 @@ public function extract_x509($cert_file, $chain_file = null) {
881917 ];
882918 }
883919
920+ private function extract_first_certificate ($ file ) {
921+ if (!$ this ->is_readable_link_or_file ($ file )) {
922+ return false ;
923+ }
924+ $ contents = file_get_contents ($ file );
925+ if (!$ contents ) {
926+ return false ;
927+ }
928+ $ matches = [];
929+ if (!preg_match ('/-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/ms ' , $ contents , $ matches )) {
930+ return false ;
931+ }
932+ return $ matches [0 ];
933+ }
934+
884935 private function is_domain_name_or_wildcard ($ input ) {
885936 $ input = filter_var ($ input , FILTER_VALIDATE_DOMAIN );
886937 if (!$ input ) {
0 commit comments