Author: Niklas Keller (me@kelunik.com)
Example #1
0
    /**
     * {@inheritdoc}
     */
    public function generate(KeyPair $keyPair, array $domains)
    {
        if (!($privateKey = openssl_pkey_get_private($keyPair->getPrivate()))) {
            // TODO: Improve error message
            throw new AcmeException("Couldn't use private key.");
        }
        $san = implode(",", array_map(function ($dns) {
            return "DNS:{$dns}";
        }, $domains));
        // http://www.heise.de/netze/rfc/rfcs/rfc7633.shtml
        // http://www.heise.de/netze/rfc/rfcs/rfc6066.shtml
        $mustStaple = $this->mustStaple ? "tlsfeature = status_request" : "";
        $tempFile = tempnam(sys_get_temp_dir(), "acme-openssl-config-");
        $tempConf = <<<EOL
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
{$mustStaple}

[ req_distinguished_name ]

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation
subjectAltName = {$san}
EOL;
        (yield \Amp\File\put($tempFile, $tempConf));
        $csr = openssl_csr_new(["CN" => reset($domains)], $privateKey, ["digest_alg" => "sha256", "config" => $tempFile]);
        (yield \Amp\File\unlink($tempFile));
        if (!$csr) {
            // TODO: Improve error message
            throw new AcmeException("CSR could not be generated.");
        }
        (yield new CoroutineResult(openssl_csr_export($csr, $csr)));
    }
Example #2
0
 /**
  * @param string $name
  * @param KeyPair $keyPair
  * @return \Generator
  * @throws Exception
  */
 public function put($name = '', KeyPair $keyPair)
 {
     $file = $this->getFileName($name);
     try {
         if (!file_exists(dirname($file))) {
             FileHelper::createDirectory(dirname($file));
         }
         file_put_contents($file, $keyPair->getPrivate());
         chmod($file, 0600);
     } catch (FilesystemException $e) {
         throw new Exception("Could not save key.", 0, $e);
     }
     return $keyPair;
 }
Example #3
0
/**
 * Generates the payload which must be provided in challenges, e.g. HTTP-01 and DNS-01.
 *
 * @api
 * @param KeyPair $accountKeyPair account key pair
 * @param string  $token challenge token
 * @return string payload to be provided at /.well-known/acme-challenge/$token for HTTP-01 and _acme-challenge.example.com for DNS-01
 * @throws AcmeException If something went wrong.
 * @see https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#key-authorizations
 */
function generateKeyAuthorization(KeyPair $accountKeyPair, $token)
{
    if (!is_string($token)) {
        throw new InvalidArgumentException(sprintf("\$token must be of type string, %s given.", gettype($token)));
    }
    if (!($privateKey = openssl_pkey_get_private($accountKeyPair->getPrivate()))) {
        throw new AcmeException("Couldn't read private key.");
    }
    if (!($details = openssl_pkey_get_details($privateKey))) {
        throw new AcmeException("Couldn't get private key details.");
    }
    if ($details["type"] !== OPENSSL_KEYTYPE_RSA) {
        throw new AcmeException("Key type not supported, only RSA supported currently.");
    }
    $enc = new Base64UrlSafeEncoder();
    $payload = ["e" => $enc->encode($details["rsa"]["e"]), "kty" => "RSA", "n" => $enc->encode($details["rsa"]["n"])];
    return $token . "." . $enc->encode(hash("sha256", json_encode($payload), true));
}
Example #4
0
 private function doPut($path, KeyPair $keyPair)
 {
     if (!is_string($path)) {
         throw new InvalidArgumentException(sprintf("\$root must be of type string, %s given.", gettype($path)));
     }
     $file = $this->root . "/" . $path;
     try {
         // TODO: Replace with async version once available
         if (!file_exists(dirname($file))) {
             $success = mkdir(dirname($file), 0755, true);
             if (!$success) {
                 throw new KeyStoreException("Could not create key store directory.");
             }
         }
         (yield \Amp\File\put($file, $keyPair->getPrivate()));
         (yield \Amp\File\chmod($file, 0600));
     } catch (FilesystemException $e) {
         throw new KeyStoreException("Could not save key.", 0, $e);
     }
     (yield new CoroutineResult($keyPair));
 }
