本文目录导读:

在PHP项目中实现用户自定义证书功能,通常涉及证书生成、管理、存储和验证等环节,以下是完整的实现方案:
基础架构设计
数据库表结构
-- 用户证书表
CREATE TABLE `user_certificates` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`user_id` INT NOT NULL,
`certificate_name` VARCHAR(255) NOT NULL,
`certificate_type` ENUM('ssl', 'smime', 'code_signing') DEFAULT 'ssl',
`certificate_data` LONGTEXT NOT NULL, -- 证书内容(PEM格式)
`private_key` TEXT NOT NULL, -- 加密后的私钥
`public_key` TEXT NOT NULL,
`serial_number` VARCHAR(255) UNIQUE,
`valid_from` DATETIME NOT NULL,
`valid_to` DATETIME NOT NULL,
`issuer` VARCHAR(255) DEFAULT '',
`subject` TEXT,
`fingerprint` VARCHAR(255),
`status` ENUM('active', 'expired', 'revoked') DEFAULT 'active',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_user_id` (`user_id`),
INDEX `idx_status` (`status`)
);
证书生成功能
使用OpenSSL生成自签名证书
<?php
class CertificateManager {
private $config = [
'digest_alg' => 'sha256',
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
];
/**
* 生成用户证书
*/
public function generateCertificate(array $userData, array $options = []): array {
// 1. 生成密钥对
$keyResource = openssl_pkey_new($this->config);
if (!$keyResource) {
throw new RuntimeException('密钥生成失败');
}
// 2. 获取私钥
openssl_pkey_export($keyResource, $privateKey);
// 3. 准备证书信息
$dn = [
'commonName' => $userData['domain'] ?? $userData['username'],
'organizationName' => $userData['organization'] ?? 'User Cert',
'organizationalUnitName' => $userData['department'] ?? 'Development',
'emailAddress' => $userData['email'],
'countryName' => $userData['country'] ?? 'CN',
'stateOrProvinceName' => $userData['province'] ?? 'Guangdong',
'localityName' => $userData['city'] ?? 'Shenzhen'
];
// 4. 设置有效期
$validDays = $options['valid_days'] ?? 365;
$validFrom = time();
$validTo = $validFrom + ($validDays * 86400);
// 5. 创建CSR
$csr = openssl_csr_new($dn, $keyResource, $this->config);
if (!$csr) {
throw new RuntimeException('CSR生成失败');
}
// 6. 自签名证书
$cert = openssl_csr_sign($csr, null, $keyResource, $validDays,
$this->config, $options['serial'] ?? random_int(100000, 999999));
if (!$cert) {
throw new RuntimeException('证书签名失败');
}
// 7. 导出证书
openssl_x509_export($cert, $certContent);
// 8. 获取公钥
$publicKey = openssl_pkey_get_public($cert);
$publicKeyDetails = openssl_pkey_get_details($publicKey);
// 9. 计算指纹
$fingerprint = openssl_x509_fingerprint($cert, 'sha256');
// 10. 获取证书信息
$certInfo = openssl_x509_parse($cert);
return [
'certificate' => $certContent,
'private_key' => $privateKey,
'public_key' => base64_encode($publicKeyDetails['key']),
'serial_number' => $certInfo['serialNumber'],
'valid_from' => date('Y-m-d H:i:s', $validFrom),
'valid_to' => date('Y-m-d H:i:s', $validTo),
'fingerprint' => bin2hex($fingerprint),
'subject' => json_encode($dn),
'issuer' => json_encode($dn), // 自签名证书
];
}
}
证书存储与加密
安全的私钥存储
<?php
class SecureStorage {
private $encryptionKey;
public function __construct() {
// 从安全环境变量获取加密密钥
$this->encryptionKey = getenv('CERT_ENCRYPTION_KEY');
if (!$this->encryptionKey) {
throw new RuntimeException('加密密钥未配置');
}
}
/**
* 加密私钥
*/
public function encryptPrivateKey(string $privateKey): string {
$iv = openssl_random_pseudo_bytes(16);
$encrypted = openssl_encrypt(
$privateKey,
'aes-256-cbc',
hex2bin($this->encryptionKey),
0,
$iv
);
return base64_encode($iv . $encrypted);
}
/**
* 解密私钥
*/
public function decryptPrivateKey(string $encryptedData): string {
$data = base64_decode($encryptedData);
$iv = substr($data, 0, 16);
$encrypted = substr($data, 16);
return openssl_decrypt(
$encrypted,
'aes-256-cbc',
hex2bin($this->encryptionKey),
0,
$iv
);
}
}
证书管理API
RESTful API设计
<?php
// 证书API控制器
class CertificateController {
private $certManager;
private $storage;
private $db;
public function __construct() {
$this->certManager = new CertificateManager();
$this->storage = new SecureStorage();
$this->db = Database::getInstance();
}
/**
* 创建证书
*/
public function create(Request $request): Response {
$user = $request->getUser();
$data = $request->getBody();
try {
// 1. 生成证书
$certData = $this->certManager->generateCertificate([
'username' => $user->username,
'email' => $user->email,
'domain' => $data['domain'] ?? "$user->username.example.com"
], [
'valid_days' => $data['valid_days'] ?? 365,
'type' => $data['type'] ?? 'ssl'
]);
// 2. 加密私钥
$encryptedPrivateKey = $this->storage->encryptPrivateKey(
$certData['private_key']
);
// 3. 保存到数据库
$stmt = $this->db->prepare(
"INSERT INTO user_certificates
(user_id, certificate_name, certificate_type, certificate_data,
private_key, public_key, serial_number, valid_from, valid_to,
fingerprint, subject, issuer)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
$stmt->execute([
$user->id,
$data['name'] ?? "Certificate-{$certData['serial_number']}",
$data['type'] ?? 'ssl',
$certData['certificate'],
$encryptedPrivateKey,
$certData['public_key'],
$certData['serial_number'],
$certData['valid_from'],
$certData['valid_to'],
$certData['fingerprint'],
$certData['subject'],
$certData['issuer']
]);
$certId = $this->db->lastInsertId();
// 4. 返回证书信息(注意:不返回私钥)
return Response::success([
'id' => $certId,
'certificate' => $certData['certificate'],
'fingerprint' => $certData['fingerprint'],
'valid_from' => $certData['valid_from'],
'valid_to' => $certData['valid_to']
]);
} catch (Exception $e) {
return Response::error($e->getMessage(), 500);
}
}
/**
* 下载证书
*/
public function download(int $certId, Request $request): Response {
$user = $request->getUser();
// 获取证书信息
$cert = $this->getUserCertificate($certId, $user->id);
if (!$cert) {
return Response::error('证书不存在', 404);
}
// 创建下载文件
$filename = "certificate_{$cert['id']}.p12";
$password = $this->generatePassword();
// 导出为PKCS12格式
$pkcs12 = $this->exportToPKCS12(
$cert['certificate_data'],
$this->storage->decryptPrivateKey($cert['private_key']),
$password
);
// 设置响应头
header('Content-Type: application/x-pkcs12');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . strlen($pkcs12));
echo $pkcs12;
exit;
}
}
证书验证
证书验证逻辑
<?php
class CertificateValidator {
/**
* 验证证书有效性
*/
public function validateCertificate(string $certificateContent): array {
$result = [
'valid' => false,
'error' => null,
'info' => []
];
// 1. 解析证书
$cert = openssl_x509_read($certificateContent);
if (!$cert) {
$result['error'] = '无效的证书格式';
return $result;
}
try {
// 2. 获取证书信息
$certInfo = openssl_x509_parse($cert);
// 3. 检查有效期
$now = time();
$validFrom = $certInfo['validFrom_time_t'];
$validTo = $certInfo['validTo_time_t'];
if ($now < $validFrom) {
$result['error'] = '证书尚未生效';
return $result;
}
if ($now > $validTo) {
$result['error'] = '证书已过期';
return $result;
}
// 4. 验证签名
$pubKey = openssl_pkey_get_public($cert);
$signatureCheck = openssl_x509_checkpurpose(
$cert,
X509_PURPOSE_SSL_CLIENT,
[],
[]
);
// 5. 验证CA链(可选)
// $caFile = '/path/to/ca.crt';
// $caCert = file_get_contents($caFile);
// $chainValid = openssl_x509_check_private_key($cert, $caCert);
$result['valid'] = true;
$result['info'] = [
'subject' => $certInfo['subject'],
'issuer' => $certInfo['issuer'],
'valid_from' => date('Y-m-d H:i:s', $validFrom),
'valid_to' => date('Y-m-d H:i:s', $validTo),
'fingerprint' => openssl_x509_fingerprint($cert, 'sha256'),
'serial_number' => $certInfo['serialNumber']
];
} finally {
openssl_x509_free($cert);
}
return $result;
}
}
安全最佳实践
关键安全措施
<?php
class SecurityConfig {
// 1. 密钥管理
const ENCRYPTION_KEY_LENGTH = 32; // 256位
const PRIVATE_KEY_LENGTH = 2048; // 至少2048位
// 2. 证书限制
const MAX_CERTIFICATES_PER_USER = 10;
const MIN_VALID_DAYS = 1;
const MAX_VALID_DAYS = 3650; // 10年
// 3. 加密配置
const ENCRYPTION_ALGORITHM = 'aes-256-gcm'; // 使用认证加密
const HASH_ALGORITHM = 'sha256';
// 4. 生成安全的随机序列号
public static function generateSerialNumber(): string {
return bin2hex(random_bytes(16));
}
// 5. 证书内容验证
public static function sanitizeCertificateData(string $data): string {
// 只允许PEM格式
if (!preg_match('/^-----BEGIN CERTIFICATE-----[\s\S]*-----END CERTIFICATE-----$/', $data)) {
throw new InvalidArgumentException('无效的证书格式');
}
return $data;
}
}
前端实现示例
证书上传与管理UI
<!-- 证书上传组件 -->
<div class="certificate-upload">
<form id="certForm" enctype="multipart/form-data">
<div class="form-group">
<label>证书名称</label>
<input type="text" name="name" required>
</div>
<div class="form-group">
<label>证书类型</label>
<select name="type">
<option value="ssl">SSL证书</option>
<option value="smime">S/MIME邮件证书</option>
<option value="code_signing">代码签名证书</option>
</select>
</div>
<div class="form-group">
<label>上传证书文件(PEM格式)</label>
<input type="file" name="certificate" accept=".pem,.crt,.cert">
</div>
<div class="form-group">
<label>私钥文件(PEM格式)</label>
<input type="file" name="private_key" accept=".pem,.key">
</div>
<button type="submit">上传证书</button>
</form>
<!-- 证书列表 -->
<div class="certificate-list">
<table>
<thead>
<tr>
<th>名称</th>
<th>有效期</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="certList"></tbody>
</table>
</div>
</div>
<script>
// 证书管理API调用
class CertificateManager {
async createCertificate(data) {
const response = await fetch('/api/certificates', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error('创建证书失败');
}
return response.json();
}
async downloadCertificate(certId) {
const response = await fetch(`/api/certificates/${certId}/download`, {
headers: {
'Authorization': `Bearer ${getToken()}`
}
});
if (!response.ok) {
throw new Error('下载失败');
}
// 处理文件下载
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `certificate_${certId}.p12`;
a.click();
}
async revokeCertificate(certId) {
const response = await fetch(`/api/certificates/${certId}/revoke`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${getToken()}`
}
});
return response.ok;
}
}
</script>
测试用例
<?php
class CertificateTest extends PHPUnit\Framework\TestCase {
public function testCertificateGeneration() {
$manager = new CertificateManager();
$result = $manager->generateCertificate([
'username' => 'testuser',
'email' => 'test@example.com',
'domain' => 'test.example.com'
]);
$this->assertArrayHasKey('certificate', $result);
$this->assertArrayHasKey('private_key', $result);
$this->assertStringContainsString('BEGIN CERTIFICATE', $result['certificate']);
// 验证证书有效性
$validator = new CertificateValidator();
$validation = $validator->validateCertificate($result['certificate']);
$this->assertTrue($validation['valid']);
}
public function testEncryptedStorage() {
$storage = new SecureStorage();
$privateKey = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----";
$encrypted = $storage->encryptPrivateKey($privateKey);
$decrypted = $storage->decryptPrivateKey($encrypted);
$this->assertEquals($privateKey, $decrypted);
}
}
注意事项
-
安全性:
- 私钥必须加密存储
- 使用HTTPS传输所有证书数据
- 实施严格的访问控制
- 定期轮换加密密钥
-
性能:
- 缓存证书验证结果
- 使用队列处理大批量证书生成
- 限制用户可创建的证书数量
-
合规性:
- 遵循X.509标准
- 支持证书撤销列表(CRL)
- 记录所有证书操作日志
-
扩展性:
- 支持Let's Encrypt集成
- 实现证书自动续期
- 提供多种导出格式(PEM, DER, PKCS12)
这个方案提供了完整的用户自定义证书实现,你可以根据具体需求进行调整和扩展。