Пример #1
0
        /**
         * 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]);
        }
Пример #2
0
 /**
  * Requests filesystem credentials if necessary.
  *
  * The key files and certificates need to be stored on disk.
  * If the directories aren't writeable by WordPress, the user needs to manually enter
  * access keys (FTP, SSH, ...).
  *
  * @since 1.0.0
  * @access protected
  *
  * @param bool $network_wide Whether filesystem access is needed for a network-wide request.
  * @return bool Whether the filesystem was successfully set up.
  */
 protected function maybe_request_filesystem_credentials($network_wide = false)
 {
     if (defined('DOING_AJAX') && DOING_AJAX) {
         $credentials = array();
         $fields = array('hostname', 'port', 'username', 'password', 'public_key', 'private_key', 'connection_type');
         foreach ($fields as $field) {
             if (isset($_REQUEST[$field])) {
                 $credentials[$field] = $_REQUEST[$field];
             }
         }
         if (CoreUtil::needs_filesystem_credentials($credentials)) {
             return false;
         }
         return true;
     }
     $url = App::get_admin_action_url($network_wide ? 'network' : 'site');
     $extra_fields = array('action', '_wpnonce');
     return CoreUtil::setup_filesystem($url, $extra_fields);
 }
Пример #3
0
 /**
  * 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;
 }
Пример #4
0
 /**
  * Sends a signed request to the Let's Encrypt API.
  *
  * All requests except the directory request go through this method.
  *
  * @since 1.0.0
  * @access public
  *
  * @param string $endpoint The endpoint to send a request to.
  * @param array  $data     Data to send with the request.
  * @return string|array|WP_Error Either a JSON-decoded array response, a plain text response or an error object.
  */
 public function signed_request($endpoint, $data = null)
 {
     $account_keypair = AccountKeyPair::get();
     $account_key_resource = $account_keypair->read_private();
     if (is_wp_error($account_key_resource)) {
         return $account_key_resource;
     }
     $account_key_details = $account_keypair->get_private_details();
     if (is_wp_error($account_key_details)) {
         return $account_key_details;
     }
     $protected = $header = array('alg' => 'RS256', 'jwk' => array('kty' => 'RSA', 'n' => Util::base64_url_encode($account_key_details['rsa']['n']), 'e' => Util::base64_url_encode($account_key_details['rsa']['e'])));
     if (null !== ($nonce = $this->get_last_nonce())) {
         $protected['nonce'] = $nonce;
     } else {
         $this->directory();
         if (null !== ($nonce = $this->get_last_nonce())) {
             $protected['nonce'] = $nonce;
         }
     }
     if (!isset($protected['nonce'])) {
         return new WP_Error('signed_request_no_nonce', __('No nonce available for a signed request.', 'wp-encrypt'));
     }
     $data64 = Util::base64_url_encode(str_replace('\\/', '/', json_encode($data)));
     $protected64 = Util::base64_url_encode(json_encode($protected));
     $sign_status = openssl_sign($protected64 . '.' . $data64, $signature, $account_key_resource, 'SHA256');
     if (false === $sign_status) {
         return new WP_Error('private_key_cannot_sign', sprintf(__('Could not sign request with private key. Original error message: %s', 'wp-encrypt'), openssl_error_string()));
     }
     $signature64 = Util::base64_url_encode($signature);
     return $this->request($endpoint, 'POST', array('header' => $header, 'protected' => $protected64, 'payload' => $data64, 'signature' => $signature64));
 }
Пример #5
0
 /**
  * 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;
 }
Пример #6
0
 /**
  * Returns an array of directories for the certificates and keys for a domain.
  *
  * @since 1.0.0
  * @access protected
  *
  * @param string $domain The domain to get the directories for.
  * @return array An array containing 'base', 'cert', 'chain', 'fullchain' and 'key' keys with their respective directory as value.
  */
 protected function get_certificate_dirs($domain)
 {
     $site_domain = 'network' === $this->context ? Util::get_network_domain() : Util::get_site_domain();
     $certificate_dirs = array('base' => CoreUtil::get_letsencrypt_certificates_dir_path() . '/' . $domain);
     $certificate_dirs['cert'] = $certificate_dirs['base'] . '/' . Certificate::CERT_NAME;
     $certificate_dirs['chain'] = $certificate_dirs['base'] . '/' . Certificate::CHAIN_NAME;
     $certificate_dirs['fullchain'] = $certificate_dirs['base'] . '/' . Certificate::FULLCHAIN_NAME;
     $certificate_dirs['key'] = $certificate_dirs['base'] . '/' . KeyPair::PRIVATE_NAME;
     return $certificate_dirs;
 }
Пример #7
0
 /**
  * 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;
 }