自签名证书生成
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
-nodes
-days 365
rsa:2048
self.key
self.crt
-subj /CN=*.abc.com
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;
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'); 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' } }
|
node-modules/egg-cluster/lib/app_worker.js
1 2 3 4
| if (httpsOptions.ciphers) { httpsOptions.ciphers = httpsOptions.ciphers; }
|
参考