您现在的位置是:网站首页 > 证书管理文章详情
证书管理
陈川
【
Node.js
】
49561人已围观
8958字
证书管理的基本概念
证书管理在Node.js中主要涉及数字证书的生成、验证、存储和使用。数字证书通常用于HTTPS服务器、客户端认证、数据加密等场景。常见的证书格式包括PEM、DER、PFX等,其中PEM是最常用的文本格式,DER是二进制格式,PFX/PKCS#12则包含私钥和证书链。
Node.js内置的crypto
和tls
模块提供了基础的证书处理能力。对于更高级的需求,可以使用第三方库如node-forge
、pem
等。证书通常包含公钥、持有者信息、颁发机构信息和有效期等关键数据。
生成自签名证书
在开发环境中,经常需要快速生成自签名证书用于测试。使用OpenSSL命令行工具可以轻松实现:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
在Node.js中也可以通过代码生成:
const crypto = require('crypto');
const { promisify } = require('util');
const generateKeyPair = promisify(crypto.generateKeyPair);
async function createSelfSignedCert() {
const { publicKey, privateKey } = await generateKeyPair('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
const cert = crypto.createCertificate({
issuer: { CN: 'Test CA' },
subject: { CN: 'localhost' },
publicKey,
serialNumber: '123456',
notBefore: new Date().toISOString(),
notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString()
});
const certPem = cert.sign(privateKey, crypto.createHash('sha256'));
return { cert: certPem, key: privateKey };
}
证书验证与链式信任
验证证书的有效性需要考虑多个方面:
const tls = require('tls');
const fs = require('fs');
const options = {
cert: fs.readFileSync('server.crt'),
key: fs.readFileSync('server.key'),
ca: [fs.readFileSync('ca.crt')], // 信任的CA证书
requestCert: true, // 要求客户端提供证书
rejectUnauthorized: true // 拒绝未通过验证的连接
};
const server = tls.createServer(options, (socket) => {
console.log('客户端证书验证状态:', socket.authorized);
if (socket.authorized) {
console.log('客户端证书信息:', socket.getPeerCertificate());
}
});
证书存储与管理策略
生产环境中证书管理需要考虑:
- 存储安全:私钥必须加密存储,可以使用Node.js的
crypto
模块加密:
const cipher = crypto.createCipher('aes-256-cbc', '加密密码');
let encrypted = cipher.update(privateKey, 'utf8', 'hex');
encrypted += cipher.final('hex');
- 自动续期:使用ACME协议自动续期Let's Encrypt证书:
const { ACME } = require('acme-client');
const client = new ACME({
directoryUrl: ACME.directory.letsencrypt.production
});
// 获取证书挑战
const challenge = await client.createOrder({
identifiers: [{ type: 'dns', value: 'example.com' }]
});
- 证书轮换:实现零停机时间的证书更新:
const cluster = require('cluster');
if (cluster.isMaster) {
// 主进程监听证书文件变化
fs.watch('cert.pem', () => {
cluster.workers.forEach(worker => worker.send('reload-cert'));
});
} else {
process.on('message', (msg) => {
if (msg === 'reload-cert') {
server.setSecureContext({
cert: fs.readFileSync('new-cert.pem'),
key: fs.readFileSync('new-key.pem')
});
}
});
}
证书转换与格式处理
不同格式证书间的转换是常见需求:
- PEM转PFX:
const forge = require('node-forge');
const p12Asn1 = forge.pkcs12.toPkcs12Asn1(
forge.pki.privateKeyFromPem(privateKey),
[forge.pki.certificateFromPem(cert)],
'password'
);
const p12Der = forge.asn1.toDer(p12Asn1).getBytes();
fs.writeFileSync('cert.pfx', p12Der, 'binary');
- 解析证书信息:
const x509 = new (require('x509.js'));
const certInfo = x509.parseCert(fs.readFileSync('cert.pem', 'utf8'));
console.log('颁发者:', certInfo.issuer.commonName);
console.log('有效期:', certInfo.notAfter);
高级证书操作
- **证书撤销列表(CRL)**处理:
const crl = new crypto.X509CRL(fs.readFileSync('revoked.crl'));
console.log('撤销的证书序列号:', crl.revokedCertificates);
- OCSP装订实现:
const ocsp = require('ocsp');
const server = tls.createServer({
cert: fs.readFileSync('server.crt'),
key: fs.readFileSync('server.key')
}, (socket) => {
// 处理连接
});
ocsp.Server.create({
cert: fs.readFileSync('server.crt'),
key: fs.readFileSync('server.key')
}, (req, cb) => {
cb(null, {
cert: fs.readFileSync('server.crt'),
issuer: fs.readFileSync('ca.crt'),
thisUpdate: new Date(),
nextUpdate: new Date(Date.now() + 3600000)
});
}).listen(8080);
- **证书透明度(CT)**日志提交:
const { submit } = require('ct-submit');
const result = await submit({
chain: [fs.readFileSync('cert.pem'), fs.readFileSync('ca.pem')],
url: 'https://ct.googleapis.com/logs/argon2020'
});
console.log('SCT:', result.sct);
性能优化与安全实践
- 会话恢复减少TLS握手开销:
const tlsSessionStore = {};
server.on('newSession', (id, data, cb) => {
tlsSessionStore[id.toString('hex')] = data;
cb();
});
server.on('resumeSession', (id, cb) => {
cb(null, tlsSessionStore[id.toString('hex')] || null);
});
- 证书钉扎增强安全性:
const tls = require('tls');
const pins = new Set([
'sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
]);
const socket = tls.connect({
host: 'example.com',
port: 443,
checkServerIdentity: (host, cert) => {
const fingerprint = crypto.createHash('sha256')
.update(cert.raw)
.digest('base64');
if (!pins.has(`sha256/${fingerprint}`)) {
throw new Error('证书指纹不匹配');
}
}
});
- 证书过期监控:
const checkExpiry = (certPath) => {
const cert = fs.readFileSync(certPath, 'utf8');
const match = cert.match(/-----BEGIN CERTIFICATE-----([\s\S]+?)-----END CERTIFICATE-----/);
const der = Buffer.from(match[1], 'base64');
const asn1 = forge.asn1.fromDer(forge.util.createBuffer(der));
const certObj = forge.pki.certificateFromAsn1(asn1);
const expiryDate = new Date(certObj.validity.notAfter);
const daysLeft = Math.floor((expiryDate - new Date()) / (1000 * 60 * 60 * 24));
if (daysLeft < 30) {
console.warn(`警告: 证书将在${daysLeft}天后过期`);
}
};
多域名与通配符证书处理
SAN(Subject Alternative Name)证书支持多个域名:
const { Certificate } = require('@fidm/x509');
const cert = Certificate.fromPEM(fs.readFileSync('cert.pem'));
console.log('支持的域名:', cert.subjectaltnames);
生成包含SAN的自签名证书:
const forge = require('node-forge');
const keys = forge.pki.rsa.generateKeyPair(2048);
const cert = forge.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = '01';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
const attrs = [
{ name: 'commonName', value: 'example.com' },
{ name: 'countryName', value: 'US' }
];
cert.setSubject(attrs);
cert.setIssuer(attrs);
cert.setExtensions([
{
name: 'subjectAltName',
altNames: [
{ type: 2, value: 'example.com' }, // DNS
{ type: 2, value: '*.example.com' }, // 通配符
{ type: 7, ip: '127.0.0.1' } // IP地址
]
}
]);
cert.sign(keys.privateKey, forge.md.sha256.create());
const pem = forge.pki.certificateToPem(cert);
证书管理工具集成
- 与Kubernetes集成:
const k8s = require('@kubernetes/client-node');
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
async function updateTLSSecret(namespace, secretName, cert, key) {
await k8sApi.replaceNamespacedSecret(secretName, namespace, {
metadata: { name: secretName },
data: {
'tls.crt': Buffer.from(cert).toString('base64'),
'tls.key': Buffer.from(key).toString('base64')
},
type: 'kubernetes.io/tls'
});
}
- 与AWS ACM集成:
const AWS = require('aws-sdk');
const acm = new AWS.ACM();
async function importCertToACM(cert, key, chain) {
const params = {
Certificate: cert,
PrivateKey: key,
CertificateChain: chain
};
return acm.importCertificate(params).promise();
}
- 与Hashicorp Vault集成:
const vault = require("node-vault")({
apiVersion: "v1",
endpoint: "https://vault.example.com",
token: "s.xxxxxxxxxx"
});
async function getCertFromVault(path) {
const secret = await vault.read(`pki/issue/${path}`, {
common_name: "example.com",
ttl: "24h"
});
return {
cert: secret.data.certificate,
key: secret.data.private_key,
chain: secret.data.issuing_ca
};
}
证书管理的最佳实践
- 密钥分离:将证书和私钥存储在不同位置,降低泄露风险
- 最小权限原则:应用程序只需要读取权限,不需要写入权限
- 审计日志:记录所有证书操作
const auditLog = (action, certName) => {
const logEntry = {
timestamp: new Date().toISOString(),
action,
cert: certName,
user: process.env.USER,
ip: require('ip').address()
};
fs.appendFileSync('cert-audit.log', JSON.stringify(logEntry) + '\n');
};
// 使用示例
auditLog('renew', 'example.com');
- 自动化测试:定期验证证书链完整性
const testCertChain = async (certPath, caPath) => {
try {
const cert = fs.readFileSync(certPath);
const ca = fs.readFileSync(caPath);
const https = require('https');
const agent = new https.Agent({
ca,
rejectUnauthorized: true
});
await require('axios').get('https://example.com', { httpsAgent: agent });
return true;
} catch (err) {
console.error('证书链验证失败:', err);
return false;
}
};