- Notifications
You must be signed in to change notification settings - Fork97
Yet another ACME client: a decoupled LetsEncrypt client
License
afosto/yaac
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Written in PHP, this client aims to be a simplified and decoupled Let’s Encrypt client, based onACME V2.
Instead of, for example writing the certificate to the disk under an nginx configuration, this client just returns thedata (the certificate and private key).
Why would I need this package? At Afosto we run our software in a multi-tenant setup, as any other SaaS would do, andtherefore we cannot make use of the many clients that are already out there.
Almost all clients are coupled to a type of webserver or a fixed (set of) domain(s). This package can be extremelyuseful in case you need to dynamically fetch and install certificates.
- PHP7+
- openssl
- Flysystem (any adapter would do) - to store the Lets Encrypt account information
Getting started is easy. First install the client, then you need to construct a flysystem filesystem, instantiate theclient and you can start requesting certificates.
Installing this package is done easily with composer.
composer require afosto/yaac
To start the client you need 3 things; a username for your Let’s Encrypt account, a bootstrapped Flysystem and you need todecide whether you want to issueFake LE Intermediate X1
(staging:MODE_STAGING
) orLet's Encrypt Authority X3
(live:MODE_LIVE
, use for production) certificates.
useLeague\Flysystem\Filesystem;useLeague\Flysystem\Adapter\Local;useAfosto\Acme\Client;//Prepare flysystem$adapter =newLocal('data');$filesystem =newFilesystem($adapter);//Construct the client$client =newClient(['username' =>'example@example.org','fs' =>$filesystem,'mode' => Client::MODE_STAGING,]);
While you instantiate the client, when needed a new Let’s Encrypt account is created and then agrees to the TOS.
To start retrieving certificates, we need to create an order first. This is done as follows:
$order =$client->createOrder(['example.org','www.example.org']);
In the example above the primary domain is followed by a secondary domain(s). Make sure that for each domain you areable to prove ownership. As a result the certificate will be valid for all provided domains.
Before you can obtain a certificate for a given domain you need to prove that you own the given domain(s).We request the authorizations to prove ownership. Obtain the authorizations for order. For each domain supplied in thecreate order request an authorization is returned.
$authorizations =$client->authorize($order);
You now have an array ofAuthorization
objects. These have the challenges you can use (bothDNS
andHTTP
) toprovide proof of ownership.
HTTP validation (where serve specific content at a specific url on the domain, like:example.org/.well-known/acme-challenge/*
) is done as follows:
Use the following example to get the HTTP validation going. First obtain the challenges, the next step is to make thechallenges accessible from
foreach ($authorizationsas$authorization) {$file =$authorization->getFile();file_put_contents($file->getFilename(),$file->getContents()); }
If you need a wildcard certificate, you will need to use DNS validation, see below
You can also use DNS validation - to do this, you will need access to an API of your DNSprovider to create TXT records for the target domains.
foreach ($authorizationsas$authorization) {$txtRecord =$authorization->getTxtRecord();//To get the name of the TXT record call:$txtRecord->getName();//To get the value of the TXT record call:$txtRecord->getValue();}
After exposing the challenges (made accessible through HTTP or DNS) we should perform a self test just tobe sure it works before asking Let's Encrypt to validate ownership.
For a HTTP challenge test call:
if (!$client->selfTest($authorization, Client::VALIDATION_HTTP)) {thrownew \Exception('Could not verify ownership via HTTP');}
For a DNS test call:
if (!$client->selfTest($authorization, Client::VALIDATION_DNS)) {thrownew \Exception('Could not verify ownership via DNS');}sleep(30);// this further sleep is recommended, depending on your DNS provider, see below
With DNS validation, after theselfTest
has confirmed that DNS has been updated, it isrecommended you wait some additional time before proceeding, e.g.sleep(30);
. This isbecause Let’s Encrypt will performmultiple viewpoint validation,and your DNS provider may not have completed propagating the changes across their network.
If you proceed too soon,Let's Encrypt will fail to validate.
Next step is to request validation of ownership. For each authorization (domain) we ask Let’s Encrypt to verify thechallenge.
For HTTP validation:
foreach ($authorizationsas$authorization) {$client->validate($authorization->getHttpChallenge(),15);}
For DNS validation:
foreach ($authorizationsas$authorization) {$client->validate($authorization->getDnsChallenge(),15);}
The code above will first perform a self test and, if successful, will do 15 attempts to ask Let’s Encrypt to validate the challenge (with 1 second intervals) andretrieve an updated status (it might take Let’s Encrypt a few seconds to validate the challenge).
Now to know if we can request a certificate for the order, test if the order is ready as follows:
if ($client->isReady($order)) {//The validation was successful.}
We now know validation was completed and can obtain the certificate. This is done as follows:
$certificate =$client->getCertificate($order);
We now have the certificate, to store it on the filesystem:
//Store the certificate and private key where you need itfile_put_contents('certificate.cert',$certificate->getCertificate());file_put_contents('private.key',$certificate->getPrivateKey());
To get a seperate intermediate certificate and domain certificate:
$domainCertificate =$certificate->getCertificate(false);$intermediateCertificate =$certificate->getIntermediate();
Are you using this package, would love to know. Please send a PR to enlist your project or company.
About
Yet another ACME client: a decoupled LetsEncrypt client