PluginProbe ʕ •ᴥ•ʔ
Easy HTTPS Redirection (SSL) / 2.0.0
Easy HTTPS Redirection (SSL) v2.0.0
trunk 1.5 1.6 1.8 1.9.1 1.9.2 2.0.0 2.0.1
https-redirection / classes / utilities / ehssl-ssl-utils.php
https-redirection / classes / utilities Last commit date
ehssl-ssl-utils.php 1 year ago ehssl-utils.php 1 year ago
ehssl-ssl-utils.php
291 lines
1 <?php
2
3 class EHSSL_SSL_Utils {
4
5 public static function get_current_domain() {
6 return parse_url( home_url(), PHP_URL_HOST );
7 }
8
9 /**
10 * Retrieves the SSL info if have any
11 *
12 * @return array|bool The SSL information array.
13 */
14 public static function get_ssl_info( $domain ) {
15 $cert_info = [];
16
17 $stream_context = stream_context_create( array(
18 "ssl" => array(
19 "capture_peer_cert" => true,
20 // "verify_peer" => false, // Disable verification for testing
21 // "verify_peer_name" => false, // Disable hostname verification
22 // "allow_self_signed" => true, // Allow self-signed certs
23 )
24 ) );
25
26 $err_str = '';
27 $client = @stream_socket_client( "ssl://" . $domain . ":443", $errno, $err_str, 60, STREAM_CLIENT_CONNECT, $stream_context );
28
29 if ( $client ) {
30 $cert = stream_context_get_params( $client );
31 $cert_info = openssl_x509_parse( $cert['options']['ssl']['peer_certificate'] );
32 }
33
34 if (!empty($err_str)){
35 EHSSL_Logger::log( $err_str, 4 );
36 }
37
38 return $cert_info;
39 }
40
41 public static function get_parsed_ssl_info($domain) {
42 $cert_info = self::get_ssl_info( $domain );
43
44 $parsed_cert_info = array();
45
46 if ( !empty($cert_info) ) {
47 $valid_from = $cert_info['validFrom_time_t'];
48 $valid_to = $cert_info['validTo_time_t'];
49
50 // Get certificate issuer.
51 $issuer_arr = array();
52 $issuer_arr[] = isset($cert_info['issuer']['O']) ? $cert_info['issuer']['O'] : '';
53 $issuer_arr[] = isset($cert_info['issuer']['CN']) ? $cert_info['issuer']['CN'] : '';
54 $issuer_arr[] = isset($cert_info['issuer']['C']) ? $cert_info['issuer']['C'] : '';
55 $issuer_arr = array_filter($issuer_arr);
56
57 if (empty($issuer_arr)){
58 $issuer = 'Unknown';
59 } else {
60 $issuer = implode(', ', $issuer_arr);
61 }
62
63 $subject = isset($cert_info['subject']['CN']) ? $cert_info['subject']['CN'] : $domain;
64 $cert_hash = md5( $issuer . $valid_from . $valid_to );
65 $id = substr( $cert_hash, 0, 7 ); // Generate a short ID for Expiry Certificate list table.
66
67 $parsed_cert_info = array(
68 'id' => $id,
69 'label' => $subject,
70 'issuer' => $issuer,
71 'issued_on' => $valid_from,
72 'expires_on' => $valid_to,
73 'cert_hash' => $cert_hash,
74 );
75 }
76
77 return $parsed_cert_info;
78 }
79
80 /**
81 * Get the parsed SSL info if any to display in the dashboard.
82 */
83 public static function get_parsed_current_ssl_info_for_dashbaord() {
84 $domain = self::get_current_domain();
85
86 $info = self::get_ssl_info( $domain );
87
88 if ( empty($info) ) {
89 return false;
90 }
91
92 $certinfo = array(
93 "Issued To" => array(
94 "Common Name (CN)" => isset( $info['subject']['CN'] ) ? $info['subject']['CN'] : "N/A",
95 "Organization (O)" => isset( $info['subject']['O'] ) ? $info['subject']['O'] : "N/A",
96 "Organizational Unit (OU)" => isset( $info['subject']['OU'] ) ? $info['subject']['OU'] : "N/A",
97 ),
98 "Issued By" => array(
99 "Common Name (CN)" => isset( $info['issuer']['CN'] ) ? $info['issuer']['CN'] : "N/A",
100 "Organization (O)" => isset( $info['issuer']['O'] ) ? $info['issuer']['O'] : "N/A",
101 "Organizational Unit (OU)" => isset( $info['issuer']['OU'] ) ? $info['issuer']['OU'] : "N/A",
102 "Country (C)" => isset( $info['issuer']['C'] ) ? $info['issuer']['C'] : "N/A",
103 ),
104 "Validity Period" => array(
105 "Issued On" => isset( $info['validFrom_time_t'] ) ? EHSSL_Utils::parse_timestamp( $info['validFrom_time_t'] ) : "N/A",
106 "Expires On" => isset( $info['validTo_time_t'] ) ? EHSSL_Utils::parse_timestamp( $info['validTo_time_t'] ) : "N/A",
107 ),
108 // "SHA-256 Fingerprint" => array(
109 // "Certificate" => "",
110 // "Public Key" => "",
111 // ),
112 );
113
114 return $certinfo;
115 }
116
117 public static function get_certificate_status( $expiry_timestamp ) {
118 $expiry = (new DateTime())->setTimestamp(intval($expiry_timestamp));
119 $now = new DateTime();
120 $diff = $now->diff( $expiry );
121
122 if ( $expiry < $now ) {
123 return 'expired';
124 } elseif ( $diff->days <= 7 ) {
125 return 'critical';
126 } elseif ( $diff->days <= 30 ) {
127 return 'warning';
128 } else {
129 return 'active';
130 }
131 }
132
133 public static function get_all_saved_certificates_info() {
134 $certs_info = get_posts(array(
135 'numberposts' => -1,
136 'post_type' => 'ehssl_certs_info',
137 ));
138
139 $data = [];
140 foreach ( $certs_info as $cert_info ) {
141 $data[] = array(
142 'id' => get_post_meta($cert_info->ID, 'id', true ),
143 'label' => get_post_meta($cert_info->ID, 'label', true ),
144 'issuer' => get_post_meta($cert_info->ID, 'issuer', true ),
145 'issued_on' => get_post_meta($cert_info->ID, 'issued_on', true ),
146 'expires_on' => get_post_meta($cert_info->ID, 'expires_on', true ),
147 );
148 }
149
150 return $data;
151 }
152
153 public static function check_and_save_current_cert_info() {
154 $domain = self::get_current_domain();
155
156 // Check if manual scan button was clicked. else the method ran using a cron event.
157 if (isset($_POST['ehssl_scan_for_ssl_submit'])){
158 EHSSL_Logger::log( 'Manually Scanning SSL certificate info for domain: ' . $domain);
159 }
160
161 $cert = self::get_parsed_ssl_info($domain);
162 if (empty($cert)){
163 // No ssl certificate found.
164 EHSSL_Logger::log( "No SSL certificate info found for your current domain '".$domain."' !", 1 );
165 return;
166 }
167
168 $cert_hash = isset($cert['cert_hash']) ? $cert['cert_hash'] : '';
169
170 // Save SSL info as cpt if not saved already.
171 $posts = get_posts( array(
172 'post_type' => 'ehssl_certs_info',
173 'title' => $cert_hash,
174 'posts_per_page' => 1, // We only need one post
175 'exact' => true, // Ensure an exact title match
176 'suppress_filters' => true, // Bypass filters for more predictable results
177 ) );
178
179 if ( empty( $posts ) ) {
180 EHSSL_Logger::log( 'Scanning for SSL certificate info...');
181
182 $post_id = wp_insert_post( array(
183 'post_title' => $cert_hash,
184 'post_content' => '',
185 'post_status' => 'publish',
186 'post_type' => 'ehssl_certs_info',
187 ) );
188
189 if ( is_wp_error( $post_id ) ) {
190 EHSSL_Logger::log($post_id->get_error_message(), 4);
191 return;
192 }
193
194 update_post_meta($post_id, 'id', $cert['id']);
195 update_post_meta($post_id, 'label', $cert['label']);
196 update_post_meta($post_id, 'issuer', $cert['issuer']);
197 update_post_meta($post_id, 'issued_on', $cert['issued_on']);
198 update_post_meta($post_id, 'expires_on', $cert['expires_on']);
199
200 EHSSL_Logger::log( 'New certificate info captured. ID: ' . $cert['id']);
201 } else {
202 EHSSL_Logger::log( 'Current SSL info already saved. No new SSL certificate info found.');
203 }
204 }
205
206 public static function check_and_send_notification_emails(){
207 $settings = get_option( 'httpsrdrctn_options', array());
208
209 $expiry_notification_enabled = isset( $settings['ehssl_enable_expiry_notification'] ) ? sanitize_text_field( $settings['ehssl_enable_expiry_notification'] ) : '';
210 $expiry_notification_email_before_days = isset( $settings['ehssl_expiry_notification_email_before_days'] ) ? sanitize_text_field( $settings['ehssl_expiry_notification_email_before_days'] ) : '';
211
212 if (empty($expiry_notification_enabled) || !is_numeric($expiry_notification_email_before_days)){
213 return;
214 }
215
216 $domain = self::get_current_domain();
217
218 $cert = self::get_parsed_ssl_info($domain);
219 if (empty($cert)){
220 // No SSL certificate found.
221 return;
222 }
223
224 EHSSL_Logger::log( 'Checking if certificate expiry notification email need to be sent...');
225
226 $expiry_timestamp = $cert['expires_on'];
227
228 $expiry = (new DateTime())->setTimestamp( $expiry_timestamp );
229 $now = new DateTime();
230 $diff = $now->diff( $expiry );
231
232 if ( $diff->days > intval($expiry_notification_email_before_days) ) {
233 // Still many days left for expiry. Nothing to do.
234 EHSSL_Logger::log( 'Certificate expiry date is more than ' . $expiry_notification_email_before_days . ' days away. No email will be sent.');
235 return;
236 }
237
238 $cert_hash = $cert['cert_hash'];
239 $posts = get_posts( array(
240 'post_type' => 'ehssl_certs_info',
241 'title' => $cert_hash,
242 'posts_per_page' => 1, // We only need one post
243 'exact' => true, // Ensure an exact title match
244 'suppress_filters' => true, // Bypass filters for more predictable results
245 ) );
246
247 $post = !empty($posts) ? $posts[0] : null;
248
249 // Check whether the notification email has already sent or not.
250 if (empty($post) || empty(get_post_meta($post->ID, 'expiry_notification_email_sent', true)) ){
251 // Notification email hasn't been sent yet. Send email now.
252 $is_sent = EHSSL_Email_handler::send_expiry_notification_email($cert);
253
254 update_post_meta($post->ID, 'expiry_notification_email_sent', $is_sent);
255 }
256 }
257
258 /**
259 * Should be used for debug purpose only.
260 */
261 public static function delete_all_certificate_info() {
262 global $wpdb;
263
264 $post_type = 'ehssl_certs_info';
265
266 // Query to get all post IDs of the specified custom post type.
267 $post_ids = $wpdb->get_col( $wpdb->prepare(
268 "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s",
269 $post_type
270 ) );
271
272 if ( $post_ids ) {
273 foreach ( $post_ids as $post_id ) {
274 /**
275 * wp_delete_post() permanently deletes a post.
276 * The second parameter, 'true', forces deletion bypassing the Trash.
277 */
278 wp_delete_post( $post_id, true );
279 }
280
281 EHSSL_Logger::log('SSL certificate info was deleted successfully.');
282
283 return true;
284 }
285
286 // No saved ssl certificates info found.
287 EHSSL_Logger::log('No saved SSL certificate info was detected for deletion.');
288 return false;
289 }
290
291 }