Google EAB支持自动获取及接口反向代理

This commit is contained in:
耗子 2025-05-18 16:16:15 +08:00
parent 300686aa0a
commit ec89fd685b
No known key found for this signature in database
GPG Key ID: C964D7226D045DAA
4 changed files with 563 additions and 479 deletions

View File

@ -1,355 +1,375 @@
<?php
namespace app\lib;
use think\facade\Db;
class CertHelper
{
public static $cert_config = [
'letsencrypt' => [
'name' => 'Let\'s Encrypt',
'class' => 1,
'icon' => 'letsencrypt.ico',
'wildcard' => true,
'max_domains' => 100,
'cname' => true,
'note' => null,
'inputs' => [
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => '用于注册Let\'s Encrypt账号',
'required' => true,
],
'mode' => [
'name' => '环境选择',
'type' => 'radio',
'options' => [
'live' => '正式环境',
'staging' => '测试环境',
],
'value' => 'live'
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'zerossl' => [
'name' => 'ZeroSSL',
'class' => 1,
'icon' => 'zerossl.ico',
'wildcard' => true,
'max_domains' => 100,
'cname' => true,
'note' => null,
'inputs' => [
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => 'EAB申请邮箱',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'google' => [
'name' => 'Google SSL',
'class' => 1,
'icon' => 'google.ico',
'wildcard' => true,
'max_domains' => 100,
'cname' => true,
'note' => '<a href="https://cloud.google.com/certificate-manager/docs/public-ca-tutorial" target="_blank" rel="noreferrer">查看Google SSL账户配置说明</a>',
'inputs' => [
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => 'EAB申请邮箱',
'required' => true,
],
'kid' => [
'name' => 'keyId',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'key' => [
'name' => 'b64MacKey',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'mode' => [
'name' => '环境选择',
'type' => 'radio',
'options' => [
'live' => '正式环境',
'staging' => '测试环境',
],
'value' => 'live'
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'tencent' => [
'name' => '腾讯云免费SSL',
'class' => 2,
'icon' => 'tencent.ico',
'wildcard' => false,
'max_domains' => 1,
'cname' => false,
'note' => '一个账号有50张免费证书额度证书到期或吊销可释放额度。<a href="https://cloud.tencent.com/document/product/400/89868" target="_blank" rel="noreferrer">腾讯云免费SSL简介与额度说明</a>',
'inputs' => [
'SecretId' => [
'name' => 'SecretId',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'SecretKey' => [
'name' => 'SecretKey',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => '申请证书时填写的邮箱',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'aliyun' => [
'name' => '阿里云免费SSL',
'class' => 2,
'icon' => 'aliyun.ico',
'wildcard' => false,
'max_domains' => 1,
'cname' => false,
'note' => '每个自然年有20张免费证书额度证书到期或吊销不释放额度。需要先进入阿里云控制台-<a href="https://yundun.console.aliyun.com/?p=cas#/certExtend/free/cn-hangzhou" target="_blank" rel="noreferrer">数字证书管理服务</a>,购买个人测试证书资源包。',
'inputs' => [
'AccessKeyId' => [
'name' => 'AccessKeyId',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'AccessKeySecret' => [
'name' => 'AccessKeySecret',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'username' => [
'name' => '姓名',
'type' => 'input',
'placeholder' => '申请联系人的姓名',
'required' => true,
],
'phone' => [
'name' => '手机号码',
'type' => 'input',
'placeholder' => '申请联系人的手机号码',
'required' => true,
],
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => '申请联系人的邮箱地址',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'ucloud' => [
'name' => 'UCloud免费SSL',
'class' => 2,
'icon' => 'ucloud.ico',
'wildcard' => false,
'max_domains' => 1,
'cname' => false,
'note' => '一个账号有40张免费证书额度证书到期或吊销可释放额度。',
'inputs' => [
'PublicKey' => [
'name' => '公钥',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'PrivateKey' => [
'name' => '私钥',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'username' => [
'name' => '姓名',
'type' => 'input',
'placeholder' => '申请联系人的姓名',
'required' => true,
],
'phone' => [
'name' => '手机号码',
'type' => 'input',
'placeholder' => '申请联系人的手机号码',
'required' => true,
],
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => '申请联系人的邮箱地址',
'required' => true,
],
]
],
'customacme' => [
'name' => '自定义ACME',
'class' => 1,
'icon' => 'ssl.ico',
'wildcard' => true,
'max_domains' => 100,
'cname' => true,
'note' => null,
'inputs' => [
'directory' => [
'name' => 'ACME地址',
'type' => 'input',
'placeholder' => 'ACME Directory 地址',
'required' => true,
],
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => '证书申请邮箱',
'required' => true,
],
'kid' => [
'name' => 'EAB KID',
'type' => 'input',
'placeholder' => '留空则不使用EAB认证',
],
'key' => [
'name' => 'EAB HMAC Key',
'type' => 'input',
'placeholder' => '留空则不使用EAB认证',
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
];
public static $class_config = [
1 => '基于ACME的SSL证书',
2 => '云服务商的SSL证书',
];
public static function getList()
{
return self::$cert_config;
}
private static function getConfig($aid)
{
$account = Db::name('cert_account')->where('id', $aid)->find();
if (!$account) return false;
return $account;
}
public static function getInputs($type, $config = null)
{
$config = $config ? json_decode($config, true) : [];
$inputs = self::$cert_config[$type]['inputs'];
foreach ($inputs as &$input) {
if (isset($config[$input['name']])) {
$input['value'] = $config[$input['name']];
}
}
return $inputs;
}
/**
* @return CertInterface|bool
*/
public static function getModel($aid)
{
$account = self::getConfig($aid);
if (!$account) return false;
$type = $account['type'];
$class = "\\app\\lib\\cert\\{$type}";
if (class_exists($class)) {
$config = json_decode($account['config'], true);
$ext = $account['ext'] ? json_decode($account['ext'], true) : null;
$model = new $class($config, $ext);
return $model;
}
return false;
}
/**
* @return CertInterface|bool
*/
public static function getModel2($type, $config, $ext = null)
{
$class = "\\app\\lib\\cert\\{$type}";
if (class_exists($class)) {
$model = new $class($config, $ext);
return $model;
}
return false;
}
public static function getPfx($fullchain, $privatekey, $pwd = '123456'){
openssl_pkcs12_export($fullchain, $pfx, $privatekey, $pwd);
return $pfx;
}
}
<?php
namespace app\lib;
use think\facade\Db;
class CertHelper
{
public static $cert_config = [
'letsencrypt' => [
'name' => 'Let\'s Encrypt',
'class' => 1,
'icon' => 'letsencrypt.ico',
'wildcard' => true,
'max_domains' => 100,
'cname' => true,
'note' => null,
'inputs' => [
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => '用于注册Let\'s Encrypt账号',
'required' => true,
],
'mode' => [
'name' => '环境选择',
'type' => 'radio',
'options' => [
'live' => '正式环境',
'staging' => '测试环境',
],
'value' => 'live'
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'zerossl' => [
'name' => 'ZeroSSL',
'class' => 1,
'icon' => 'zerossl.ico',
'wildcard' => true,
'max_domains' => 100,
'cname' => true,
'note' => null,
'inputs' => [
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => 'EAB申请邮箱',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'google' => [
'name' => 'Google SSL',
'class' => 1,
'icon' => 'google.ico',
'wildcard' => true,
'max_domains' => 100,
'cname' => true,
'note' => 'EAB支持通过第三方接口<a href="https://panel.haozi.net" target="_blank" rel="noreferrer">(耗子面板提供)</a>自动获取(不支持测试环境)或手动输入,<a href="https://cloud.google.com/certificate-manager/docs/public-ca-tutorial" target="_blank" rel="noreferrer">查看Google SSL账户手动配置说明</a>',
'inputs' => [
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => 'EAB申请邮箱',
'required' => true,
],
'eabMode' => [
'name' => 'EAB获取方式',
'type' => 'radio',
'options' => [
'auto' => '自动获取',
'manual' => '手动输入',
],
'value' => 'auto'
],
'kid' => [
'name' => 'keyId',
'type' => 'input',
'placeholder' => '',
'required' => true,
'show' => 'eabMode==\'manual\'',
],
'key' => [
'name' => 'b64MacKey',
'type' => 'input',
'placeholder' => '',
'required' => true,
'show' => 'eabMode==\'manual\'',
],
'mode' => [
'name' => '环境选择',
'type' => 'radio',
'options' => [
'live' => '正式环境',
'staging' => '测试环境',
],
'value' => 'live'
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
'2' => '是(反向代理)'
],
'value' => '0'
],
'proxy_url' => [
'name' => '反向代理地址',
'type' => 'input',
'placeholder' => 'https://dv.acme-v02.api.pki.goog',
'required' => true,
'show' => 'proxy==2',
],
]
],
'tencent' => [
'name' => '腾讯云免费SSL',
'class' => 2,
'icon' => 'tencent.ico',
'wildcard' => false,
'max_domains' => 1,
'cname' => false,
'note' => '一个账号有50张免费证书额度证书到期或吊销可释放额度。<a href="https://cloud.tencent.com/document/product/400/89868" target="_blank" rel="noreferrer">腾讯云免费SSL简介与额度说明</a>',
'inputs' => [
'SecretId' => [
'name' => 'SecretId',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'SecretKey' => [
'name' => 'SecretKey',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => '申请证书时填写的邮箱',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'aliyun' => [
'name' => '阿里云免费SSL',
'class' => 2,
'icon' => 'aliyun.ico',
'wildcard' => false,
'max_domains' => 1,
'cname' => false,
'note' => '每个自然年有20张免费证书额度证书到期或吊销不释放额度。需要先进入阿里云控制台-<a href="https://yundun.console.aliyun.com/?p=cas#/certExtend/free/cn-hangzhou" target="_blank" rel="noreferrer">数字证书管理服务</a>,购买个人测试证书资源包。',
'inputs' => [
'AccessKeyId' => [
'name' => 'AccessKeyId',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'AccessKeySecret' => [
'name' => 'AccessKeySecret',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'username' => [
'name' => '姓名',
'type' => 'input',
'placeholder' => '申请联系人的姓名',
'required' => true,
],
'phone' => [
'name' => '手机号码',
'type' => 'input',
'placeholder' => '申请联系人的手机号码',
'required' => true,
],
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => '申请联系人的邮箱地址',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'ucloud' => [
'name' => 'UCloud免费SSL',
'class' => 2,
'icon' => 'ucloud.ico',
'wildcard' => false,
'max_domains' => 1,
'cname' => false,
'note' => '一个账号有40张免费证书额度证书到期或吊销可释放额度。',
'inputs' => [
'PublicKey' => [
'name' => '公钥',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'PrivateKey' => [
'name' => '私钥',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'username' => [
'name' => '姓名',
'type' => 'input',
'placeholder' => '申请联系人的姓名',
'required' => true,
],
'phone' => [
'name' => '手机号码',
'type' => 'input',
'placeholder' => '申请联系人的手机号码',
'required' => true,
],
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => '申请联系人的邮箱地址',
'required' => true,
],
]
],
'customacme' => [
'name' => '自定义ACME',
'class' => 1,
'icon' => 'ssl.ico',
'wildcard' => true,
'max_domains' => 100,
'cname' => true,
'note' => null,
'inputs' => [
'directory' => [
'name' => 'ACME地址',
'type' => 'input',
'placeholder' => 'ACME Directory 地址',
'required' => true,
],
'email' => [
'name' => '邮箱地址',
'type' => 'input',
'placeholder' => '证书申请邮箱',
'required' => true,
],
'kid' => [
'name' => 'EAB KID',
'type' => 'input',
'placeholder' => '留空则不使用EAB认证',
],
'key' => [
'name' => 'EAB HMAC Key',
'type' => 'input',
'placeholder' => '留空则不使用EAB认证',
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
];
public static $class_config = [
1 => '基于ACME的SSL证书',
2 => '云服务商的SSL证书',
];
public static function getList()
{
return self::$cert_config;
}
private static function getConfig($aid)
{
$account = Db::name('cert_account')->where('id', $aid)->find();
if (!$account) return false;
return $account;
}
public static function getInputs($type, $config = null)
{
$config = $config ? json_decode($config, true) : [];
$inputs = self::$cert_config[$type]['inputs'];
foreach ($inputs as &$input) {
if (isset($config[$input['name']])) {
$input['value'] = $config[$input['name']];
}
}
return $inputs;
}
/**
* @return CertInterface|bool
*/
public static function getModel($aid)
{
$account = self::getConfig($aid);
if (!$account) return false;
$type = $account['type'];
$class = "\\app\\lib\\cert\\{$type}";
if (class_exists($class)) {
$config = json_decode($account['config'], true);
$ext = $account['ext'] ? json_decode($account['ext'], true) : null;
$model = new $class($config, $ext);
return $model;
}
return false;
}
/**
* @return CertInterface|bool
*/
public static function getModel2($type, $config, $ext = null)
{
$class = "\\app\\lib\\cert\\{$type}";
if (class_exists($class)) {
$model = new $class($config, $ext);
return $model;
}
return false;
}
public static function getPfx($fullchain, $privatekey, $pwd = '123456')
{
openssl_pkcs12_export($fullchain, $pfx, $privatekey, $pwd);
return $pfx;
}
}

View File

@ -25,7 +25,7 @@ class ACMECert extends ACMEv2
$protected = array(
'alg' => 'HS256',
'kid' => $eab_kid,
'url' => $this->resources['newAccount']
'url' => $this->unproxiedURL($this->resources['newAccount'])
);
$payload = $this->jwk_header['jwk'];

View File

@ -8,13 +8,22 @@ class ACMEv2
{ // Communication with Let's Encrypt via ACME v2 protocol
protected
$ch = null, $logger = true, $bits, $sha_bits, $directory, $resources, $jwk_header, $kid_header, $account_key, $thumbprint, $nonce = null, $proxy;
$ch = null, $logger = true, $bits, $sha_bits, $directory, $resources, $jwk_header, $kid_header, $account_key, $thumbprint, $nonce = null, $proxy, $proxy_config = null;
private $delay_until = null;
public function __construct($directory, $proxy = false)
{
/**
* @param $directory string ACME directory URL
* @param $proxy int 代理模式0为不使用代理1为使用系统代理2为使用反向代理
* @param null $proxy_config array 反向代理配置proxy参数为2时必填
* @throws Exception
*/
public function __construct($directory, $proxy = 0, $proxy_config = null)
{
$this->directory = $directory;
$this->proxy = $proxy;
if ($proxy == 2) {
$this->proxy_config = $proxy_config;
}
}
public function __destruct()
@ -190,7 +199,8 @@ class ACMEv2
}
if (!$this->kid_header['kid'] && $type === 'newAccount') {
$this->kid_header['kid'] = $ret['headers']['location'];
// 反向替换反向代理配置,防止破坏签名
$this->kid_header['kid'] = $this->unproxiedURL($ret['headers']['location']);
$this->log('AccountID: ' . $this->kid_header['kid']);
}
@ -218,7 +228,8 @@ class ACMEv2
throw new Exception('Resource "' . $type . '" not available.');
}
$protected['url'] = $this->resources[$type];
// 反向替换反向代理配置,防止破坏签名
$protected['url'] = $this->unproxiedURL($this->resources[$type]);
$protected64 = $this->base64url(json_encode($protected, JSON_UNESCAPED_SLASHES));
$payload64 = $this->base64url(is_string($payload) ? $payload : json_encode($payload, JSON_UNESCAPED_SLASHES));
@ -285,6 +296,9 @@ class ACMEv2
$this->delay_until = null;
}
// 替换反向代理配置
$url = $this->proxiedURL($url);
$method = $data === false ? 'HEAD' : ($data === null ? 'GET' : 'POST');
$user_agent = 'ACMECert v3.4.0 (+https://github.com/skoerfgen/ACMECert)';
$header = ($data === null || $data === false) ? array() : array('Content-Type: application/jose+json');
@ -406,4 +420,30 @@ class ACMEv2
}, isset($error['subproblems']) ? $error['subproblems'] : array())
);
}
// 替换反向代理配置
protected function proxiedURL($url)
{
if ($this->proxy == 2) {
return str_replace(
$this->proxy_config['origin'],
$this->proxy_config['proxy'],
$url
);
}
return $url;
}
// 反向替换反向代理配置
protected function unproxiedURL($url)
{
if ($this->proxy == 2) {
return str_replace(
$this->proxy_config['proxy'],
$this->proxy_config['origin'],
$url
);
}
return $url;
}
}

View File

@ -1,118 +1,142 @@
<?php
namespace app\lib\cert;
use app\lib\CertInterface;
use app\lib\acme\ACMECert;
use Exception;
class google implements CertInterface
{
private $directories = array(
'live' => 'https://dv.acme-v02.api.pki.goog/directory',
'staging' => 'https://dv.acme-v02.test-api.pki.goog/directory'
);
private $ac;
private $config;
private $ext;
public function __construct($config, $ext = null)
{
$this->config = $config;
if (empty($config['mode'])) $config['mode'] = 'live';
$this->ac = new ACMECert($this->directories[$config['mode']], $config['proxy']==1);
if ($ext) {
$this->ext = $ext;
$this->ac->loadAccountKey($ext['key']);
$this->ac->setAccount($ext['kid']);
}
}
public function register()
{
if (empty($this->config['email'])) throw new Exception('邮件地址不能为空');
if (empty($this->config['kid']) || empty($this->config['key'])) throw new Exception('必填参数不能为空');
if (!empty($this->ext['key'])) {
$kid = $this->ac->registerEAB(true, $this->config['kid'], $this->config['key'], $this->config['email']);
return ['kid' => $kid, 'key' => $this->ext['key']];
}
$key = $this->ac->generateRSAKey(2048);
$this->ac->loadAccountKey($key);
$kid = $this->ac->registerEAB(true, $this->config['kid'], $this->config['key'], $this->config['email']);
return ['kid' => $kid, 'key' => $key];
}
public function buyCert($domainList, &$order)
{
}
public function createOrder($domainList, &$order, $keytype, $keysize)
{
$domain_config = [];
foreach ($domainList as $domain) {
if (empty($domain)) continue;
$domain_config[$domain] = ['challenge' => 'dns-01'];
}
if (empty($domain_config)) throw new Exception('域名列表不能为空');
$order = $this->ac->createOrder($domain_config);
$dnsList = [];
if (!empty($order['challenges'])) {
foreach ($order['challenges'] as $opts) {
$mainDomain = getMainDomain($opts['domain']);
$name = str_replace('.' . $mainDomain, '', $opts['key']);
/*if (!array_key_exists($mainDomain, $dnsList)) {
$dnsList[$mainDomain][] = ['name' => '@', 'type' => 'CAA', 'value' => '0 issue "pki.goog"'];
}*/
$dnsList[$mainDomain][] = ['name' => $name, 'type' => 'TXT', 'value' => $opts['value']];
}
}
return $dnsList;
}
public function authOrder($domainList, $order)
{
$this->ac->authOrder($order);
}
public function getAuthStatus($domainList, $order)
{
return true;
}
public function finalizeOrder($domainList, $order, $keytype, $keysize)
{
if (empty($domainList)) throw new Exception('域名列表不能为空');
if ($keytype == 'ECC') {
if (empty($keysize)) $keysize = '384';
$private_key = $this->ac->generateECKey($keysize);
} else {
if (empty($keysize)) $keysize = '2048';
$private_key = $this->ac->generateRSAKey($keysize);
}
$fullchain = $this->ac->finalizeOrder($domainList, $order, $private_key);
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
return ['private_key' => $private_key, 'fullchain' => $fullchain, 'issuer' => $certInfo['issuer']['CN'], 'subject' => $certInfo['subject']['CN'], 'validFrom' => $certInfo['validFrom_time_t'], 'validTo' => $certInfo['validTo_time_t']];
}
public function revoke($order, $pem)
{
$this->ac->revoke($pem);
}
public function cancel($order)
{
}
public function setLogger($func)
{
$this->ac->setLogger($func);
}
}
<?php
namespace app\lib\cert;
use app\lib\CertInterface;
use app\lib\acme\ACMECert;
use Exception;
class google implements CertInterface
{
private $directories = array(
'live' => 'https://dv.acme-v02.api.pki.goog',
'staging' => 'https://dv.acme-v02.test-api.pki.goog'
);
private $ac;
private $config;
private $ext;
public function __construct($config, $ext = null)
{
$this->config = $config;
if (empty($config['mode'])) $config['mode'] = 'live';
if (empty($config['proxy_url'])) $config['proxy_url'] = '';
$this->ac = new ACMECert($this->directories[$config['mode']] . '/directory', (int)$config['proxy'], [
'origin' => $this->directories[$config['mode']],
'proxy' => rtrim($config['proxy_url'], '/'),
]);
if ($ext) {
$this->ext = $ext;
$this->ac->loadAccountKey($ext['key']);
$this->ac->setAccount($ext['kid']);
}
}
public function register()
{
if (empty($this->config['email'])) throw new Exception('邮件地址不能为空');
if (isset($this->config['eabMode']) && $this->config['eabMode'] == 'auto') {
$eab = $this->getEAB();
} else {
$eab = ['kid' => $this->config['kid'], 'key' => $this->config['key']];
}
if (!empty($this->ext['key'])) {
$kid = $this->ac->registerEAB(true, $eab['kid'], $eab['key'], $this->config['email']);
return ['kid' => $kid, 'key' => $this->ext['key']];
}
$key = $this->ac->generateRSAKey(2048);
$this->ac->loadAccountKey($key);
$kid = $this->ac->registerEAB(true, $eab['kid'], $eab['key'], $this->config['email']);
return ['kid' => $kid, 'key' => $key];
}
public function buyCert($domainList, &$order)
{
}
public function createOrder($domainList, &$order, $keytype, $keysize)
{
$domain_config = [];
foreach ($domainList as $domain) {
if (empty($domain)) continue;
$domain_config[$domain] = ['challenge' => 'dns-01'];
}
if (empty($domain_config)) throw new Exception('域名列表不能为空');
$order = $this->ac->createOrder($domain_config);
$dnsList = [];
if (!empty($order['challenges'])) {
foreach ($order['challenges'] as $opts) {
$mainDomain = getMainDomain($opts['domain']);
$name = str_replace('.' . $mainDomain, '', $opts['key']);
/*if (!array_key_exists($mainDomain, $dnsList)) {
$dnsList[$mainDomain][] = ['name' => '@', 'type' => 'CAA', 'value' => '0 issue "pki.goog"'];
}*/
$dnsList[$mainDomain][] = ['name' => $name, 'type' => 'TXT', 'value' => $opts['value']];
}
}
return $dnsList;
}
public function authOrder($domainList, $order)
{
$this->ac->authOrder($order);
}
public function getAuthStatus($domainList, $order)
{
return true;
}
public function finalizeOrder($domainList, $order, $keytype, $keysize)
{
if (empty($domainList)) throw new Exception('域名列表不能为空');
if ($keytype == 'ECC') {
if (empty($keysize)) $keysize = '384';
$private_key = $this->ac->generateECKey($keysize);
} else {
if (empty($keysize)) $keysize = '2048';
$private_key = $this->ac->generateRSAKey($keysize);
}
$fullchain = $this->ac->finalizeOrder($domainList, $order, $private_key);
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
return ['private_key' => $private_key, 'fullchain' => $fullchain, 'issuer' => $certInfo['issuer']['CN'], 'subject' => $certInfo['subject']['CN'], 'validFrom' => $certInfo['validFrom_time_t'], 'validTo' => $certInfo['validTo_time_t']];
}
public function revoke($order, $pem)
{
$this->ac->revoke($pem);
}
public function cancel($order)
{
}
public function setLogger($func)
{
$this->ac->setLogger($func);
}
private function getEAB()
{
$api = "https://gts.rat.dev/eab";
$response = curl_client($api, null, null, null, null, $this->config['proxy'] == 1, 'GET', 10);
$result = json_decode($response['body'], true);
if (!isset($result['msg'])) {
throw new Exception('解析返回数据失败:' . $response['body']);
} elseif ($result['msg'] != 'success') {
throw new Exception('获取EAB失败' . $result['msg']);
} elseif (empty($result['data']['key_id']) || empty($result['data']['mac_key'])) {
throw new Exception('获取EAB失败返回数据不完整');
}
return ['kid' => $result['data']['key_id'], 'key' => $result['data']['mac_key']];
}
}