自行车 / ACME证书
Let's Encrypt(ACME v2 - RFC 8555)的PHP客户端库
Requires
- php: >=5.3.0
- ext-openssl: *
This package is not auto-updated.
Last update: 2024-10-03 21:31:15 UTC
README
Let's Encrypt(https://letsencrypt.openssl.ac.cn/)(ACME v2 - RFC 8555)的PHP客户端库
版本:2.8
描述
ACMECert旨在通过几行PHP代码帮助您设置自动化的SSL/TLS证书/续订流程。
它是独立的,包含一系列函数,允许您
请参阅函数参考以获取完整列表
它将获取证书的复杂性抽象到单个函数getCertificateChain中,在该函数中,您指定您希望获取证书的一组域以及要使用的挑战类型(支持所有挑战类型)。此函数将用户定义的回调函数作为第三个参数,每次需要满足挑战时都会调用该回调函数。您需要自行设置/删除挑战令牌
$handler=function($opts){ // Write code to setup the challenge token here. // Return a function that gets called when the challenge token should be removed again: return function($opts){ // Write code to remove previously setup challenge token. }; }; $ac->getCertificateChain(..., ..., $handler);
请参阅getCertificateChain的描述以了解有关回调函数的详细信息。
以下提供了获取证书的示例。
ACMECert中的每个函数在失败时,除了返回FALSE外,还会抛出一个异常,如果ACME服务器响应了错误消息,则抛出ACME_Exception。
要求
用法示例
需要ACMECert
require 'ACMECert.php';
选择生产环境或测试环境
生产
$ac=new ACMECert();
测试
$ac=new ACMECert(false);
生成RSA私钥
$key=$ac->generateRSAKey(2048); file_put_contents('account_key.pem',$key);
等同于:
openssl genrsa -out account_key.pem 2048
生成EC私钥
$key=$ac->generateECKey('P-384'); file_put_contents('account_key.pem',$key);
等同于:
openssl ecparam -name secp384r1 -genkey -noout -out account_key.pem
在Let's Encrypt中注册账户密钥
$ac->loadAccountKey('file://'.'account_key.pem'); $ret=$ac->register(true,'info@example.com'); print_r($ret);
警告:通过将第一个参数传递为TRUE,您同意Let's Encrypt的服务条款。有关更多信息,请参阅Let’s Encrypt订阅者协议。
获取账户信息
$ac->loadAccountKey('file://'.'account_key.pem'); $ret=$ac->getAccount(); print_r($ret);
账户密钥滚动更新
$ac->loadAccountKey('file://'.'account_key.pem'); $ret=$ac->keyChange('file://'.'new_account_key.pem'); print_r($ret);
停用账户
$ac->loadAccountKey('file://'.'account_key.pem'); $ret=$ac->deactivateAccount(); print_r($ret);
撤销证书
$ac->loadAccountKey('file://'.'account_key.pem'); $ac->revoke('file://'.'fullchain.pem');
获取剩余天数
$days=$ac->getRemainingDays('file://'.'fullchain.pem'); // certificate or certificate-chain if ($days>30) { // renew 30 days before expiry die('Certificate still good, exiting..'); } // get new certificate here..
这允许您运行续订脚本,而无需精确计时,只需经常运行即可。(cronjob)
使用http-01挑战获取证书
$ac->loadAccountKey('file://'.'account_key.pem'); $domain_config=array( 'test1.example.com'=>array('challenge'=>'http-01','docroot'=>'/var/www/vhosts/test1.example.com'), 'test2.example.com'=>array('challenge'=>'http-01','docroot'=>'/var/www/vhosts/test2.example.com') ); $handler=function($opts){ $fn=$opts['config']['docroot'].$opts['key']; @mkdir(dirname($fn),0777,true); file_put_contents($fn,$opts['value']); return function($opts){ unlink($opts['config']['docroot'].$opts['key']); }; }; $fullchain=$ac->getCertificateChain('file://'.'cert_private_key.pem',$domain_config,$handler); file_put_contents('fullchain.pem',$fullchain);
使用所有(http-01、dns-01和tls-alpn-01)挑战类型一起获取证书
$ac->loadAccountKey('file://'.'account_key.pem'); $domain_config=array( 'example.com'=>array('challenge'=>'http-01','docroot'=>'/var/www/vhosts/example.com'), '*.example.com'=>array('challenge'=>'dns-01'), 'test.example.org'=>array('challenge'=>'tls-alpn-01') ); $handler=function($opts) use ($ac){ switch($opts['config']['challenge']){ case 'http-01': // automatic example: challenge directory/file is created.. $fn=$opts['config']['docroot'].$opts['key']; @mkdir(dirname($fn),0777,true); file_put_contents($fn,$opts['value']); return function($opts) use ($fn){ // ..and removed after validation completed unlink($fn); }; break; case 'dns-01': // manual example: echo 'Create DNS-TXT-Record '.$opts['key'].' with value '.$opts['value']."\n"; readline('Ready?'); return function($opts){ echo 'Remove DNS-TXT-Record '.$opts['key'].' with value '.$opts['value']."\n"; }; break; case 'tls-alpn-01': $cert=$ac->generateALPNCertificate('file://'.'some_private_key.pem',$opts['domain'],$opts['value']); // Use $cert and some_private_key.pem(<- does not have to be a specific key, // just make sure you generated one) to serve the certificate for $opts['domain'] // This example uses an included ALPN Responder - a standalone https-server // written in a few lines of node.js - which is able to complete this challenge. // store the generated verification certificate to be used by the ALPN Responder. file_put_contents('alpn_cert.pem',$cert); // To keep this example simple, the included Example ALPN Responder listens on port 443, // so - for the sake of this example - you have to stop the webserver here, like: shell_exec('/etc/init.d/apache2 stop'); // Start ALPN Responder (requires node.js) $resource=proc_open( 'node alpn_responder.js some_private_key.pem alpn_cert.pem', array( 0=>array('pipe','r'), 1=>array('pipe','w') ), $pipes ); // wait until alpn responder is listening fgets($pipes[1]); return function($opts) use ($resource,$pipes){ // Stop ALPN Responder fclose($pipes[0]); fclose($pipes[1]); proc_close($resource); shell_exec('/etc/init.d/apache2 start'); }; break; } }; // Example for using a pre-generated CSR as input to getCertificateChain instead of a private key: // $csr=$ac->generateCSR('file://'.'cert_private_key.pem',array_keys($domain_config)); // $fullchain=$ac->getCertificateChain($csr,$domain_config,$handler); $fullchain=$ac->getCertificateChain('file://'.'cert_private_key.pem',$domain_config,$handler); file_put_contents('fullchain.pem',$fullchain);
日志记录
ACMECert使用error_log记录其操作,默认情况下在PHP CLI中记录到stderr,因此易于记录到文件。
error_reporting(E_ALL); ini_set('log_errors',1); ini_set('error_log',dirname(__FILE__).'/ACMECert.log');
ACME_Exception
如果ACME-Server返回错误消息,则会抛出ACME_Exception。(ACME_Exception扩展Exception)
ACME_Exception有两个附加功能
getType()获取ACME错误代码
require 'ACMECert.php'; $ac=new ACMECert(); $ac->loadAccountKey('file://'.'account_key.pem'); try { echo $ac->getAccountID().PHP_EOL; }catch(ACME_Exception $e){ if ($e->getType()=='urn:ietf:params:acme:error:accountDoesNotExist'){ echo 'Account does not exist'.PHP_EOL; }else{ throw $e; // another error occured } }
getSubproblems()获取一个包含ACME_Exception的数组,如果ACME-Server返回多个错误
try { $cert=$ac->getCertificateChain('file://'.'cert_private_key.pem',$domain_config,$handler); } catch (ACME_Exception $e){ $ac->log($e->getMessage()); // log original error foreach($e->getSubproblems() as $subproblem){ $ac->log($subproblem->getMessage()); // log sub errors } }
函数参考
ACMECert::__construct
创建一个新的ACMECert实例。
public ACMECert::__construct ( bool $live = TRUE )
参数
live
返回值
返回一个新的ACMECert实例。
ACMECert::generateRSAKey
生成RSA私钥(用作账户密钥或证书的私钥)。
public string ACMECert::generateRSAKey ( int $bits = 2048 )
参数
bitsRSA密钥大小,以位为单位。
返回值
以PEM编码字符串的形式返回生成的RSA私钥。
错误/异常
如果无法生成RSA密钥,则抛出
Exception。
ACMECert::generateECKey
生成椭圆曲线(EC)私钥(用作账户密钥或证书的私钥)。
public string ACMECert::generateECKey ( string $curve_name = 'P-384' )
参数
curve_nameLet’s Encrypt支持曲线
P-256(prime256v1)P-384(secp384r1)P-521(secp521r1)
返回值
以PEM编码字符串的形式返回生成的EC私钥。
错误/异常
如果无法生成EC密钥,则抛出
Exception。
ACMECert::loadAccountKey
加载账户密钥。
public void ACMECert::loadAccountKey ( mixed $account_key_pem )
参数
account_key_pem可以是以下之一
- 包含PEM格式私钥的字符串。
- 以
file://开始的字符串,包含从其中读取PEM格式私钥的文件名。
返回值
没有返回值。
错误/异常
如果无法加载账户密钥,则抛出
Exception。
ACMECert::register
将加载的账户密钥与Let's Encrypt账户关联,并可选地指定联系人。
public array ACMECert::register ( bool $termsOfServiceAgreed = FALSE [, mixed $contacts = array() ] )
参数
termsOfServiceAgreed警告:通过传递
TRUE,您同意Let's Encrypt的服务条款。有关更多信息,请参阅Let’s Encrypt订阅者协议。为了成功注册账户,必须设置为
TRUE。
contacts可以是以下之一
- 包含电子邮件地址的字符串
- 电子邮件地址数组
返回值
返回一个包含账户信息的数组。
错误/异常
如果服务器返回错误消息,则抛出
ACME_Exception;如果发生其他注册错误,则抛出Exception。
ACMECert::update
更新账户联系人。
public array ACMECert::update ( mixed $contacts = array() )
参数
contacts可以是以下之一
- 包含电子邮件地址的字符串
- 电子邮件地址数组
返回值
返回一个包含账户信息的数组。
错误/异常
如果服务器返回错误消息或更新账户时发生其他错误,则抛出
ACME_Exception。
ACMECert::getAccount
获取账户信息。
public array ACMECert::getAccount()
返回值
返回一个包含账户信息的数组。
错误/异常
如果服务器返回错误消息或获取账户信息时发生其他错误,则抛出
ACME_Exception。
ACMECert::getAccountID
获取账户ID。
public string ACMECert::getAccountID()
返回值
返回账户ID
错误/异常
如果服务器返回了错误消息,将抛出
ACME_Exception;如果在获取账户 ID 时发生其他错误,将抛出Exception。
ACMECert::keyChange
账户密钥滚动(用另一个密钥交换当前账户密钥)。
如果账户密钥滚动成功,则新账户密钥将通过
loadAccountKey自动加载。
public array ACMECert::keyChange ( mixed $new_account_key_pem )
参数
new_account_key_pem可以是以下之一
- 包含PEM格式私钥的字符串。
- 以
file://开始的字符串,包含从其中读取PEM格式私钥的文件名。
返回值
返回一个包含账户信息的数组。
错误/异常
如果服务器返回了错误消息或在密钥更改期间发生其他错误,将抛出
ACME_Exception。
ACMECert::deactivateAccount
停用账户。
public array ACMECert::deactivateAccount()
返回值
返回一个包含账户信息的数组。
错误/异常
如果在账户停用期间服务器返回了错误消息或发生其他错误,将抛出
ACME_Exception。
ACMECert::getCertificateChain
获取证书链(证书和中间证书)。
这是 Apache >= 2.4.8 需要的 SSLCertificateFile,也是 Nginx 需要的 ssl_certificate。
public string ACMECert::getCertificateChain ( mixed $pem, array $domain_config, callable $callback, bool $authz_reuse = TRUE )
参数
pem用于证书的私钥(在这种情况下,使用给定的密钥自动生成所需的 CSR)或以下格式之一的已存在 CSR:
- 包含PEM格式私钥的字符串。
- 以
file://开头的字符串,包含读取 PEM 编码私钥的文件名。
或- 以
file://开头的字符串,包含读取 PEM 编码 CSR 的文件名。- 包含 CSR 内容的字符串,PEM 编码,可能以
-----BEGIN CERTIFICATE REQUEST-----开头。
domain_config一个数组,定义了要获取证书的域及其相应的挑战类型(每张证书最多 100 个域)。
第一个用作证书的
Common Name。以下是一个示例结构
$domain_config=array( '*.example.com'=>array('challenge'=>'dns-01'), 'test.example.org'=>array('challenge'=>'tls-alpn-01') 'test.example.net'=>array('challenge'=>'http-01','docroot'=>'/var/www/vhosts/test1.example.com'), );提示:通配符证书(
*.example.com)仅支持dns-01挑战类型。
challenge是必需的,必须是http-01、dns-01或tls-alpn-01之一。所有其他键都是可选的,由您使用,并且在回调函数中作为$opts['config']可用(参见 http-01 示例,其中以这种方式使用docroot)
callback每次需要满足挑战时都会调用的回调函数。
callable callback ( array $opts )在回调函数内部,您可以返回另一个回调函数,该函数在验证完成并且可以再次删除挑战令牌后调用。
提示:要在回调函数内部访问父作用域的变量,请使用
use语言结构$handler=function($opts) use ($variable_from_parent_scope){}; ^^^参数
opts
$opts['domain']要验证的域名。
$opts['config']与
domain_config数组对应的元素。
$opts['key']和$opts['value']根据所选的挑战类型包含以下内容:
authz_reuse(默认:TRUE)如果为
FALSE,则回调函数对于每个域都会被调用,不会因为可能已经复用的有效授权(authz)而跳过。这是通过在获取新的授权之前停用已有效的授权来实现的。提示:在正常情况下,这仅在测试回调函数时需要,在生产环境中不需要!
返回值
返回 PEM 编码的证书链。
错误/异常
如果服务器返回了错误消息或在获取证书期间发生其他错误,将抛出
ACME_Exception。
ACMECert::revoke
吊销证书。
public void ACMECert::revoke ( mixed $pem )
参数
pem可以是以下之一
- 一个以
file://开头的字符串,包含要读取的PEM编码证书或证书链的文件名。- 包含证书或证书链内容的字符串,PEM编码,可能以
-----BEGIN CERTIFICATE-----开头
返回值
没有返回值。
如果函数在无异常的情况下完成,则证书已成功吊销。
错误/异常
如果服务器响应了错误消息,则抛出
ACME_Exception,如果在吊销证书时发生其他错误,则抛出Exception。
ACMECert::generateCSR
为一系列域名生成CSR。
public string ACMECert::generateCSR ( mixed $private_key, array $domains )
参数
private_key可以是以下之一
- 包含PEM格式私钥的字符串。
- 以
file://开始的字符串,包含从其中读取PEM格式私钥的文件名。
domains域名数组
返回值
以字符串形式返回生成的CSR。
错误/异常
如果CSR无法生成,则抛出
Exception。
ACMECert::generateALPNCertificate
生成包含用于 tls-alpn-01 挑战的acmeIdentifier扩展的自签名验证证书。
public string ACMECert::generateALPNCertificate ( mixed $private_key, string $domain, string $token )
参数
private_key用于证书的私钥。
可以是以下之一
- 包含PEM格式私钥的字符串。
- 以
file://开始的字符串,包含从其中读取PEM格式私钥的文件名。
domain要验证的域名。
token验证令牌。
返回值
返回PEM编码的验证证书。
错误/异常
如果证书无法生成,则抛出
Exception。
ACMECert::parseCertificate
获取证书信息。
public array ACMECert::parseCertificate ( mixed $pem )
参数
pem可以是以下之一
- 一个以
file://开头的字符串,包含要读取的PEM编码证书或证书链的文件名。- 包含证书或证书链内容的字符串,PEM编码,可能以
-----BEGIN CERTIFICATE-----开头
返回值
返回包含证书信息的数组。
错误/异常
如果证书无法解析,则抛出
Exception。
ACMECert::getRemainingDays
获取证书仍有效的天数。
public float ACMECert::getRemainingDays ( mixed $pem )
参数
pem可以是以下之一
- 一个以
file://开头的字符串,包含要读取的PEM编码证书或证书链的文件名。- 包含证书或证书链内容的字符串,PEM编码,可能以
-----BEGIN CERTIFICATE-----开头
返回值
返回证书仍有效的天数。
错误/异常
如果证书无法解析,则抛出
Exception。
MIT许可协议
版权所有 (c) 2018 Stefan Körfgen
特此授予任何获得本软件及其相关文档副本(“软件”)的人权,可以在不受限制的情况下使用软件,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售软件的副本,并允许向软件提供者提供软件的人这样做,前提是符合以下条件
上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。
软件按“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性、针对特定目的的适用性和非侵权性保证。在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论此类责任源于合同、侵权或其他方式,无论是否与软件或软件的使用或其他方式有关。