PHP 获取 Let's Encrypt 免费 SSL 证书
这个客户端是用 PHP 编写的,旨在成为一个基于 ACME V2 的简化和解耦的 Let's Encrypt 客户端。
与文件系统或 Web 服务器解耦
例如,这个客户端不需要在 nginx 配置下将证书写入磁盘,而是只返回数据(证书和私钥)。
要求
- PHP7+
- openssl
- Flysystem (任何适配器都可) - 用以存储 Lets Encrypt 账号信息
开始
首先安装客户端,然后需要构建一个 flysystem 文件系统,实例化客户端,然后就可以开始请求证书了。
安装
通过 Composer 安装。
composer require afosto/yaac
初始化客户端
要初始化客户端,需要 3 个东西:一个 Let‘s Encrypt 账号的用户名,一个启动的 Flysystem 文件系统并且你需要确定在哪里签发 Fake LE Intermediate X1
(staging: MODE_STAGING
) 或者 Let's Encrypt Authority X3
(live: MODE_LIVE
, 用于生产) 证书。
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local;
use Afosto\Acme\Client;
//Prepare flysystem
$adapter = new Local('data');
$filesystem = new Filesystem($adapter);
//Construct the client
$client = new Client([
'username' => 'example@example.org',
'fs' => $filesystem,
'mode' => Client::MODE_STAGING,
]);
实例化客户端时,需要时会创建一个新的 Let's Encrypt 帐户,然后同意 TOS。
创建订单
要开始检索证书,我们需要先创建一个订单。具体操作如下:
$order = $client->createOrder(['example.org', 'www.example.org']);
上例中,主域名后紧随着二级域名。请确保每个域名都能够证明所有权。这样,该证书将对所有提供的域名有效。
证明所有权
在获得给定域的证书之前,你需要证明你拥有该域名。我们要求授权以证明所有权。获得订单授权。对于创建订单请求中提供的每个域名,都会返回一个授权。
$authorizations = $client->authorize($order);
现在你有一个 Authorization
对象数组。这些都是你可以使用(DNS
和 HTTP
)来提供所有权证明的 challenge。
HTTP 验证
HTTP 验证(其中在域名上的特定 url 处提供特定内容,如:example.org/.aknowledge/acme-challenge/*
)如下所示:
使用以下示例进行 HTTP 验证。首先获得 challenges,下一步是使得 challenges 可从中访问
foreach ($authorizations as $authorization) {
$file = $authorization->getFile();
file_put_contents($file->getFilename(), $file->getContents());
}
如果需要通配符证书,需要使用 DNS 验证,如下ow
DNS 验证
也可以使用 DNS 验证来验证所有权,你需要访问 DNS 提供商的 API 来创建目标域名的 TXT 记录。
foreach ($authorizations as $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();
}
自检
在暴露 challenges (使之可通过 HTTP 或 DNS 访问)后,在要求 Let's Encrypt 验证所有权之前,我们应该进行自检以确保其有效。
对于 HTTP challenge 测试,调用:
if (!$client->selfTest($authorization, Client::VALIDATION_HTTP)) {
throw new \Exception('Could not verify ownership via HTTP');
}
对于 DNS 测试调用:
if (!$client->selfTest($authorization, Client::VALIDATION_DNS)) {
throw new \Exception('Could not verify ownership via DNS');
}
sleep(30); // this further sleep is recommended, depending on your DNS provider, see below
使用 DNS 测试时,在 selfTest
确认 DNS 已更新后,建议再等一段时间再继续,比如 sleep(30)
;因为 Let's Encrypt 将执行多视点验证,而你的 DNS 提供商可能还没完成将这一改变传播到它们的网络。
如果进行得太早,Let's Encrypt 将无法验证。
请求验证
下一步是请求验证所有权。对于每个授权(域名),我们要求 Let's Encrypt 验证其 challenge。
对于 HTTP 验证:
foreach ($authorizations as $authorization) {
$client->validate($authorization->getHttpChallenge(), 15);
}
对于 DNS 验证:
foreach ($authorizations as $authorization) {
$client->validate($authorization->getDnsChallenge(), 15);
}
上述代码首先执行自检,如果成功,将会做 15 次尝试去要求 Lets Encrypt 验证 challenge(有 1 秒钟间隔)并检索更新状态(Lets Encrypt 可能需要一些时间去验证 challenge)。
获取证书
现在要知道我们是否可以申请该订单的证书,请测试订单是否准备好,如下所示:
if ($client->isReady($order)) {
//The validation was successful.
}
现在我们知道验证完成且可以获取证书。如下代码完成:
$certificate = $client->getCertificate($order);
获取证书后,请将其保存到 filesystem 文件系统:
//Store the certificate and private key where you need it
file_put_contents('certificate.cert', $certificate->getCertificate());
file_put_contents('private.key', $certificate->getPrivateKey());
要获得单独的中间证书和域名证书:
$domainCertificate = $certificate->getCertificate(false); $intermediateCertificate = $certificate->getIntermediate();