你所热爱的,就是你的生活关于友链

给 localhost 颁发一份证书

swwind

注意:这篇文章已经发布超过 5 年,世界线的变动可能会导致故事走向不同的结局


我们可以直接使用 openssl

什么 openssl,不存在的,我就是要用 nodejs 写。

npm 上有一个 node-forge 库,相当于纯 js 写的 openssl

我们来讲讲怎么用它。

生成并安装自签名的 CA 根证书

// 创建一份随机钥匙对
const keys = forge.pki.rsa.generateKeyPair(2048);
// 创建证书
const cert = forge.pki.createCertificate();
// 使用公钥
cert.publicKey = keys.publicKey;
// 序列号(?
cert.serialNumber = String(Date.now());
// 设置有效日期
cert.validity.notBefore = new Date();
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 5);
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 20);
// 属性设置
const attrs = [
  { name: "commonName", value: "My Certificate Authority" },
  { name: "countryName", value: "CN" },
  { shortName: "ST", value: "Zhejiang" },
  { name: "localityName", value: "Shaoxing" },
  { name: "organizationName", value: "Myself" },
  { shortName: "OU", value: "https://blog.swwind.me" },
];
// 设置 subject
cert.setSubject(attrs);
// 设置颁发者
cert.setIssuer(attrs);
// 设置 Extensions
// 这里不用改
cert.setExtensions([
  {
    name: "basicConstraints",
    critical: true,
    cA: true,
  },
  {
    name: "keyUsage",
    critical: true,
    keyCertSign: true,
  },
  {
    name: "subjectKeyIdentifier",
  },
]);

// 用自己的私钥给 CA 根证书签名
cert.sign(keys.privateKey, forge.md.sha256.create());

// 公钥 ca.crt
console.log(forge.pki.certificateToPem(cert));
// 私钥 ca.key.pem
console.log(forge.pki.privateKeyToPem(keys.privateKey));

接下来我们需要的是安装我们的 CA 根证书(添加到信任列表)。

Windows

certutil -addstore -f "ROOT" ca.crt

MacOS

sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.crt

Linux

直接通过浏览器 import,简单粗暴。

签发 localhost 的证书

// 读取根证书 caCert 和 caKey
const caCertPem = fs.readFileSync("ca.crt", "utf8");
const caKeyPem = fs.readFileSync("ca.key.pem", "utf8");
const caCert = forge.pki.certificateFromPem(caCertPem);
const caKey = forge.pki.privateKeyFromPem(caKeyPem);

// 生成随机秘钥对
const keys = forge.pki.rsa.generateKeyPair(2048);
// 生成证书
const cert = forge.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = String(Date.now());
cert.validity.notBefore = new Date();
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1);
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 10);

const attrs = [
  { name: "commonName", value: "localhost" },
  { name: "countryName", value: "CN" },
  { shortName: "ST", value: "Zhejiang" },
  { name: "localityName", value: "Shaoxing" },
  { name: "organizationName", value: "Myself" },
  { shortName: "OU", value: "https://blog.swwind.me" },
];

// 颁发者设置为根证书
cert.setSubject(attrs);
cert.setIssuer(caCert.subject.attributes);

// 证书设置
cert.setExtensions([
  {
    name: "basicConstraints",
    critical: true,
    cA: false,
  },
  {
    name: "keyUsage",
    keyCertSign: true,
    digitalSignature: true,
    nonRepudiation: true,
    keyEncipherment: true,
    dataEncipherment: true,
  },
  {
    name: "extKeyUsage",
    serverAuth: true,
    clientAuth: true,
    codeSigning: true,
    emailProtection: true,
    timeStamping: true,
  },
  {
    name: "nsCertType",
    client: true,
    server: true,
    email: true,
    objsign: true,
    sslCA: true,
    emailCA: true,
    objCA: true,
  },
  {
    name: "subjectAltName",
    // 这里填多个域名或者 ip
    altNames: [
      {
        type: 2, // DNS
        value: "localhost",
      },
      {
        type: 7, // ipv4
        ip: "127.0.0.1",
      },
      {
        type: 7, // ipv6
        ip: "[::1]",
      },
    ],
  },
  {
    name: "subjectKeyIdentifier",
  },
]);
// 使用根证书签名
cert.sign(caKey, forge.md.sha256.create());

// 公钥 localhost.crt
console.log(forge.pki.certificateToPem(cert));
// 私钥 localhost.key.pem
console.log(forge.pki.privateKeyToPem(keys.privateKey));

然后就可以用 localhost.crtlocalhost.key.pem 为所欲为啦。

可以用 http-server 测试一下

yarn global add http-server
echo hello world > index.html
http-server -S -C localhost.crt -K localhost.key.pem

效果:

chrome-https

firefox 上截不了图

其他偷懒方法

mkcert 只需要两句话

mkcert -install
mkcert localhost 127.0.0.1 ::1

我就是要把最方便的方法放在最后面