Beispiel #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)));
    }
Beispiel #2
0
 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);
     }
 }
Beispiel #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));
 }
Beispiel #5
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);
    }