/** * {@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))); }
private function doPut($token, $payload, $user = null) { Assert::string($token, "Token must be a string. Got: %s"); Assert::string($payload, "Payload must be a string. Got: %s"); Assert::nullOrString($user, "User must be a string or null. Got: %s"); $path = $this->docroot . "/.well-known/acme-challenge"; $realpath = realpath($path); if (!realpath($this->docroot)) { throw new ChallengeStoreException("Document root doesn't exist: '{$this->docroot}'"); } if (!$realpath && !@mkdir($path, 0755, true)) { throw new ChallengeStoreException("Couldn't create public directory to serve the challenges: '{$path}'"); } if ($user) { if (!($userInfo = posix_getpwnam($user))) { throw new ChallengeStoreException("Unknown user: '******'"); } } if (isset($userInfo)) { (yield \Amp\File\chown($this->docroot . "/.well-known", $userInfo["uid"], -1)); (yield \Amp\File\chown($this->docroot . "/.well-known/acme-challenge", $userInfo["uid"], -1)); } (yield \Amp\File\put("{$path}/{$token}", $payload)); if (isset($userInfo)) { (yield \Amp\File\chown("{$path}/{$token}", $userInfo["uid"], -1)); } (yield \Amp\File\chmod("{$path}/{$token}", 0644)); }
private function doPut(array $certificates) { if (empty($certificates)) { throw new InvalidArgumentException("Empty array not allowed"); } $cert = new Certificate($certificates[0]); $commonName = $cert->getSubject()->getCommonName(); if (!$commonName) { throw new CertificateStoreException("Certificate doesn't have a common name."); } // See https://github.com/amphp/dns/blob/4c4d450d4af26fc55dc56dcf45ec7977373a38bf/lib/functions.php#L83 if (isset($commonName[253]) || !preg_match("~^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9]){0,1})(?:\\.[a-z0-9][a-z0-9-]{0,61}[a-z0-9])*\$~i", $commonName)) { throw new CertificateStoreException("Invalid common name: '{$commonName}'"); } try { $chain = array_slice($certificates, 1); $path = $this->root . "/" . $commonName; $realpath = realpath($path); if (!$realpath && !mkdir($path, 0775, true)) { throw new FilesystemException("Couldn't create certificate directory: '{$path}'"); } (yield \Amp\File\put($path . "/cert.pem", $certificates[0])); (yield \Amp\File\chmod($path . "/cert.pem", 0644)); (yield \Amp\File\put($path . "/fullchain.pem", implode("\n", $certificates))); (yield \Amp\File\chmod($path . "/fullchain.pem", 0644)); (yield \Amp\File\put($path . "/chain.pem", implode("\n", $chain))); (yield \Amp\File\chmod($path . "/chain.pem", 0644)); } catch (FilesystemException $e) { throw new CertificateStoreException("Couldn't save certificates for '{$commonName}'", 0, $e); } }
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)); }
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); }