const https = require('https') const fs = require('fs') const options = { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem'), } const server = https.createServer(options, (req, res) => { res.writeHead(200) res.end('hello world\n') }) server.listen(8080)
与创建 http 服务最大的区别在于:https 服务需要证书。因此需要在 options 选项中提供 key 和 cert 两个字段。大部分前端不知道如何创建 key 和 cert,虽然网上能查到一些 openssl 命令,但也不知道是什么含义。所谓授人以鱼不如授人以渔,这里先从一些基本概念讲起,然后一步步教大家如何创建一个可以被浏览器绝对信任的自签名证书。
由于只有私钥持有者才能“签署”消息,如果不考虑密钥泄露的问题,就不能抵赖说不是自己干的。
这些参与方、概念和流程的集合被称为公钥基础设施(Public Key Infrastructure)
$ openssl genrsa -out key.pem 2048会生成一个 key.pem 文件,内容如下:
-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCB.... -----END PRIVATE KEY-----然后,还需要一个与私钥相对应的公钥(public key),生成方式:
$ openssl req -new -sha256 -key key.pem -out csr.pem按照提示操作即可,下面是示例输入(中国-浙江省-杭州市-西湖-苏堤-keliq):
You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:Zhejiang Locality Name (eg, city) []:Hangzhou Organization Name (eg, company) [Internet Widgits Pty Ltd]:West Lake Organizational Unit Name (eg, section) []:Su Causeway Common Name (e.g. server FQDN or YOUR name) []:keliq Email Address []:email@example.com Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []:123456 An optional company name []:XiHu生成的文件内容如下:
-----BEGIN CERTIFICATE REQUEST----- MIIDATCCAekCAQAwgY8xCzAJBgNVBAYTAkNOMREw... -----END CERTIFICATE REQUEST-----公钥文件创建之后,接下来有两个选择:
$ openssl x509 -req -in csr.pem -signkey key.pem -out cert.pem Certificate request self-signature ok subject=C = CN, ST = Zhejiang, L = Hangzhou, O = West Lake, OU = Su Causeway, CN = keliq, emailAddress = email@example.com最终得到了 cert.pem,内容如下:
-----BEGIN CERTIFICATE----- MIIDpzCCAo8CFAf7LQmMUweTSW+ECkjc7g1uy3jCMA0... -----END CERTIFICATE-----到这里,所有环节都走完了,再来回顾一下,总共生成了三个文件,环环相扣:
const options = { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem'), }启动之后,如果你在浏览器中访问,会发现出错了:
$ sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.pem
$ openssl x509 -in cert.pem -text -noout输出结果如下:
Certificate: Data: Version: 1 (0x0) Serial Number: 07:fb:2d:09:8c:53:07:93:49:6f:84:0a:48:dc:ee:0d:6e:cb:78:c2 Signature Algorithm: sha256WithRSAEncryption Issuer: C = CN, ST = Zhejiang, L = Hangzhou, O = West Lake, OU = Su Causeway, CN = keliq, emailAddress = email@example.com Validity Not Before: Nov 15 06:29:36 2023 GMT Not After : Dec 15 06:29:36 2023 GMT Subject: C = CN, ST = Zhejiang, L = Hangzhou, O = West Lake, OU = Su Causeway, CN = keliq, emailAddress = email@example.com Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:ac:63:b1:f1:7a:69:aa:84:ef:9d:0e:be:c1:f7: 80:3f:6f:59:e1:7d:c5:c6:db:ff:2c:f3:99:12:7f: ... Exponent: 65537 (0x10001) Signature Algorithm: sha256WithRSAEncryption Signature Value: 70:d9:59:10:46:dc:7b:b3:19:c8:bd:4b:c5:70:4f:89:b6:6a: 53:1c:f2:35:27:c8:0a:ed:a8:0a:13:1f:46:3e:e7:a7:ff:1f: ...我们发现,这个证书并没有 Subject Alternative Name 这个字段,那如何增加这个字段呢?有两种方式:
$ openssl x509 -req \ -in csr.pem \ -signkey key.pem \ -extfile <(printf "subjectAltName=DNS:localhost") \ -out cert.pem再次用命令查看证书详情,可以发现 Subject Alternative Name 字段已经有了:
.. X509v3 extensions: X509v3 Subject Alternative Name: DNS:localhost X509v3 Subject Key Identifier: 21:65:8F:93:49:BC:DF:8C:17:1B:6C:43:AC:31:3C:A9:34:3C:CB:77 ...用新生成的 cert.pem 启动 https 服务,再次访问就正常了,可以点击小锁查看证书详细信息:
[req] prompt = no default_bits = 4096 default_md = sha512 distinguished_name = dn x509_extensions = v3_req [dn] C=CN ST=Zhejiang L=Hangzhou O=West Lake OU=Su Causeway CN=keliq emailAddress=keliq@example.com [v3_req] keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName=@alt_names [alt_names] DNS.1 = localhost IP.2 = 127.0.0.1 IP.3 = 0.0.0.0然后一条命令直接生成密钥和证书文件:
$ openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 \ -config openssl.cnf \ -keyout key.pem \ -out cert.pem再次查看证书详情,观察 Subject Alternative Name 字段:
... X509v3 extensions: X509v3 Key Usage: Digital Signature, Non Repudiation, Key Encipherment X509v3 Subject Alternative Name: DNS:localhost, IP Address:127.0.0.1, IP Address:0.0.0.0 X509v3 Subject Key Identifier: B6:FC:1E:68:CD:8B:97:D0:80:0E:F1:18:D3:39:86:29:90:0B:9D:1F ...这样无论是访问 localhost 还是 127.0.0.1 或者 0.0.0.0,浏览器都能够信任。