diff --git a/app/lib/CertHelper.php b/app/lib/CertHelper.php
index 81debda..98ccef9 100644
--- a/app/lib/CertHelper.php
+++ b/app/lib/CertHelper.php
@@ -1,355 +1,375 @@
- [
- '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' => '查看Google SSL账户配置说明',
- '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张免费证书额度,证书到期或吊销可释放额度。腾讯云免费SSL简介与额度说明',
- '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张免费证书额度,证书到期或吊销不释放额度。需要先进入阿里云控制台-数字证书管理服务,购买个人测试证书资源包。',
- '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;
- }
-}
+ [
+ '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支持通过第三方接口(耗子面板提供)自动获取(不支持测试环境)或手动输入,查看Google SSL账户手动配置说明',
+ '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张免费证书额度,证书到期或吊销可释放额度。腾讯云免费SSL简介与额度说明',
+ '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张免费证书额度,证书到期或吊销不释放额度。需要先进入阿里云控制台-数字证书管理服务,购买个人测试证书资源包。',
+ '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;
+ }
+}
diff --git a/app/lib/acme/ACMECert.php b/app/lib/acme/ACMECert.php
index db6e275..5c7ab27 100644
--- a/app/lib/acme/ACMECert.php
+++ b/app/lib/acme/ACMECert.php
@@ -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'];
diff --git a/app/lib/acme/ACMEv2.php b/app/lib/acme/ACMEv2.php
index 08ad00d..879b4db 100644
--- a/app/lib/acme/ACMEv2.php
+++ b/app/lib/acme/ACMEv2.php
@@ -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;
+ }
}
diff --git a/app/lib/cert/google.php b/app/lib/cert/google.php
index 8f10384..3045966 100644
--- a/app/lib/cert/google.php
+++ b/app/lib/cert/google.php
@@ -1,118 +1,142 @@
- '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);
- }
-}
+ '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']];
+ }
+}