/** * Renders the settings page. * * The settings page contains all UI for the plugin. The user can specify the settings for * his or her organization, then register the account with Let's Encrypt and then generate * a certificate. * * The settings page furthermore provides some additional settings to specify how the plugin * should behave. * * @since 1.0.0 * @access public */ public function render_page() { if (CoreUtil::needs_filesystem_credentials()) { ?> <div class="notice notice-warning"> <p><?php printf(__('The directories %1$s and %2$s that WP Encrypt needs access to are not automatically writable by the site. Unless you change this, it is not possible to auto-renew certificates.', 'wp-encrypt'), '<code>' . CoreUtil::get_letsencrypt_certificates_dir_path() . '</code>', '<code>' . CoreUtil::get_letsencrypt_challenges_dir_path() . '</code>'); ?> </p> <p><?php _e('Note that you can still manually renew certificates by providing valid filesystem credentials each time.', 'wp-encrypt'); ?> </p> </div> <?php } $account_registration_info = Util::get_registration_info('account'); $certificate_registration_info = Util::get_registration_info('certificate'); $account_registration_timestamp = false; $certificate_generation_timestamp = false; $site_domains = array(); if (isset($account_registration_info['createdAt'])) { $account_registration_timestamp = strtotime($account_registration_info['createdAt']) + HOUR_IN_SECONDS * get_option('gmt_offset'); } elseif (isset($account_registration_info['_wp_time'])) { $account_registration_timestamp = strtotime($account_registration_info['_wp_time']); } if (isset($certificate_registration_info['_wp_time'])) { $certificate_generation_timestamp = strtotime($certificate_registration_info['_wp_time']); if (isset($certificate_registration_info['domains'])) { $site_domains = $certificate_registration_info['domains']; } } $has_certificate = 0 < count($site_domains); $form_action = App::get_admin_action_file($this->context); $primary = 'save'; if (Util::get_option('valid')) { $primary = 'register'; if (Util::can_generate_certificate()) { $primary = 'generate'; } } ?> <style type="text/css"> .wp-encrypt-form { margin-bottom: 40px; } .remove, .reset { color: #aa0000; text-decoration: none; } .remove:hover, .reset:hover { color: #ff0000; text-decoration: none; border: none; } .remove { margin-left: 6px; line-height: 28px; } .danger-headline { color: #aa0000; } </style> <div class="wrap"> <h1><?php _e('WP Encrypt', 'wp-encrypt'); ?> </h1> <form class="wp-encrypt-form" method="post" action="<?php echo $form_action; ?> "> <?php settings_fields('wp_encrypt_settings'); ?> <?php do_settings_sections(self::PAGE_SLUG); ?> <?php submit_button('', ('save' === $primary ? 'primary' : 'secondary') . ' large', 'submit', false); ?> </form> <?php if (Util::get_option('valid')) { ?> <h2><?php _e('Let’s Encrypt Account', 'wp-encrypt'); ?> </h2> <form class="wp-encrypt-form" method="post" action="<?php echo $form_action; ?> "> <p class="description"> <?php _e('By clicking on this button, you will register an account for the above organization with Let’s Encrypt.', 'wp-encrypt'); ?> <?php printf(__('By registering, you verify that you have read and accepted the <a href="%s" target="_blank">Let’s Encrypt Subscriber Agreement</a>.', 'wp-encrypt'), Client::get()->get_license_url()); ?> <?php if ($account_registration_timestamp) { ?> <br /> <?php printf(__('Your account was registered on %1$s at %2$s.', 'wp-encrypt'), date_i18n(get_option('date_format'), $account_registration_timestamp), date_i18n(get_option('time_format'), $account_registration_timestamp)); ?> <?php } ?> </p> <?php $this->action_post_fields('wpenc_register_account'); ?> <?php submit_button(__('Register Account', 'wp-encrypt'), 'register' === $primary ? 'primary' : 'secondary', 'submit', false, array('id' => 'register-account-button')); ?> </form> <?php if (Util::can_generate_certificate()) { ?> <h2><?php _e('Let’s Encrypt Certificate', 'wp-encrypt'); ?> </h2> <form class="wp-encrypt-form" method="post" action="<?php echo $form_action; ?> "> <p class="description"> <?php _e('Here you can manage the actual certificate.', 'wp-encrypt'); ?> <?php if ('network' === $this->context) { ?> <?php _e('The certificate will be valid for all the sites in your network by default.', 'wp-encrypt'); ?> <?php } ?> <?php if ($certificate_generation_timestamp) { ?> <br /> <?php if ('network' === $this->context && 0 < count($site_domains)) { ?> <?php printf(__('Your certificate was last generated on %1$s at %2$s for the following domains: %3$s', 'wp-encrypt'), date_i18n(get_option('date_format'), $certificate_generation_timestamp), date_i18n(get_option('time_format'), $certificate_generation_timestamp), implode(', ', $site_domains)); ?> <?php } else { ?> <?php printf(__('Your certificate was last generated on %1$s at %2$s.', 'wp-encrypt'), date_i18n(get_option('date_format'), $certificate_generation_timestamp), date_i18n(get_option('time_format'), $certificate_generation_timestamp)); ?> <?php } ?> <?php } ?> </p> <?php $this->action_post_fields('wpenc_generate_certificate'); ?> <?php submit_button(__('Generate Certificate', 'wp-encrypt'), 'generate' === $primary ? 'primary' : 'secondary', 'submit', false, array('id' => 'generate-certificate-button')); ?> <?php if ($has_certificate) { ?> <a id="revoke-certificate-button" class="remove" href="<?php echo App::get_admin_action_url($this->context, 'wpenc_revoke_certificate'); ?> "><?php _e('Revoke Certificate', 'wp-encrypt'); ?> </a> <?php } ?> </form> <?php if ($has_certificate) { ?> <?php $site_domain = 'network' === $this->context ? Util::get_network_domain() : Util::get_site_domain(); $certificate_dirs = $this->get_certificate_dirs($site_domain); ?> <h3><?php _e('Certificate & Key Locations', 'wp-encrypt'); ?> </h4> <ul> <li><?php printf(__('Certificate: %s', 'wp-encrypt'), '<code>' . $certificate_dirs['cert'] . '</code>'); ?> </li> <li><?php printf(__('Certificate Chain: %s', 'wp-encrypt'), '<code>' . $certificate_dirs['chain'] . '</code>'); ?> </li> <li><?php printf(__('Certificate Full Chain: %s', 'wp-encrypt'), '<code>' . $certificate_dirs['fullchain'] . '</code>'); ?> </li> <li><?php printf(__('Private Key: %s', 'wp-encrypt'), '<code>' . $certificate_dirs['key'] . '</code>'); ?> </li> </ul> <p><?php _e('Please check the Help tabs at the top of this page to learn how to set up the SSL certificate.', 'wp-encrypt'); ?> </p> <?php } ?> <?php } ?> <?php } ?> <?php if (defined('WP_ENCRYPT_ENABLE_DANGER_ZONE') && WP_ENCRYPT_ENABLE_DANGER_ZONE && ($account_registration_timestamp || $certificate_generation_timestamp)) { ?> <h2 class="danger-headline"><?php _e('Danger Zone', 'wp-encrypt'); ?> </h2> <p> <?php _e('By clicking the button below, you will remove all existing certificate and key files permanently. Use this with extreme caution.', 'wp-encrypt'); ?> <br /> <strong><?php _e('Do not hit this button while your server is using any of those files.', 'wp-encrypt'); ?> </strong> </p> <a id="reset-button" class="reset" href="<?php echo App::get_admin_action_url($this->context, 'wpenc_reset'); ?> "><?php _e('Reset', 'wp-encrypt'); ?> </a> <?php } ?> </div> <?php // for AJAX /*if ( CoreUtil::needs_filesystem_credentials() ) { wp_print_request_filesystem_credentials_modal(); }*/ }
/** * Revokes a certificate with Let's Encrypt. * * @since 1.0.0 * @access public * * @param string $domain The root domain of the certificate to revoke. * @return bool|WP_Error True if the certificate was revoked successfully, an error object otherwise. */ public function revoke_certificate($domain) { $certificate = Certificate::get($domain); if (!$certificate->exists()) { return new WP_Error('cert_not_exist', sprintf(__('The certificate <code>%s</code> does not exist.', 'wp-encrypt'), $domain_path . '/cert.pem')); } $pem = $certificate->read(); if (is_wp_error($pem)) { return $pem; } $client = Client::get(); $result = $client->revoke($pem); if (is_wp_error($result)) { return $result; } if (200 !== $client->get_last_code()) { return $this->parse_wp_error($result, 'revoke_cert_invalid_response_code', __('Invalid response code for revoke certificate request.', 'wp-encrypt')); } return true; }
/** * Validates a domain with Let's Encrypt through a HTTP challenge. * * @since 1.0.0 * @access public * @static * * @param string $domain The domain to validate. * @param array $account_key_details The account private key details. * @return bool|WP_Error True if the domain was successfully validated, an error object otherwise. */ public static function validate($domain, $account_key_details) { $filesystem = Util::get_filesystem(); $status = Util::maybe_create_letsencrypt_challenges_dir(); if (is_wp_error($status)) { return $status; } $client = Client::get(); $response = $client->auth($domain); if (is_wp_error($response)) { return $response; } $challenge = array_reduce($response['challenges'], function ($v, $w) { if ($v) { return $v; } if ('http-01' === $w['type']) { return $w; } return false; }); if (!$challenge) { return new WP_Error('no_challenge_available', sprintf(__('No HTTP challenge available for domain %1$s. Original response: %2$s', 'wp-encrypt'), $domain, json_encode($response))); } $location = $client->get_last_location(); $directory = Util::get_letsencrypt_challenges_dir_path(); $token_path = $directory . '/' . $challenge['token']; if (!$filesystem->is_dir($directory) && !$filesystem->mkdir($directory, 0755, true)) { return new WP_Error('challenge_cannot_create_dir', sprintf(__('Could not create challenge directory <code>%s</code>. Please check your filesystem permissions.', 'wp-encrypt'), $directory)); } $header = array('e' => Util::base64_url_encode($account_key_details['rsa']['e']), 'kty' => 'RSA', 'n' => Util::base64_url_encode($account_key_details['rsa']['n'])); $data = $challenge['token'] . '.' . Util::base64_url_encode(hash('sha256', json_encode($header), true)); if (false === $filesystem->put_contents($token_path, $data)) { return new WP_Error('challenge_cannot_write_file', sprintf(__('Could not write challenge to file <code>%s</code>. Please check your filesystem permissions.', 'wp-encrypt'), $token_path)); } $filesystem->chmod($token_path, 0644); $response = wp_remote_get(Util::get_letsencrypt_challenges_dir_url() . '/' . $challenge['token']); if (is_wp_error($response)) { $filesystem->delete($token_path); return new WP_Error('challenge_request_failed', sprintf(__('Challenge request failed for domain %s.', 'wp-encrypt'), $domain)); } if ($data !== trim(wp_remote_retrieve_body($response))) { $filesystem->delete($token_path); return new WP_Error('challenge_self_check_failed', sprintf(__('Challenge self check failed for domain %s.', 'wp-encrypt'), $domain)); } $result = $client->challenge($challenge['uri'], $challenge['token'], $data); $done = false; do { if (empty($result['status']) || 'invalid' === $result['status']) { $filesystem->delete($token_path); return new WP_Error('challenge_remote_check_failed', sprintf(__('Challenge remote check failed for domain %s.', 'wp-encrypt'), $domain)); } $done = 'pending' !== $result['status']; if (!$done) { sleep(1); } $result = $client->request($location, 'GET'); if ('invalid' === $result['status']) { $filesystem->delete($token_path); return new WP_Error('challenge_remote_check_failed', sprintf(__('Challenge remote check failed for domain %s.', 'wp-encrypt'), $domain)); } } while (!$done); $filesystem->delete($token_path); return true; }