ptrstovka / letsencrypt-php
Let's Encrypt ACME v2 客户端
Requires
- php: ~7.1
- ext-openssl: *
- guzzlehttp/guzzle: ~6.0
- psr/log: ^1.0
Requires (Dev)
- phpunit/phpunit: >=5.4.3
- squizlabs/php_codesniffer: ^2.3
Suggests
- psr/log-implementation: A PSR-3 compatible logger is recommended for troubleshooting
This package is auto-updated.
Last update: 2020-10-13 12:43:47 UTC
README
这是一个用于ACME v2的Let's Encrypt客户端库,它允许使用PHP自动创建免费的SSL/TLS证书。这包括自2018年2月起由Let's Encrypt支持的通配符证书。
虽然这个包包含一个命令行工具,但这个库的真正意图是使其易于集成到需要签发证书的现有PHP应用程序中。
有关Let's Encrypt和ACME的更多信息及文档,请参阅Let's Encrypt文档。
先决条件
由于实现了ECDSA,最低要求的PHP版本是7.1.0。
此客户端还依赖于OpenSSL。
安装
通过Composer
$ composer require ptrstovka/letsencrypt-php
用法
此处显示了基本功能和必要的参数。每个类中都有扩展描述。
建议通过设置更高的最大执行时间来为脚本留出更多时间。有多种方法可以做到这一点。一种方法是将以下内容添加到页面的顶部
ini_set('max_execution_time', 120); // Maximum execution time in seconds.
初始化客户端
use Elphin\LEClient; // Initiating a basic LEClient with an array of string e-mail address(es). $client = new LEClient($email); // Initiating a LECLient and use the LetsEncrypt staging URL. $client = new LEClient($email, true); // Initiating a LEClient and log status messages (LOG_DEBUG for full debugging). $client = new LEClient($email, true, LEClient::LOG_STATUS);
如果没有找到账户,客户端将自动创建新账户。它将转发初始化时提供的电子邮件地址,如上所示。
使用账户功能
$acct = $client->getAccount(); // Retrieves the LetsEncrypt Account instance created by the client. $acct->updateAccount($email); // Updates the account with new contact information. Supply an array of string e-mail address(es). $acct->changeAccountKeys(); // Generates a new RSA keypair for the account and updates the keys with LetsEncrypt. $acct->deactivateAccount(); // Deactivates the account with LetsEncrypt.
创建证书订单实例。如果找到了本地存储的订单,它将使用此订单。否则,它将创建一个新订单。如果提供的域名与订单不匹配,它也将创建一个新的订单。构建Let's Encrypt订单实例
$order = $client->getOrCreateOrder($basename, $domains); // Get or create order. The basename is preferably the top domain name. This will be the directory in which the keys are stored. Supply an array of string domain names to create a certificate for. $order = $client->getOrCreateOrder($basename, $domains, $keyType); // Get or create order. keyType can be set to "ec" to get ECDSA certificate. "rsa" is default value. $order = $client->getOrCreateOrder($basename, $domains, $keyType, $notBefore); // Get or create order. Supply a notBefore date as a string similar to 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss). $order = $client->getOrCreateOrder($basename, $domains, $keyType, $notBefore, $notAfter); // Get or create order. Supply a notBefore and notAfter date as a string similar to 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss).
使用订单功能
$valid = $order->allAuthorizationsValid(); // Check whether all authorizations in this order instance are valid. $pending = $order->getPendingAuthorizations($type); // Get an array of pending authorizations. Performing authorizations is described further on. Type is LEOrder::CHALLENGE_TYPE_HTTP or LEOrder::CHALLENGE_TYPE_DNS. $verify = $order->verifyPendingOrderAuthorization($identifier, $type); // Verify a pending order. The identifier is a string domain name. Type is LEOrder::CHALLENGE_TYPE_HTTP or LEOrder::CHALLENGE_TYPE_DNS. $deactivate = $order->deactivateOrderAuthorization($identifier); // Deactivate an authorization. The identifier is a string domain name. $finalize = $order->finalizeOrder(); // Finalize the order and generate a Certificate Signing Request automatically. $finalize = $order->finalizeOrder($csr); // Finalize the order with a custom Certificate Signing Request string. $finalized = $order->isFinalized(); // Check whether the order is finalized. $cert = $order->getCertificate(); // Retrieves the certificate and stores it in the keys directory, under the specific order (basename). $revoke = $order->revokeCertificate(); // Revoke the certificate without a reason. $revoke = $order->revokeCertificate($reason); // Revoke the certificate with a reason integer as found in section 5.3.1 of RFC5280.
辅助函数
LEFunctions::RSAGenerateKeys($directory, $privateKeyFile, $publicKeyFile); // Generate a RSA keypair in the given directory. Variables privateKeyFile and publicKeyFile are optional and have default values private.pem and public.pem. LEFunctions::ECGenerateKeys($directory, $privateKeyFile, $publicKeyFile); // Generate a EC keypair in the given directory (PHP 7.1+ required). Variables privateKeyFile and publicKeyFile are optional and have default values private.pem and public.pem. LEFunctions::Base64UrlSafeEncode($input); // Encode the input string as a base64 URL safe string. LEFunctions::Base64UrlSafeDecode($input); // Decode a base64 URL safe encoded string. LEFunctions::log($data, $function); // Print the data. The function variable is optional and defaults to the calling function's name. LEFunctions::checkHTTPChallenge($domain, $token, $keyAuthorization); // Checks whether the HTTP challenge is valid. Performing authorizations is described further on. LEFunctions::checkDNSChallenge($domain, $DNSDigest); // Checks whether the DNS challenge is valid. Performing authorizations is described further on. LEFunctions::createhtaccess($directory); // Created a simple .htaccess file in the directory supplied, denying all visitors.
授权挑战
Let's Encrypt (ACME)会对您希望在证书中包含的域名进行授权,以验证您确实有权访问该特定域名。因此,在创建订单时,将为每个域名添加一个授权。如果某个域名最近(在最后30天内)已被您的账户验证,例如在另一个订单中,则无需再次验证。此时,可以通过对文件进行HTTP请求(http-01)或DNS TXT记录(dns-01)来验证域名。客户端通过调用getPendingAuthorizations()提供所需数据以进行选择的验证。由于创建文件或DNS记录对于每个服务器而言都不同,因此客户端中未实现这一点。在用户满足挑战要求后,必须调用verifyPendingOrderAuthorization()。在此客户端中,它将首先通过checkHTTPChallenge()或checkDNSChallenge()自行验证挑战,然后再开始由Let's Encrypt进行验证。请注意,通配符域名只能通过DNS挑战进行验证。以下展示了两种挑战的示例。
HTTP挑战
在此示例中,我们假设有一个域名需要验证。
$pending = $order->getPendingAuthorizations(LEOrder::CHALLENGE_TYPE_HTTP);
这将返回一个数组
Array
(
[0] => Array
(
[type] => http-01
[identifier] => test.example.org
[filename] => A8Q1DAVcd_k_oKAC0D_y4ln2IWrRX51jmXnR9UMMtOb
[content] => A8Q1DAVcd_k_oKAC0D_y4ln2IWrRX51jmXnR9UMMtOb.C4kIiiwfcynb3i48AQVtZRtNrD51z4JiIrdQsgVqcL8
)
)
对于成功的验证,将向以下URL发出请求
http://test.example.org/.well-known/acme-challenge/A8Q1DAVcd_k_oKAC0D_y4ln2IWrRX51jmXnR9UMMtOb
此文件的应设置为与上面数组中的内容相同的内容。用户应在验证授权之前创建此文件。
DNS 挑战
在此示例中,我们假设还有两个域需要验证。一个是通配符域。此示例中的第二个域是为了演示目的而添加的。将子域添加到证书中,而这些子域已经被通配符域覆盖,并不会带来太多额外的价值。
$pending = $order->getPendingAuthorizations(LEOrder::CHALLENGE_TYPE_DNS);
这将返回一个数组
Array
(
[0] => Array
(
[type] => dns-01
[identifier] => example.org
[DNSDigest] => FV5HgbpjIYe1x9MkPI81Nffo2oA-Jo2S88gCL7-Ky5P
)
[1] => Array
(
[type] => dns-01
[identifier] => test.example.org
[DNSDigest] => WM5YIsgaZQv1b9DbRZ81EwCf2fi-Af2JlgxTC7-Up5D
)
)
为了成功验证,DNS 记录应创建如下
| 名称 | TTL | 类型 | 值 |
|---|---|---|---|
| _acme-challenge.example.org | 60 | TXT | FV5HgbpjIYe1x9MkPI81Nffo2oA-Jo2S88gCL7-Ky5P |
| _acme-challenge.test.example.org | 60 | TXT | WM5YIsgaZQv1b9DbRZ81EwCf2fi-Af2JlgxTC7-Up5D |
如果需要,可以将 TTL 值设置得更高。为了确保验证成功,建议运行一个使用 DNS 挑战的两部分脚本,中间留有一定的间隔时间以允许 DNS 记录更新。用户自己应确保在记录可以验证之前设置此 DNS 记录。
DNS 记录名称也取决于您的提供商,因此 getPendingAuthorizations() 不提供现成的记录名称。一些提供商仅接受如 _acme-challenge 这样的名称,而不包含顶级域名,用于 _acme-challenge.example.org。一些提供商接受(或要求?)如上所示的全名。
通配符域,如 *.example.org,将按上述方式验证为 example.org。这意味着 DNS 记录名称应为 _acme-challenge.example.org
完整示例
对于 HTTP 和 DNS 验证,主代码目录中提供了完整示例。HTTP 验证示例包含在一个文件中。如上所述,DNS 验证示例分为两部分,以便在同时允许 DNS 记录更新。虽然记录的 TTL 可能很低,但在更改后,您的提供商可能需要一些时间来更新您的 DNS 记录。
如果您无法获取这些示例或客户端库,请尝试查看上述提到的 Let's Encrypt 文档。
变更日志
请参阅 CHANGELOG 以获取有关最近更改的更多信息。
测试
单元测试如下执行
$ composer test
测试套件包括一些与外部依赖项的集成测试,例如验证每个支持的 DNS-over-HTTP 服务是否按预期工作。可以使用以下命令运行完整的测试套件
$ composer test-all
贡献
请参阅 CONTRIBUTING 和 CODE_OF_CONDUCT 以获取详细信息。
安全
关于 SSL/TLS 证书,安全是一个重要的话题。由于此客户端是 PHP 脚本,它很可能在 Web 服务器上运行。显然,存储在您的 Web 服务器上的私钥永远不应该从网络上访问。
当客户端第一次创建密钥目录时,它将在该目录中存储一个 .htaccess 文件,拒绝所有访问者。始终确保您的密钥不能从网络上访问!我不会对您的私钥公开负责。如果发生这种情况,最简单的解决方案是更改您的账户密钥(如上所述)或停用您的账户并创建一个新的账户。接下来,创建一个新的证书。
如果您发现任何与安全相关的问题,请通过电子邮件发送给 peter@peterstovka.com,而不是使用问题跟踪器。
鸣谢
-
[Paul Dixon][link-author] 重构单元测试和存储接口
-
[Youri van Weegberg][link-author2] 此项目所基于的原始 PHP ACME2 客户端
-
[wutno][link-author3] DNS-over-HTTPS 支持
许可证
MIT 许可证 (MIT)。请参阅 许可证文件 获取更多信息。
开发者
这是一个由 Paul Dixon 开发的分叉版本的裸克隆。