Node.js Express启用SNICallback后证书验证失败问题求助
问题描述
我用Let's Encrypt证书搭建了Node.js Express HTTPS服务,原配置可正常被浏览器及其他应用访问。为实现证书自动续期无需重启服务,我通过SNICallback结合tls安全上下文实时读取证书链。修改后浏览器可正常获取新证书,但其他应用连接时出现错误:Error: unable to verify the first certificate,疑似服务器未提供中间证书。
原配置代码
var express = require('express'); var fs = require('fs'); var http = require('http'); let https = require('https'); const app = express(); let privateKey = fs.readFileSync(SERVER_KEY, 'utf8'); let certificate = fs.readFileSync(SERVER_CRT, 'utf8'); let certauth = fs.readFileSync(SERVER_CA, 'utf8'); let credentials = {key: privateKey, cert: certificate, ca: certauth}; //HTTPS server const httpsServer = https.createServer(credentials, app); httpsServer.listen(443); //HTTP redirect server const httpServer = http.createServer(function (req, res) { res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url }); res.end(); }); httpServer.listen(80);
证书链参数
SERVER_KEY=/etc/letsencrypt/live/dummy.example.com/privkey.pem SERVER_CRT=/etc/letsencrypt/live/dummy.example.com/cert.pem SERVER_CA=/etc/letsencrypt/live/dummy.example.com/chain.pem
修改后的配置代码
var express = require('express'); var fs = require('fs'); var http = require('http'); let https = require('https'); var tls = require('tls'); const app = express(); function getCredentials(){ let privateKey = fs.readFileSync(SERVER_KEY, 'utf8'); let certificate = fs.readFileSync(SERVER_CRT, 'utf8'); let certauth = fs.readFileSync(SERVER_CA, 'utf8'); let credentials = {key: privateKey, cert: certificate, ca: certauth}; return credentials; } var ctx = function() { return tls.createSecureContext(getCredentials()) }; //HTTPS server const httpsServer = https.createServer({ SNICallback: (servername, cb) => cb(null, ctx()) }, app); httpsServer.listen(443); //HTTP redirect server const httpServer = http.createServer(function (req, res) { res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url }); res.end(); }); httpServer.listen(80);
解决方案
问题根源在于SNICallback返回的安全上下文里,cert字段仅传入了服务器证书,未包含中间证书链。浏览器会自动尝试补充缺失的中间证书,但多数非浏览器应用不会做这件事,必须手动将服务器证书与中间证书拼接后作为cert的值。
修改getCredentials函数,合并服务器证书和中间证书链:
function getCredentials(){ let privateKey = fs.readFileSync(SERVER_KEY, 'utf8'); // 合并服务器证书与中间证书链 let certificate = fs.readFileSync(SERVER_CRT, 'utf8') + fs.readFileSync(SERVER_CA, 'utf8'); let credentials = {key: privateKey, cert: certificate}; return credentials; }
另外说明:tls.createSecureContext中的ca字段是用来验证客户端证书的,并非服务器返回给客户端的证书链。原配置里的ca参数属于冗余设置,因为Let's Encrypt的根证书已在多数系统的信任存储中。
修改完成后,SNICallback返回的安全上下文会同时发送服务器证书和中间证书,非浏览器应用即可正常验证证书链。
内容的提问来源于stack exchange,提问作者nick_j_white
相关产品推荐
相关产品推荐

