如题
问题描述
为指定的域名或者IP地址生成自签名证书, 要求使用HTTPS协议访问时, 主流浏览器Chrome, firefox等不弹出警告.
这里的自签名证书是指可根据IP或域名动态生成的二级证书.不借由第三方权威颁发机构生成.
举个简单的场景, A机在指定端口起了服务后, B机在浏览器中想要通过HTTPS协议访问A机服务器, 浏览器不警告.
通常的服务器证书, 我们是向let’s Encript或其他权威机构申请, 而这里, 要求A机服务器的证书是由自己生成的CA机构颁发.
解决方法
解决思路:
先生成一个根证书颁发机构 (Root certificate authority), 然后基于颁发机构生成二级证书, 在二级证书中绑定域名或者IP地址.
针对问题描述中的提到的例子, 相当于在A机生成根证书颁发机构RCA 和基于该颁发机构生成的绑定A机IP地址的二级证书,然后B机通过某种方式下载了该 RCA, 同时系统设置信任[相当危险], 这样便可走HTTPS协议访问A机的服务了.
下面分传统的openssl和nodejs两个方式来实现下.
Openssl方式
先生成根证书:
新增一个shell 脚本文件generate_root_ca.sh, 放入如下内容, 然后执行脚本即可.[具体各参数的含义, 可参考x509v3 config]
#!/bin/sh echo "[req] default_bits = 2048 distinguished_name = req_distinguished_name x509_extensions = v3_req prompt = no [req_distinguished_name] countryName = XX stateOrProvinceName = N/A localityName = N/A organizationName = Self-signed Cert commonName = Self-signed Cert [v3_req] basicConstraints = CA:TRUE " > root.cnf openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout rootCA.key -out rootCA.crt -config root.cnf rm root.cnf
此处的关键在于
basicConstraints
设置为CA:TRUE
也可以直接在终端生成, 执行:
# 生成private key, 可以添加选项 -des3 来给 private key 设置密码.这里略过 openssl genrsa -out rootCA.key 2048 # 生成cert, -days 用于设置过期时间, 指定加密算法为 sha256, 执行后会有提示, 设置即可 openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 730 -out rootCA.crt
生成后得让计算机信任这个机构. 以MacOS为例:
打开keychain Access, file > import items 选中生成的rootCA.crt文件, 双击该文件, 选择 Always Trust.
这样,基于该机构颁发的证书, 都是被本机信任的.
现在我们来生成基于该CA颁发的证书:
以IP地址为例, 新增shell脚本文件 generate_ip_cert.sh, 添加如下内容:
IP=$(echo $1)
echo "
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
IP.1 = $IP
" > cert.cnf
openssl genrsa -out key.pem 2048
openssl req -new -key key.pem -out csr.pem -subj "/C=XX/ST=MyST"
openssl x509 -req -in csr.pem -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out cert.pem -days 730 -sha256 -extfile cert.cnf
rm cert.cnf
执行./generate_ip_cert.sh IPAddress
[IPAddress为你需要为其生成证书的IP地址]
如果为指定的域名生成证书, 只需修改alt_names部分即可:
DNS=$(echo $1)
echo "
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = $DNS
" > cert.cnf
openssl genrsa -out key.pem 2048
openssl req -new -key key.pem -out csr.pem -subj "/C=XX/ST=MyST"
openssl x509 -req -in csr.pem -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out cert.pem -days 730 -sha256 -extfile cert.cnf
rm cert.cnf
同样也可以在终端一步步走openssl 生成, 这里略过.
随后将生成的cert.pem, key.pem放进服务器的配置中即可.
Nodejs 方式
原理同上面的一样, 这里用一个小的express实例来实验下.
为了生成证书, 首先我们需要引用了一个开源的package: selfsigned, 因为原来的库并不支持基于Root CA生成证书, 这里我们用Andrey Novikov fork后的改进版本.
直接起一个express, entry point为app.js. 在package.json中添加:
"selfsigned": "https://github.com/Envek/selfsigned.git#7477718"
随后npm install.
app.js中添加如下内容:
const express = require('express') const https = require('https') const fs = require('fs') const ip = require('ip') const selfsigned = require('selfsigned') const app = express() const address = ip.address() const port = 3008 fs.mkdirSync('./ca', { recursive: true }) const rootCA = selfsigned.generate( [ { name: 'commonName', value: 'Self-signedCert' }, { name: 'countryName', value: 'XX' }, { name: 'organizationName', value: 'Myorg' }, ], { keySize: 2048, algorithm: 'sha256', extensions: [ { name: 'basicConstraints', cA: true, }, ] } ) fs.writeFileSync('./ca/rootCA.crt', rootCA.cert) const pems = selfsigned.generate( [ { name: 'commonName', value: 'Self-signedCert' }, { name: 'countryName', value: 'XX' }, { name: 'organizationName', value: 'Myorg' }, ], { keySize: 2048, ca: rootCA, algorithm: 'sha256', extensions: [ { name: 'basicConstraints', cA: false, }, { name: "keyUsage", keyCertSign: false, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true, }, { name: "extKeyUsage", serverAuth: true, clientAuth: true, codeSigning: true, timeStamping: true, }, { name: "subjectAltName", altNames: [ { type: 7, ip: address, }, ], }, ], } ) fs.writeFileSync('./ca/cert.pem', pems.cert) fs.writeFileSync('./ca/key.pem', pems.private) app.get('/', (req, res) => { res.send('Hello World!') }) https.createServer({ key: fs.readFileSync('./ca/key.pem'), cert: fs.readFileSync('./ca/cert.pem') }, app).listen(port, () => { console.log(`listening on https://${address}:${port}`) })
将项目的ca目录下的rootCA.crt 拖到 keychain中, 并设置为信任的机构.
终端执行
node app.js
:浏览器访问正常.可以点开🔒️的图标, 查看我们生成的二级证书:
最后, 值得一提的是Firefox, 系统设置信任对Firefox并不起效, 需要在perferences 中将生成的rootCA.crt 导入进去才可以. [此为危险操作]