/** * Generates a CSR for one or more domains. * * @since 1.0.0 * @access public * * @param resource $key_resource The private key resource for the CSR. * @param array $domains Array of domains the CSR should be created for. * @param array $dn Array of CSR settings. It should have the array keys * 'ST' (for country), 'C' (for two-letter country code) * and 'O' (for organization name). * @return string|WP_Error The generated CSR if successful or an error object otherwise. */ public function generate_csr($key_resource, $domains, $dn = array()) { $filesystem = Util::get_filesystem(); $status = Util::maybe_create_letsencrypt_certificates_dir(); if (is_wp_error($status)) { return $status; } $san = implode(',', array_map(array($this, 'dnsify'), $domains)); $output = 'HOME = . RANDFILE = $ENV::HOME/.rnd [ req ] default_bits = 2048 default_keyfile = privkey.pem distinguished_name = req_distinguished_name req_extensions = v3_req [ req_distinguished_name ] countryName = Country Name (2 letter code) [ v3_req ] basicConstraints = CA:FALSE subjectAltName = ' . $san . ' keyUsage = nonRepudiation, digitalSignature, keyEncipherment'; $tmp_config_path = tempnam(sys_get_temp_dir(), 'wpenc'); if (false === $filesystem->put_contents($tmp_config_path, $output)) { return new WP_Error('csr_cannot_write_tmp_file', __('Could not write CSR configuration to temporary file. Please check your filesystem permissions.', 'wp-encrypt')); } $dn = wp_parse_args($dn, array('CN' => $this->domain, 'ST' => 'United States of America', 'C' => 'US', 'O' => 'Unknown')); $dn = apply_filters('wp_encrypt_csr_dn', $dn, $this->domain); $csr = openssl_csr_new($dn, $key_resource, array('config' => $tmp_config_path, 'digest_alg' => 'sha256')); if (false === $csr) { $filesystem->delete($tmp_config_path); return new WP_Error('csr_cannot_generate', sprintf(__('Could not generate CSR. Original error message: %s', 'wp-encrypt'), openssl_error_string())); } if (false === openssl_csr_export($csr, $csr)) { $filesystem->delete($tmp_config_path); return new WP_Error('csr_cannot_export', sprintf(__('Could not export CSR. Original error message: %s', 'wp-encrypt'), openssl_error_string())); } $filesystem->delete($tmp_config_path); if (false === $filesystem->put_contents($this->path . '/last.csr', $csr)) { return new WP_Error('csr_cannot_write', sprintf(__('Could not write CSR into file <code>%s</code>. Please check your filesystem permissions.', 'wp-encrypt'), $this->path . '/last.csr')); } preg_match('#REQUEST-----(.*)-----END#s', $csr, $matches); return trim($matches[1]); }
/** * Returns the private key resource. * * @since 1.0.0 * @access public * * @param bool $force_refresh Whether to explicitly re-check the private key resource. * @return resource|WP_Error The private key resource if successful or an error object otherwise. */ public function read_private($force_refresh = false) { $filesystem = Util::get_filesystem(); if (null === $this->private_key_resource || $force_refresh) { $path = $this->path . '/' . self::PRIVATE_NAME; if (!$filesystem->exists($path)) { return new WP_Error('private_key_missing', sprintf(__('Missing private key <code>%s</code>.', 'wp-encrypt'), $path)); } $private_key = openssl_pkey_get_private('file://' . $path); if (false === $private_key) { return new WP_Error('private_key_invalid', sprintf(__('Invalid private key <code>%1$s</code>. Original error message: %2$s', 'wp-encrypt'), $path, openssl_error_string())); } $this->private_key_resource = $private_key; } return $this->private_key_resource; }
/** * Deletes all certificates, keys and challenges. * * Use this method with extreme caution - do not use it when your server is currently using any of * those files. * * @since 1.0.0 * @access public * * @return bool|WP_Error True if everything was deleted successfully, an error object otherwise. */ public function reset() { $filesystem = Util::get_filesystem(); $paths = array(Util::get_letsencrypt_certificates_dir_path(), Util::get_letsencrypt_challenges_dir_path()); foreach ($paths as $path) { if ($filesystem->is_dir($path)) { $filelist = $filesystem->dirlist(trailingslashit($path), true); if (is_array($filelist)) { foreach ($filelist as $fileinfo) { if (!$filesystem->delete(trailingslashit($path) . $fileinfo['name'], true, $fileinfo['type'])) { return new WP_Error('cannot_delete_directory', sprintf(__('Unable to delete <code>%s</code>.', 'wp-encrypt'), trailingslashit($path) . $fileinfo['name'])); } } } } } 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; }