Example #5
0
 /**
  * Apply certificates to DirectAdmin
  *
  * @return bool
  * @throws \Exception
  */
 public function applyCertificates()
 {
     $sock = new HTTPSocket();
     $sock->connect('127.0.0.1', 2222);
     $sock->set_login('admin');
     $sock->set_method('POST');
     $sock->query('/CMD_API_SSL', ['domain' => $this->getHostname(), 'action' => 'save', 'type' => 'paste', 'certificate' => $this->domainKeys->getPrivate() . PHP_EOL . $this->getCertificate(), 'submit' => 'Save']);
     $result = $sock->fetch_parsed_body();
     if ($result['error'] != 0) {
         throw new \Exception('Error while executing first API request: ' . $result['details']);
     }
     $sock = new HTTPSocket();
     $sock->connect('127.0.0.1', 2222);
     $sock->set_login('admin');
     $sock->set_method('POST');
     $sock->query('/CMD_API_SSL', ['domain' => $this->getHostname(), 'action' => 'save', 'type' => 'cacert', 'active' => 'yes', 'cacert' => implode("\n", $this->getCertificateAuthorityCertificates()), 'submit' => 'Save']);
     $result = $sock->fetch_parsed_body();
     if ($result['error'] != 0) {
         throw new \Exception('Error while executing second API request: ' . $result['details']);
     }
     return true;
 }
Example #6
0
 /**
  * Apply certificates to DirectAdmin
  *
  * @return bool
  * @throws \Exception
  */
 public function applyCertificates()
 {
     if (defined('CRON')) {
         $domainPath = '/usr/local/directadmin/data/users/' . $this->account->getUsername() . '/domains/' . $this->getDomain();
         file_put_contents($domainPath . '.key', $this->domainKeys->getPrivate());
         chown($domainPath . '.key', 'diradmin');
         chgrp($domainPath . '.key', 'diradmin');
         chmod($domainPath . '.key', 0600);
         file_put_contents($domainPath . '.cert', $this->getCertificate());
         chown($domainPath . '.cert', 'diradmin');
         chgrp($domainPath . '.cert', 'diradmin');
         chmod($domainPath . '.cert', 0600);
         file_put_contents($domainPath . '.cacert', implode("\n", $this->getCertificateAuthorityCertificates()));
         chown($domainPath . '.cacert', 'diradmin');
         chgrp($domainPath . '.cacert', 'diradmin');
         chmod($domainPath . '.cacert', 0600);
         $config = new Config($domainPath . '.conf');
         $config->config('SSLCertificateKeyFile', $domainPath . '.key');
         $config->config('SSLCertificateFile', $domainPath . '.cert');
         $config->config('SSLCACertificateFile', $domainPath . '.cacert');
         $config->config('ssl', 'ON');
     } else {
         $sock = $this->getSocket();
         $sock->set_method('POST');
         $sock->query('/CMD_API_SSL', ['domain' => $this->getDomain(), 'action' => 'save', 'type' => 'paste', 'certificate' => $this->domainKeys->getPrivate() . PHP_EOL . $this->getCertificate(), 'submit' => 'Save']);
         $result = $sock->fetch_parsed_body();
         if ($result['error'] != 0) {
             throw new \Exception('Error while executing first API request: ' . $result['details']);
         }
         $sock->set_method('POST');
         $sock->query('/CMD_API_SSL', ['domain' => $this->getDomain(), 'action' => 'save', 'type' => 'cacert', 'active' => 'yes', 'cacert' => implode("\n", $this->getCertificateAuthorityCertificates()), 'submit' => 'Save']);
         $result = $sock->fetch_parsed_body();
         if ($result['error'] != 0) {
             throw new \Exception('Error while executing second API request: ' . $result['details']);
         }
     }
     return true;
 }
