$site_url ) ); } // Check certificate validity. $cert_info = self::get_certificate_info(); if ( is_wp_error( $cert_info ) ) { return static::warning( __( 'Your site uses HTTPS, but we couldn\'t verify your SSL certificate details. This might be a temporary issue.', '0-day-analytics' ), __( 'Try refreshing the scan. If the issue persists, contact your hosting provider to verify your SSL certificate is properly installed.', '0-day-analytics' ), array( 'error' => $cert_info->get_error_message() ) ); } // Check if certificate is expiring soon (within 30 days). $days_until_expiry = $cert_info['days_until_expiry']; if ( $days_until_expiry <= 0 ) { return static::fail( 'critical', __( 'Your SSL certificate has expired! Visitors will see security warnings when accessing your site, and browsers may block access entirely.', '0-day-analytics' ), __( 'Contact your hosting provider immediately to renew your SSL certificate. If you\'re using Let\'s Encrypt, check that auto-renewal is working.', '0-day-analytics' ), $cert_info ); } if ( $days_until_expiry <= 14 ) { return static::warning( sprintf( /* translators: %d: number of days */ __( 'Your SSL certificate expires in %d days. If it\'s not renewed, visitors will see security warnings.', '0-day-analytics' ), $days_until_expiry ), __( 'Contact your hosting provider to ensure your SSL certificate will be renewed. Most providers auto-renew certificates, but it\'s good to verify.', '0-day-analytics' ), $cert_info ); } if ( $days_until_expiry <= 30 ) { return static::info( sprintf( /* translators: %d: number of days */ __( 'Your site is secure with HTTPS. Your SSL certificate expires in %d days - make sure auto-renewal is enabled.', '0-day-analytics' ), $days_until_expiry ), $cert_info ); } return static::pass( sprintf( /* translators: %d: number of days */ __( 'Your site is secure with HTTPS. Your SSL certificate is valid for another %d days.', '0-day-analytics' ), $days_until_expiry ), $cert_info ); } /** * Get SSL certificate information. * * @return array|\WP_Error Certificate info or error. * * @since 4.9.0 */ private static function get_certificate_info() { $site_url = get_site_url(); $parsed = wp_parse_url( $site_url ); $host = $parsed['host'] ?? ''; if ( empty( $host ) ) { return new \WP_Error( 'invalid_host', __( 'Could not determine site hostname.', '0-day-analytics' ) ); } // Validate hostname format to prevent socket injection. if ( ! preg_match( '/^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$/', $host ) ) { return new \WP_Error( 'invalid_host', __( 'Invalid hostname format.', '0-day-analytics' ) ); } $context = stream_context_create( array( 'ssl' => array( 'capture_peer_cert' => true, 'verify_peer' => false, 'verify_peer_name' => false, ), ) ); $socket = @stream_socket_client( "ssl://{$host}:443", $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $context ); if ( ! $socket ) { return new \WP_Error( 'connection_failed', $errstr ); } $params = stream_context_get_params( $socket ); fclose( $socket ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing a stream socket, not a file. if ( empty( $params['options']['ssl']['peer_certificate'] ) ) { return new \WP_Error( 'no_certificate', __( 'No certificate found.', '0-day-analytics' ) ); } $cert = openssl_x509_parse( $params['options']['ssl']['peer_certificate'] ); $valid_to = $cert['validTo_time_t'] ?? 0; return array( 'issuer' => $cert['issuer']['O'] ?? $cert['issuer']['CN'] ?? __( 'Unknown', '0-day-analytics' ), 'valid_from' => gmdate( 'Y-m-d', $cert['validFrom_time_t'] ?? 0 ), 'valid_to' => gmdate( 'Y-m-d', $valid_to ), 'days_until_expiry' => (int) floor( ( $valid_to - time() ) / DAY_IN_SECONDS ), ); } } }