Skip to content

Commit

Permalink
Optimize challenge solving by solving several challenges at once
Browse files Browse the repository at this point in the history
  • Loading branch information
jderusse committed Mar 23, 2018
1 parent 8caf4a1 commit 8ddb2f1
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 116 deletions.
70 changes: 43 additions & 27 deletions AcmeClient.php
Expand Up @@ -151,25 +151,27 @@ function ($domain) {
}

$orderEndpoint = $this->getHttpClient()->getLastLocation();
$base64encoder = $this->getHttpClient()->getBase64Encoder();
foreach ($response['authorizations'] as $authorizationEndpoint) {
$authorizationsResponse = $this->getHttpClient()->unsignedRequest('GET', $authorizationEndpoint, null, true);
$domain = (empty($authorizationsResponse['wildcard']) ? '' : '*.').$authorizationsResponse['identifier']['value'];
foreach ($authorizationsResponse['challenges'] as $challenge) {
$authorizationsChallenges[$domain][] = new AuthorizationChallenge(
$authorizationsResponse['identifier']['value'],
$challenge['status'],
$challenge['type'],
$challenge['url'],
$challenge['token'],
$challenge['token'].'.'.$base64encoder->encode($this->getHttpClient()->getJWKThumbprint())
);
$authorizationsChallenges[$domain][] = $this->createAuthorizationChallenge($authorizationsResponse['identifier']['value'], $challenge);
}
}

return new CertificateOrder($authorizationsChallenges, $orderEndpoint);
}

/**
* {@inheritdoc}
*/
public function reloadAuthorization(AuthorizationChallenge $challenge)
{
$response = (array) $this->getHttpClient()->unsignedRequest('GET', $challenge->getUrl());

return $this->createAuthorizationChallenge($challenge->getDomain(), $response);
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -252,6 +254,24 @@ public function finalizeOrder(CertificateOrder $order, CertificateRequest $csr,
return new CertificateResponse($csr, $certificatesChain);
}

/**
* Find a resource URL.
*
* @param string $resource
*
* @return string
*/
public function getResourceUrl($resource)
{
if (!$this->directory) {
$this->directory = new ResourcesDirectory(
$this->getHttpClient()->unsignedRequest('GET', $this->directoryUrl, null, true)
);
}

return $this->directory->getResourceUrl($resource);
}

/**
* Request a resource (URL is found using ACME server directory).
*
Expand All @@ -275,24 +295,6 @@ protected function requestResource($method, $resource, array $payload, $returnJs
);
}

/**
* Find a resource URL.
*
* @param string $resource
*
* @return string
*/
public function getResourceUrl($resource)
{
if (!$this->directory) {
$this->directory = new ResourcesDirectory(
$this->getHttpClient()->unsignedRequest('GET', $this->directoryUrl, null, true)
);
}

return $this->directory->getResourceUrl($resource);
}

/**
* Retrieve the resource account.
*
Expand All @@ -311,4 +313,18 @@ private function getResourceAccount()

return $this->account;
}

private function createAuthorizationChallenge($domain, array $response)
{
$base64encoder = $this->getHttpClient()->getBase64Encoder();

return new AuthorizationChallenge(
$domain,
$response['status'],
$response['type'],
$response['url'],
$response['token'],
$response['token'].'.'.$base64encoder->encode($this->getHttpClient()->getJWKThumbprint())
);
}
}
10 changes: 10 additions & 0 deletions AcmeClientV2Interface.php
Expand Up @@ -16,6 +16,7 @@
use AcmePhp\Core\Exception\Protocol\CertificateRequestFailedException;
use AcmePhp\Core\Exception\Protocol\CertificateRequestTimedOutException;
use AcmePhp\Core\Exception\Protocol\ChallengeNotSupportedException;
use AcmePhp\Core\Protocol\AuthorizationChallenge;
use AcmePhp\Core\Protocol\CertificateOrder;
use AcmePhp\Ssl\CertificateRequest;
use AcmePhp\Ssl\CertificateResponse;
Expand Down Expand Up @@ -68,4 +69,13 @@ public function requestOrder(array $domains);
* @return CertificateResponse the certificate data to save it somewhere you want
*/
public function finalizeOrder(CertificateOrder $order, CertificateRequest $csr, $timeout = 180);

/**
* Request the current status of an authorization challenge.
*
* @param AuthorizationChallenge $challenge The challenge to request
*
* @return AuthorizationChallenge A new instance of the challenge
*/
public function reloadAuthorization(AuthorizationChallenge $challenge);
}
33 changes: 20 additions & 13 deletions Challenge/Dns/LibDnsResolver.php
Expand Up @@ -20,6 +20,8 @@
use LibDNS\Messages\MessageTypes;
use LibDNS\Records\QuestionFactory;
use LibDNS\Records\ResourceTypes;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

/**
* Resolves DNS with LibDNS (pass over internal DNS cache and check several nameservers).
Expand All @@ -28,6 +30,8 @@
*/
class LibDnsResolver implements DnsResolverInterface
{
use LoggerAwareTrait;

/**
* @var QuestionFactory
*/
Expand Down Expand Up @@ -65,6 +69,8 @@ public function __construct(
$this->encoder = null === $encoder ? (new EncoderFactory())->create() : $encoder;
$this->decoder = null === $decoder ? (new DecoderFactory())->create() : $decoder;
$this->nameServer = $nameServer;

$this->logger = new NullLogger();
}

/**
Expand All @@ -80,30 +86,31 @@ public static function isSupported()
*/
public function getTxtEntries($domain)
{
$nsDomain = implode('.', array_slice(explode('.', rtrim($domain, '.')), -2));
$nameServers = $this->request(
implode('.', array_slice(explode('.', rtrim($domain, '.')), -2)),
$nsDomain,
ResourceTypes::NS,
$this->nameServer
);

$this->logger->debug('Fetched NS in charge of domain', ['nsDomain' => $nsDomain, 'servers' => $nameServers]);
if (empty($nameServers)) {
throw new AcmeDnsResolutionException(
sprintf('Unable to find domain %s on nameserver %s', $domain, $this->nameServer)
);
throw new AcmeDnsResolutionException(sprintf('Unable to find domain %s on nameserver %s', $domain, $this->nameServer));
}
$entries = null;

$identicalEntries = [];
foreach ($nameServers as $nameServer) {
$ip = gethostbyname($nameServer);
$serverEntries = $this->request($domain, ResourceTypes::TXT, $ip);
if (null === $entries) {
$entries = $serverEntries;
} elseif ($entries !== $serverEntries) {
throw new AcmeDnsResolutionException(
sprintf('Dns not fully propagated into nameserver %s', $nameServer)
);
}
$identicalEntries[json_encode($serverEntries)][] = $nameServer;
}

$this->logger->info('DNS records fetched', ['mapping' => $identicalEntries]);
if (1 !== count($identicalEntries)) {
throw new AcmeDnsResolutionException('Dns not fully propagated');
}

return $entries;
return json_decode(key($identicalEntries));
}

private function request($domain, $type, $nameServer)
Expand Down

0 comments on commit 8ddb2f1

Please sign in to comment.