HTTPS 证书管理

 发布 : 2020-08-23  字数统计 : 1.2k 字  阅读时长 : 6 分  分类 : 工程部署  浏览 :

自签名证书生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout self.key -out self.crt -subj /CN=*.abc.com

# 使用sha256算法
-sha256
# 默认情况下,openssl req自动创建私钥时都要求加密并提示输入加密密码,指定该选项后则禁止对私钥文件加密
-nodes
# 证书有效期365天
-days 365
# 2048位密钥
rsa:2048
# :密钥文件,自己命名
self.key
# iphone、mac等使用的证书文件
self.crt
# 使用abc.com域名的通配方式作为使用者
-subj /CN=*.abc.com

# 生成windows使用的证书文件pfx, 执行后,会提示输入证书密码
openssl pkcs12 -export -out self.pfx -inkey self.key -in self.crt

证书信息的获取

注意:本例初始化的证书存储位置与上传证书位置不一致,上传的证书只保留最新的一份。
每次服务启动时读取上传路径的,没有,则读取使用初始化的。
获取证书信息同理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const fs = require('fs');
const childProcess = require('child_process');
const path = require('path');
const uploadDir = "上传路径";

let pem;
if (fs.existsSync(path.join(uploadDir, 'cert.pem'))) {
pem = path.join(uploadDir, 'cert.pem');
} else {
pem = 'cert.pem'; //初始
}

let cmdStr = `openssl x509 -in ${pem} -noout -serial &&
opensll x509 -in ${pem} -noout -subject &&
opensll x509 -in ${pem} -noout -issuer &&
opensll x509 -in ${pem} -noout -startdate &&
opensll x509 -in ${pem} -noout -enddate`;

try {
let stdoutArray = childProcess.execSync(cmdStr).toString().split('\n');
let data = {
serial: stdoutArray[0].replace('serial=', ""), //序列号
subject: stdoutArray[1].replace('subject=', ""), //颁发给
issuer: stdoutArray[2].replace('issuer=', ""), //颁发者
notBefore: stdoutArray[3].replace('notBefore=', ""), //开始时间
notAfter: stdoutArray[4].replace('notAfter=', ""), //失效时间
}
} catch(err) {
console.log(err);
}

证书的验证及上传存储

本例前端使用表单提交,后端 Nodejs 接收。

前端使用 Element UI 的文件上传组件, 后端利用 eggjs 的 egg-multipart 插件,需要先配置扩展,支持对应格式 pfx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
async upload() {
const uploadDir = "上传路径"
const stream = await ctx.getFileStream();
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir)
}
const target = path.join(uploadDir, "temp.pfx");
const writeStream = fs.createWriteStream(target);
stream.pipe(writeStream);

let password = stream.fields.password; //form 参数 by `stream.fields`

let pfx = path.join(uploadDir, 'temp.pfx');
let pem = path.join(uploadDir, 'temp.pem');
let key = path.join(uploadDir, 'temp-key.pem');
let cert = path.join(uploadDir, 'temp-cert.pem');

// pfx && password 解析出 pem
// 验证-可能存在 verify 验证证书报错
// 转为 key && cert
let cmdStr = `openssl pkcs12 -in ${pfx} -nodes -out ${pem} -password pass:${password} &&
openssl verify ${pem} &&
openssl rsa -in ${pem} -out ${key} &&
openssl x509 -in ${pem} -out ${cert}`;

let execPromise = new Promise((resolve, reject) => {
childProcess.exec(cmdStr, function(error, stdout, stderr) {
if (error) {
return resolve({
code: 1,
msg: "Wrong certificate or password"
})
} else {
if (stdout.indexof('certificate has expired') > -1) {
return resolve({
code: 2,
data: "expired",
msg: stdout
})
} else {
return resolve({
code: 0,
msg: stdout
})
}
}
})
})
let data = await execPromise;
ctx.body = data;
}

证书上传后重启服务

证书上传后,将原来上传证书删除,然后新上传的重命名,保证服务重启后读取的证书为刚刚上传的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
async restart() {
// 之前上传的先删除,然后把刚上传的重命名
let key = path.join(uploadDir, 'key.pem');
let cert = path.join(uploadDir, 'cert.pem');
if (fs.existsSync(key)) {
fs.unlinkSync(key);
}
if (fs.existsSync(cert)) {
fs.unlinkSync(cert);
}
// 上步生成的
let tempKey = path.join(uploadDir, 'temp-key.pem');
let tempCert = path.join(uploadDir, 'temp-cert.pem');
try {
fs.renameSync(tempKey, key),
fs.renameSync(tempCert, cert);
} catch (e) {
}

let cmdStr = `restart node` //重启服务命令
childProcess.exec(cmdStr, function(error, stdout, stderr)) {
if(error) {
ctx.logger.error(error)
} else {
}
}

// 服务重启,无法回调,直接返回
ctx.body = {
code: 0,
msg: ""
}
}

证书文件的删除

用于上传后提示过期,选择不应用。或者上传后直接取消应用的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async delete() {
let pfx = path.join(uploadDir, 'temp.pfx');
let pem = path.join(uploadDir, 'temp.pem');
let key = path.join(uploadDir, 'temp-key.pem');
let cert = path.join(uploadDir, 'temp-cert.pem');
if (fs.existsSync(pfx)) {
fs.unlinkSync(pfx);
}
if (fs.existsSync(pem)) {
fs.unlinkSync(pem);
}
if (fs.existsSync(key)) {
fs.unlinkSync(key);
}
if (fs.existsSync(cert)) {
fs.unlinkSync(cert);
}

ctx.body = {
code: 0,
msg: ""
}
}

Egg https 配置

1
2
3
4
5
6
7
8
9
10
11
config.cluster = {
listen: {
path: "",
port: 443
},
https: {
key: key,
cert: cert,
ciphers: 'ECDHE-RSA-AES128-GCM-SHA256' //此参数并不支持,需要修改源码lib包
}
}

node-modules/egg-cluster/lib/app_worker.js

1
2
3
4
if (httpsOptions.ciphers) {
httpsOptions.ciphers = httpsOptions.ciphers;
}

参考

留下足迹