diff --git a/README.md b/README.md index 938371e..465e738 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,12 @@ file_put_contents('certificate.cert', $certificate->getCertificate()); file_put_contents('private.key', $certificate->getPrivateKey()); ``` +>To get a seperate intermediate certificate and domain certificate: +>```php +>$domainCertificate = $certificate->getCertificate(false); +>$intermediateCertificate = $certificate->getIntermediate(); +>``` + ### Who is using it? Are you using this package, would love to know. Please send a PR to enlist your project or company. diff --git a/src/Client.php b/src/Client.php index 378c5e7..ecc2ec0 100644 --- a/src/Client.php +++ b/src/Client.php @@ -194,7 +194,7 @@ class Client foreach ($domains as $domain) { $identifiers[] = [ - 'type' => 'dns', + 'type' => 'dns', 'value' => $domain, ]; } @@ -331,8 +331,8 @@ class Client $data['certificate'], $this->signPayloadKid(null, $data['certificate']) ); - $certificate = $str = preg_replace('/^[ \t]*[\r\n]+/m', '', (string)$certificateResponse->getBody()); - return new Certificate($privateKey, $csr, $certificate); + $chain = $str = preg_replace('/^[ \t]*[\r\n]+/m', '', (string)$certificateResponse->getBody()); + return new Certificate($privateKey, $csr, $chain); } @@ -383,8 +383,8 @@ class Client protected function getSelfTestClient() { return new HttpClient([ - 'verify' => false, - 'timeout' => 10, + 'verify' => false, + 'timeout' => 10, 'connect_timeout' => 3, 'allow_redirects' => true, ]); @@ -459,9 +459,9 @@ class Client protected function getSelfTestDNSClient() { return new HttpClient([ - 'base_uri' => 'https://cloudflare-dns.com', + 'base_uri' => 'https://cloudflare-dns.com', 'connect_timeout' => 10, - 'headers' => [ + 'headers' => [ 'Accept' => 'application/dns-json', ], ]); @@ -511,7 +511,7 @@ class Client $this->getUrl(self::DIRECTORY_NEW_ACCOUNT), $this->signPayloadJWK( [ - 'contact' => [ + 'contact' => [ 'mailto:' . $this->getOption('username'), ], 'termsOfServiceAgreed' => true, @@ -590,7 +590,7 @@ class Client { try { $response = $this->getHttpClient()->request($method, $url, [ - 'json' => $payload, + 'json' => $payload, 'headers' => [ 'Content-Type' => 'application/jose+json', ] @@ -650,9 +650,9 @@ class Client protected function getJWKHeader(): array { return [ - 'e' => Helper::toSafeString(Helper::getKeyDetails($this->getAccountKey())['rsa']['e']), + 'e' => Helper::toSafeString(Helper::getKeyDetails($this->getAccountKey())['rsa']['e']), 'kty' => 'RSA', - 'n' => Helper::toSafeString(Helper::getKeyDetails($this->getAccountKey())['rsa']['n']), + 'n' => Helper::toSafeString(Helper::getKeyDetails($this->getAccountKey())['rsa']['n']), ]; } @@ -671,10 +671,10 @@ class Client $this->nonce = $response->getHeaderLine('replay-nonce'); } return [ - 'alg' => 'RS256', - 'jwk' => $this->getJWKHeader(), + 'alg' => 'RS256', + 'jwk' => $this->getJWKHeader(), 'nonce' => $this->nonce, - 'url' => $url + 'url' => $url ]; } @@ -691,10 +691,10 @@ class Client $nonce = $response->getHeaderLine('replay-nonce'); return [ - "alg" => "RS256", - "kid" => $this->account->getAccountURL(), + "alg" => "RS256", + "kid" => $this->account->getAccountURL(), "nonce" => $nonce, - "url" => $url + "url" => $url ]; } @@ -720,7 +720,7 @@ class Client return [ 'protected' => $protected, - 'payload' => $payload, + 'payload' => $payload, 'signature' => Helper::toSafeString($signature), ]; } @@ -746,7 +746,7 @@ class Client return [ 'protected' => $protected, - 'payload' => $payload, + 'payload' => $payload, 'signature' => Helper::toSafeString($signature), ]; } diff --git a/src/Data/Certificate.php b/src/Data/Certificate.php index 9fda72e..b393d5c 100644 --- a/src/Data/Certificate.php +++ b/src/Data/Certificate.php @@ -12,11 +12,21 @@ class Certificate */ protected $privateKey; + /** + * @var string + */ + protected $chain; + /** * @var string */ protected $certificate; + /** + * @var string + */ + protected $intermediateCertificate; + /** * @var string */ @@ -31,15 +41,16 @@ class Certificate * Certificate constructor. * @param $privateKey * @param $csr - * @param $certificate + * @param $chain * @throws \Exception */ - public function __construct($privateKey, $csr, $certificate) + public function __construct($privateKey, $csr, $chain) { $this->privateKey = $privateKey; $this->csr = $csr; - $this->certificate = $certificate; - $this->expiryDate = Helper::getCertExpiryDate($certificate); + $this->chain = $chain; + list($this->certificate, $this->intermediateCertificate) = Helper::splitCertificate($chain); + $this->expiryDate = Helper::getCertExpiryDate($chain); } /** @@ -61,12 +72,23 @@ class Certificate } /** - * Return the certificate as a multi line string + * Return the certificate as a multi line string, by default it includes the intermediate certificate as well + * + * @param bool $asChain * @return string */ - public function getCertificate(): string + public function getCertificate($asChain = true): string { - return $this->certificate; + return $asChain ? $this->chain : $this->certificate; + } + + /** + * Return the intermediate certificate as a multi line string + * @return string + */ + public function getIntermediate(): string + { + return $this->intermediateCertificate; } /** diff --git a/src/Helper.php b/src/Helper.php index da5ae14..bad5893 100644 --- a/src/Helper.php +++ b/src/Helper.php @@ -2,8 +2,6 @@ namespace Afosto\Acme; -use Afosto\Acme\Data\Authorization; -use GuzzleHttp\Client as HttpClient; use GuzzleHttp\Exception\ClientException; /** @@ -92,11 +90,11 @@ class Helper file_put_contents($fn, implode("\n", $config)); $csr = openssl_csr_new([ 'countryName' => 'NL', - 'commonName' => $primaryDomain, + 'commonName' => $primaryDomain, ], $key, [ - 'config' => $fn, + 'config' => $fn, 'req_extensions' => 'SAN', - 'digest_alg' => 'sha512', + 'digest_alg' => 'sha512', ]); unlink($fn); @@ -140,4 +138,29 @@ class Helper return $accountDetails; } + + /** + * Split a two certificate bundle into separate multi line string certificates + * @param string $chain + * @return array + * @throws \Exception + */ + public static function splitCertificate(string $chain): array + { + preg_match( + '/^(?-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----)\n' + . '(?-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----)$/s', + $chain, + $certificates + ); + + $domain = $certificates['domain'] ?? null; + $intermediate = $certificates['intermediate'] ?? null; + + if (!$domain || !$intermediate) { + throw new \Exception('Could not parse certificate string'); + } + + return [$domain, $intermediate]; + } }