您现在的位置是:网站首页 > 证书管理文章详情

证书管理

证书管理的基本概念

证书管理在Node.js中主要涉及数字证书的生成、验证、存储和使用。数字证书通常用于HTTPS服务器、客户端认证、数据加密等场景。常见的证书格式包括PEM、DER、PFX等,其中PEM是最常用的文本格式,DER是二进制格式,PFX/PKCS#12则包含私钥和证书链。

Node.js内置的cryptotls模块提供了基础的证书处理能力。对于更高级的需求,可以使用第三方库如node-forgepem等。证书通常包含公钥、持有者信息、颁发机构信息和有效期等关键数据。

生成自签名证书

在开发环境中,经常需要快速生成自签名证书用于测试。使用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());
  }
});

证书存储与管理策略

生产环境中证书管理需要考虑:

  1. 存储安全:私钥必须加密存储,可以使用Node.js的crypto模块加密:
const cipher = crypto.createCipher('aes-256-cbc', '加密密码');
let encrypted = cipher.update(privateKey, 'utf8', 'hex');
encrypted += cipher.final('hex');
  1. 自动续期:使用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' }]
});
  1. 证书轮换:实现零停机时间的证书更新:
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')
      });
    }
  });
}

证书转换与格式处理

不同格式证书间的转换是常见需求:

  1. 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');
  1. 解析证书信息:
const x509 = new (require('x509.js'));
const certInfo = x509.parseCert(fs.readFileSync('cert.pem', 'utf8'));
console.log('颁发者:', certInfo.issuer.commonName);
console.log('有效期:', certInfo.notAfter);

高级证书操作

  1. **证书撤销列表(CRL)**处理:
const crl = new crypto.X509CRL(fs.readFileSync('revoked.crl'));
console.log('撤销的证书序列号:', crl.revokedCertificates);
  1. 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);
  1. **证书透明度(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);

性能优化与安全实践

  1. 会话恢复减少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);
});
  1. 证书钉扎增强安全性:
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('证书指纹不匹配');
    }
  }
});
  1. 证书过期监控
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);

证书管理工具集成

  1. 与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'
  });
}
  1. 与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();
}
  1. 与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
  };
}

证书管理的最佳实践

  1. 密钥分离:将证书和私钥存储在不同位置,降低泄露风险
  2. 最小权限原则:应用程序只需要读取权限,不需要写入权限
  3. 审计日志:记录所有证书操作
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');
  1. 自动化测试:定期验证证书链完整性
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;
  }
};

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步