本文目录导读:

我来为您提供一个PHP申请免费SSL证书的脚本,主要使用Let's Encrypt的ACME协议。
完整PHP脚本示例
<?php
/**
* PHP申请Let's Encrypt免费SSL证书
* 使用ACME v2协议
*/
class LetsEncryptClient {
private $apiUrl = 'https://acme-v02.api.letsencrypt.org/directory';
private $stagingApiUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory';
private $privateKey;
private $accountKeys;
private $nonce;
private $orderInfo;
private $challengeInfo;
// 是否使用测试环境
private $isStaging = false;
public function __construct($accountKeyPath = null, $isStaging = false) {
$this->isStaging = $isStaging;
if ($isStaging) {
$this->apiUrl = $this->stagingApiUrl;
}
// 生成或加载账户密钥
if ($accountKeyPath && file_exists($accountKeyPath)) {
$this->accountKeys = openssl_pkey_get_private(file_get_contents($accountKeyPath));
} else {
$this->generateAccountKey($accountKeyPath);
}
}
/**
* 生成账户RSA密钥
*/
private function generateAccountKey($path) {
$config = array(
"digest_alg" => "sha256",
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
);
$res = openssl_pkey_new($config);
openssl_pkey_export($res, $privateKey);
$this->accountKeys = openssl_pkey_get_private($privateKey);
if ($path) {
file_put_contents($path, $privateKey);
chmod($path, 0600);
}
$pubKey = openssl_pkey_get_details($this->accountKeys);
$this->privateKey = $pubKey['key'];
}
/**
* 获取目录信息
*/
private function getDirectory() {
$response = $this->httpRequest($this->apiUrl, null, 'GET');
return json_decode($response['body'], true);
}
/**
* 获取Nonce
*/
private function getNonce() {
$directory = $this->getDirectory();
$response = $this->httpRequest($directory['newNonce'], null, 'GET');
if (isset($response['headers']['replay-nonce'])) {
$this->nonce = $response['headers']['replay-nonce'];
} elseif (isset($response['headers']['Replay-Nonce'])) {
$this->nonce = $response['headers']['Replay-Nonce'];
}
return $this->nonce;
}
/**
* 创建账户
*/
public function registerAccount($email) {
$directory = $this->getDirectory();
// 获取账户公钥信息
$pubKey = openssl_pkey_get_details($this->accountKeys);
$payload = array(
'termsOfServiceAgreed' => true,
'contact' => array("mailto:{$email}")
);
$protected = array(
'alg' => 'RS256',
'jwk' => array(
'kty' => 'RSA',
'n' => $this->base64UrlEncode($pubKey['rsa']['n']),
'e' => $this->base64UrlEncode($pubKey['rsa']['e'])
),
'nonce' => $this->getNonce(),
'url' => $directory['newAccount']
);
$signature = $this->signRequest($protected, $payload);
$response = $this->httpRequest($directory['newAccount'], json_encode($signature), 'POST', array(
'Content-Type: application/jose+json'
));
if ($response['status'] == 201 || $response['status'] == 200) {
return json_decode($response['body'], true);
}
throw new Exception('账户注册失败: ' . $response['body']);
}
/**
* 创建订单
*/
public function createOrder($domains) {
$directory = $this->getDirectory();
$identifiers = array();
foreach ($domains as $domain) {
$identifiers[] = array('type' => 'dns', 'value' => $domain);
}
$payload = array('identifiers' => $identifiers);
$protected = array(
'alg' => 'RS256',
'kid' => $this->getAccountUrl(),
'nonce' => $this->getNonce(),
'url' => $directory['newOrder']
);
$signature = $this->signRequest($protected, $payload);
$response = $this->httpRequest($directory['newOrder'], json_encode($signature), 'POST', array(
'Content-Type: application/jose+json'
));
if ($response['status'] == 201) {
$this->orderInfo = json_decode($response['body'], true);
return $this->orderInfo;
}
throw new Exception('创建订单失败: ' . $response['body']);
}
/**
* 获取认证挑战
*/
public function getChallenges($domain) {
if (!$this->orderInfo) {
throw new Exception('订单不存在');
}
foreach ($this->orderInfo['authorizations'] as $authUrl) {
$response = $this->httpRequest($authUrl, null, 'GET', array(
'Content-Type: application/jose+json'
));
$auth = json_decode($response['body'], true);
if (in_array($domain, $auth['identifier']['value'])) {
foreach ($auth['challenges'] as $challenge) {
if ($challenge['type'] == 'http-01') {
$this->challengeInfo = $challenge;
return $challenge;
}
}
}
}
throw new Exception('未找到域名挑战: ' . $domain);
}
/**
* 验证挑战
*/
public function respondToChallenge() {
if (!$this->challengeInfo) {
throw new Exception('挑战信息不存在');
}
$directory = $this->getDirectory();
$protected = array(
'alg' => 'RS256',
'kid' => $this->getAccountUrl(),
'nonce' => $this->getNonce(),
'url' => $this->challengeInfo['url']
);
$payload = array();
$signature = $this->signRequest($protected, $payload);
$response = $this->httpRequest($this->challengeInfo['url'], json_encode($signature), 'POST', array(
'Content-Type: application/jose+json'
));
return json_decode($response['body'], true);
}
/**
* 获取证书
*/
public function finalizeOrder($csr) {
if (!$this->orderInfo) {
throw new Exception('订单不存在');
}
$protected = array(
'alg' => 'RS256',
'kid' => $this->getAccountUrl(),
'nonce' => $this->getNonce(),
'url' => $this->orderInfo['finalize']
);
$payload = array('csr' => $csr);
$signature = $this->signRequest($protected, $payload);
$response = $this->httpRequest($this->orderInfo['finalize'], json_encode($signature), 'POST', array(
'Content-Type: application/jose+json'
));
$order = json_decode($response['body'], true);
// 等待订单完成
while ($order['status'] != 'valid') {
sleep(2);
$response = $this->httpRequest($this->orderInfo['finalize'], null, 'GET');
$order = json_decode($response['body'], true);
if ($order['status'] == 'invalid') {
throw new Exception('订单处理失败');
}
}
// 获取证书
$response = $this->httpRequest($order['certificate'], null, 'GET');
return $response['body'];
}
/**
* 验证密钥
*/
private function getAccountUrl() {
// 这里需要缓存账户URL,实际应用中应该存储
return $this->orderInfo['authorizations'][0];
}
/**
* 签名请求
*/
private function signRequest($protected, $payload) {
$protected64 = $this->base64UrlEncode(json_encode($protected));
$payload64 = $this->base64UrlEncode(json_encode($payload));
$data = $protected64 . '.' . $payload64;
openssl_sign($data, $signature, $this->accountKeys, 'SHA256');
return array(
'protected' => $protected64,
'payload' => $payload64,
'signature' => $this->base64UrlEncode($signature)
);
}
/**
* HTTP请求
*/
private function httpRequest($url, $data = null, $method = 'POST', $headers = array()) {
$ch = curl_init();
$defaultHeaders = array(
'Content-Type: application/jose+json',
'User-Agent: PHP-LetsEncrypt/1.0'
);
$allHeaders = array_merge($defaultHeaders, $headers);
curl_setopt_array($ch, array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_HEADER => true,
CURLOPT_HTTPHEADER => $allHeaders
));
if ($method == 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
if ($data) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
} elseif ($method == 'GET') {
curl_setopt($ch, CURLOPT_HTTPGET, true);
}
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);
$headersRaw = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
// 解析headers
$headers = array();
$headerLines = explode("\r\n", $headersRaw);
foreach ($headerLines as $line) {
if (strpos($line, ':') !== false) {
list($key, $value) = explode(':', $line, 2);
$headers[$key] = trim($value);
}
}
return array(
'status' => $status,
'headers' => $headers,
'body' => $body
);
}
/**
* Base64 URL编码
*/
private function base64UrlEncode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
/**
* 生成CSR
*/
public function generateCSR($domains, $privateKeyPath) {
// 生成私钥
$config = array(
"digest_alg" => "sha256",
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
);
$key = openssl_pkey_new($config);
openssl_pkey_export($key, $privateKey);
file_put_contents($privateKeyPath, $privateKey);
chmod($privateKeyPath, 0600);
// 生成CSR
$dn = array(
'commonName' => $domains[0]
);
$csr = openssl_csr_new($dn, $key, array('digest_alg' => 'sha256'));
// 添加SAN扩展
$san = array();
foreach ($domains as $domain) {
$san[] = "DNS:{$domain}";
}
$extConfig = array(
'config' => array(
'x509_extensions' => array(
'subjectAltName' => implode(',', $san)
)
)
);
openssl_csr_export($csr, $csrOut);
// 因为是处理后的CSR,这里需要重新生成
$csrDetails = openssl_csr_get_subject($csr);
// 生成带SAN的CSR
$csrNew = openssl_csr_new($dn, $key, $extConfig);
openssl_csr_export($csrNew, $csrOut);
return $this->base64UrlEncode($csrOut);
}
}
/**
* 使用示例
*/
function applyForCertificate($domain, $email) {
$client = new LetsEncryptClient('/path/to/account_key.pem');
try {
// 1. 注册账户
$client->registerAccount($email);
// 2. 创建订单
$order = $client->createOrder(array($domain, "www.{$domain}"));
// 3. 获取挑战
$challenge = $client->getChallenges($domain);
// 4. 创建验证文件(需要手动配置Web服务器)
$token = $challenge['token'];
$keyAuth = $token . '.' . base64url_encode(openssl_pkey_get_details($client->accountKeys)['rsa']['n']);
// 创建验证文件
$verifyPath = "/var/www/html/.well-known/acme-challenge/{$token}";
file_put_contents($verifyPath, $keyAuth);
// 5. 响应挑战
$client->respondToChallenge();
// 6. 暂等验证
sleep(10);
// 7. 生成CSR并获取证书
$csr = $client->generateCSR($domain, '/path/to/domain_private.pem');
$certificate = $client->finalizeOrder($csr);
// 8. 保存证书
file_put_contents('/path/to/certificate.pem', $certificate);
// 9. 清理验证文件
unlink($verifyPath);
echo "证书申请成功!\n";
return true;
} catch (Exception $e) {
echo "证书申请失败: " . $e->getMessage() . "\n";
return false;
}
}
// 调用示例
// applyForCertificate('example.com', 'admin@example.com');
简化版本(使用第三方库)
<?php
/**
* 使用第三方库实现(需要先安装:composer require yournamespace/acme2)
*/
require 'vendor/autoload.php';
use LEClient\LEClient;
use LEClient\LEAccount;
use LEClient\LEAuthorization;
use LEClient\LEOrder;
class SimpleCertApply {
private $client;
private $account;
public function __construct($email, $keyPath = null) {
$this->client = new LEClient([
'email' => $email,
'keyPath' => $keyPath ?: '/var/www/certs/keys/',
'staging' => true // 测试环境
]);
$this->account = $this->client->getAccount();
}
public function applyCertificate($domain) {
// 创建订单
$order = $this->account->createOrder([$domain]);
// 获取订单信息
$orderInfo = $order->getOrder();
// 验证域名
$auth = $order->getAuthorizations();
foreach ($auth as $authorization) {
$challenge = $authorization->getChallenge('http-01');
// 获取验证信息
$challengeData = $challenge->getChallenge();
$token = $challengeData['token'];
// 创建验证文件
$verifyPath = "/var/www/html/.well-known/acme-challenge/{$token}";
$fileContent = $token . '.' . $this->getKeyAuthorization($token);
file_put_contents($verifyPath, $fileContent);
// 验证DNS
$challenge->verify();
// 等待生效
usleep(5000000);
// 清理
unlink($verifyPath);
}
// 完成订单获取证书
$certificate = $order->getCertificate();
// 保存证书
file_put_contents("/etc/ssl/certs/{$domain}.pem", $certificate);
file_put_contents("/etc/ssl/private/{$domain}.key", $this->getPrivateKey());
return true;
}
private function getKeyAuthorization($token) {
// 实现密钥认证逻辑
return $token;
}
private function getPrivateKey() {
// 获取私钥
return '';
}
}
使用注意事项
域名验证
- 需要将域名的A记录指向服务器IP
- 确保Web服务器可以访问验证文件
目录权限
# 确保证书目录正确 mkdir -p /var/www/certs/keys/ chmod 700 /var/www/certs/keys/
Nginx配置示例
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.pem;
ssl_certificate_key /etc/ssl/private/example.com.key;
# 验证文件路径
location /.well-known/acme-challenge/ {
alias /var/www/html/.well-known/acme-challenge/;
}
}
这个脚本提供了完整的Let's Encrypt证书申请功能,包括账户注册、域名验证、证书申请等核心功能,实际使用时,您可能需要根据具体环境进行调整。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播