Node.js读取证书文件夹后,如何提取证书对应的域名?
从X509证书中提取域名的Node.js实现
核心结论
可以直接从X509证书中提取关联域名,Node.js的crypto.X509Certificate对象提供了相关属性来获取证书的域名信息,包括传统的通用名称(CN)和现代证书常用的主题备用名称(SAN)。
需要修正的代码问题
你的代码存在几个影响功能的问题:
- 未引入
path模块,代码中使用了path.extname等方法但未声明const path = require('path'); - 证书目录中的文件后缀是
.crt,但代码中过滤的是.cert,会导致无法匹配到证书文件 - 未引入
X509Certificate,需要从crypto模块导入:const { X509Certificate } = require('crypto'); - 回调嵌套过深,容易引发逻辑混乱,建议改用同步读取或Promise化的文件操作方法
提取域名的具体实现
证书的域名信息主要来自两个字段:
- Subject Alternative Names(SAN):现代SSL证书的标准字段,支持绑定多个域名
- Common Name(CN):旧版证书使用的单域名字段
以下是修改后的完整代码,实现从证书中提取域名并构建secureContext:
const fs = require('fs'); const path = require('path'); const { exit } = require('process'); const { X509Certificate } = require('crypto'); const tls = require('node:tls'); let https; try { https = require('node:https'); } catch (err) { console.log('https support is disabled!'); exit(-1); } const cert_dir = '/etc/myapp/certs'; const secureContext = {}; // 检查证书目录是否存在 try { fs.statSync(cert_dir); } catch (err) { console.log(err); exit(-1); } // 读取目录下的所有文件 const files = fs.readdirSync(cert_dir); // 过滤出.crt后缀的证书文件 const certFiles = files.filter(item => path.extname(item) === '.crt'); certFiles.forEach(crtFile => { const certPath = path.join(cert_dir, crtFile); // 对应密钥文件后缀为.pem,替换后缀获取路径 const keyPath = path.join(cert_dir, crtFile.replace('.crt', '.pem')); try { // 验证密钥文件存在 fs.statSync(keyPath); const certBuffer = fs.readFileSync(certPath); const x509 = new X509Certificate(certBuffer); let domains = []; // 优先提取SAN字段的域名 const san = x509.subjectAltName; if (san) { domains = san.split(', ') .filter(item => item.startsWith('DNS:')) .map(item => item.slice(4)); } // 无SAN时提取CN字段 if (domains.length === 0) { const cnMatch = x509.subject.match(/CN=([^,]+)/); if (cnMatch) { domains.push(cnMatch[1]); } } // 为每个域名创建安全上下文 if (domains.length > 0) { const context = tls.createSecureContext({ key: fs.readFileSync(keyPath), cert: certBuffer }); domains.forEach(domain => { secureContext[domain] = context; console.log(`绑定域名 ${domain} 到证书 ${crtFile}`); }); } else { console.log(`证书 ${crtFile} 中未找到有效域名`); } } catch (err) { console.log(`处理证书 ${crtFile} 失败: ${err.message}`); } }); // 服务器配置 const options = { SNICallback: function (domain, cb) { if (secureContext[domain]) { cb ? cb(null, secureContext[domain]) : secureContext[domain]; } else { cb ? cb(new Error('No keys/certificates for domain requested')) : throw new Error('No keys/certificates for domain requested'); } }, // 默认证书(确保路径有效) key: fs.readFileSync('../path_to_key.pem'), cert: fs.readFileSync('../path_to_cert.crt'), }; // 请求处理函数 function handle(req, res) { res.writeHead(200); res.end('Hello Secure World!\n'); } const server = https.createServer(options, handle); server.listen(449, () => { console.log('HTTPS server listening on port 449'); });
关键说明
- SAN字段解析:
subjectAltName返回的字符串格式为"DNS:example.com, DNS:www.example.com",需拆分后过滤出DNS类型的域名 - CN字段降级:通过正则匹配
subject字段中的CN=xxx部分,作为SAN字段的备选方案 - 安全上下文:使用
tls.createSecureContext生成对应域名的安全上下文,存入secureContext供SNI回调使用 - 错误处理:单个证书处理失败不会中断整个流程,避免服务器启动异常
内容的提问来源于stack exchange,提问作者Dimitrios Desyllas
相关产品推荐
相关产品推荐

