/** * {@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))); }
/** * @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; }
/** * 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)); }
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)); }
/** * 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; }
/** * 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; }
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()); }
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); }