Example #7
0
 private function doRequestCertificate(KeyPair $keyPair, array $domains) : Generator
 {
     if (!($privateKey = openssl_pkey_get_private($keyPair->getPrivate()))) {
         throw new AcmeException("Couldn't use private key");
     }
     $san = implode(",", array_map(function ($dns) {
         return "DNS:" . $dns;
     }, $domains));
     $csr = openssl_csr_new(["CN" => reset($domains), "ST" => "Germany", "C" => "DE", "O" => "Unknown", "subjectAltName" => $san, "basicConstraints" => "CA:FALSE", "extendedKeyUsage" => "serverAuth"], $privateKey, ["digest_alg" => "sha256", "req_extensions" => "v3_req"]);
     if (!$csr) {
         throw new AcmeException("CSR couldn't be generated!");
     }
     openssl_csr_export($csr, $csr);
     $begin = "REQUEST-----";
     $end = "----END";
     $csr = substr($csr, strpos($csr, $begin) + strlen($begin));
     $csr = substr($csr, 0, strpos($csr, $end));
     $enc = new Base64UrlSafeEncoder();
     /** @var Response $response */
     $response = (yield $this->acmeClient->post(AcmeResource::NEW_CERTIFICATE, ["csr" => $enc->encode(base64_decode($csr))]));
     if ($response->getStatus() === 201) {
         if (!$response->hasHeader("location")) {
             throw new AcmeException("Protocol Violation: No Location Header");
         }
         return current($response->getHeader("location"));
     }
     throw new AcmeException("Invalid response code: " . $response->getStatus() . "\n" . $response->getBody());
 }
Example #8
0
    private function doRequestCertificate(KeyPair $keyPair, array $domains)
    {
        if (empty($domains)) {
            throw new AcmeException("Parameter \$domains must not be empty.");
        }
        if (!($privateKey = openssl_pkey_get_private($keyPair->getPrivate()))) {
            // TODO: Improve error message
            throw new AcmeException("Couldn't use private key.");
        }
        $tempFile = tempnam(sys_get_temp_dir(), "acme_openssl_config_");
        $tempConf = <<<EOL
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req

[ req_distinguished_name ]

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @san

[ san ]
EOL;
        $i = 0;
        $san = implode("\n", array_map(function ($dns) use(&$i) {
            $i++;
            return "DNS.{$i} = {$dns}";
        }, $domains));
        (yield \Amp\File\put($tempFile, $tempConf . "\n" . $san . "\n"));
        $csr = openssl_csr_new(["CN" => reset($domains), "ST" => "Germany", "C" => "DE", "O" => "Unknown"], $privateKey, ["digest_alg" => "sha256", "req_extensions" => "v3_req", "config" => $tempFile]);
        (yield \Amp\File\unlink($tempFile));
        if (!$csr) {
            // TODO: Improve error message
            throw new AcmeException("CSR could not be generated.");
        }
        openssl_csr_export($csr, $csr);
        $begin = "REQUEST-----";
        $end = "----END";
        $csr = substr($csr, strpos($csr, $begin) + strlen($begin));
        $csr = substr($csr, 0, strpos($csr, $end));
        $enc = new Base64UrlSafeEncoder();
        /** @var Response $response */
        $response = (yield $this->acmeClient->post(AcmeResource::NEW_CERTIFICATE, ["csr" => $enc->encode(base64_decode($csr))]));
        if ($response->getStatus() === 201) {
            if (!$response->hasHeader("location")) {
                throw new AcmeException("Protocol Violation: No Location Header");
            }
            (yield new CoroutineResult(current($response->getHeader("location"))));
            return;
        }
        throw $this->generateException($response);
    }