NodeJS+Express中xml-crypto签名XML报错:ERR_OSSL_UNSUPPORTED
问题:客户端证书用于XML签名时出现OpenSSL不支持错误
背景信息
- 基于NodeJS 20.11.1 + Express搭建HTTPS服务器,通过
/cert路由获取保加利亚牙医的智能卡客户端证书,目标是用该证书签名XML文档 - 业务流程:请求
https://ptest-auth.his.bg/token获取XML,签名后提交换取accessToken
当前代码
import { SignedXml } from "xml-crypto"; const keyPath = path.join(__dirname, "..", "..", "selfsigned.key"); const certPath = path.join(__dirname, "..", "..", "selfsigned.crt"); const key = fs.readFileSync(keyPath); const cert = fs.readFileSync(certPath); const options = { key: key, cert: cert, requestCert: true, rejectUnauthorized: false, }; app.get("/cert", async (req: Request, res: Response) => { // 获取医生的数字证书 const clientCert = (req.socket as TLSSocket).getPeerCertificate(true).raw.toString("base64"); // 构建PEM格式证书 const match = clientCert.match(/.{0,64}/g); const prefix = "-----BEGIN CERTIFICATE-----\n"; const postfix = "-----END CERTIFICATE-----"; if (!match) { return res.status(400).json({ error: "No client certificate provided", }); } const pemText = prefix + match.join("\n") + postfix; // 获取XML文档 axios.get("https://ptest-auth.his.bg/token") .then((response) => { // 正常响应处理 }) .catch((error) => { // API返回401时处理(无证书的情况) const data = error.response.data; // 签名文档 const signature = new SignedXml({ privateKey: pemText }); signature.addReference({ xpath: "/*", digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha512", transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], }); signature.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; signature.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"; signature.computeSignature(data); const signedData = signature.getSignedXml(); console.log(signedData); }); res.status(200).json({ success: true, message: "Client certificate", clientCert, }); }); https.createServer(options, app).listen(8002, () => { console.log("listening on port 8002"); });
报错信息
Error: error:1E08010C:DECODER routines::unsupported at Sign.sign (node:internal/crypto/sig:128:29) at D:\GitHub\dentrino-gql\node_modules\xml-crypto\lib\signature-algorithms.js:51:32 at RsaSha512.getSignature (D:\GitHub\dentrino-gql\node_modules\xml-crypto\lib\types.js:52:20) at SignedXml.calculateSignatureValue (D:\GitHub\dentrino-gql\node_modules\xml-crypto\lib\signed-xml.js:244:42) at SignedXml.computeSignature (D:\GitHub\dentrino-gql\node_modules\xml-crypto\lib\signed-xml.js:634:18) at D:\GitHub\dentrino-gql\dist\microservices\app.js:118:19 at process.processTicksAndRejections (node:internal/process/task_queues:95:5) { library: 'DECODER routines', reason: 'unsupported', code: 'ERR_OSSL_UNSUPPORTED' }
核心疑问
- 获取的客户端证书是否足够用于XML签名?
- 若足够,为何出现该错误?
- 请求可行的解决思路
问题分析与解决思路
1. 错误核心原因:混淆了证书与私钥的作用
你代码里的关键错误是用客户端证书(公钥载体)代替私钥进行签名。XML数字签名必须使用私钥完成,而客户端证书仅包含公钥和身份认证信息,没有私钥,OpenSSL无法用公钥执行签名操作,这就是ERR_OSSL_UNSUPPORTED的直接原因。
2. 客户端证书本身不能直接用于签名
从TLS握手获取的getPeerCertificate结果只是公钥证书,对应的私钥存储在智能卡硬件内部,无法通过TLS协议导出。智能卡的安全设计就是私钥永不离开硬件,必须通过专用接口调用硬件内部的签名功能。
3. 具体解决步骤
- 放弃提取私钥的思路:不要尝试从证书或TLS会话中获取私钥,这不符合智能卡的安全机制
- 对接PKCS#11接口:使用
pkcs11js或node-pkcs11这类库,通过智能卡驱动提供的PKCS#11模块,直接调用硬件内部的签名功能 - 自定义XML签名逻辑:给
xml-crypto的SignedXml对象自定义签名函数,不直接传入私钥,而是通过PKCS#11调用智能卡完成签名 - 验证证书有效性:可以用NodeJS的
crypto模块验证客户端证书的可信链,确保证书是合法的保加利亚牙医资质证书
代码调整示例(伪代码)
import * as pkcs11 from 'pkcs11js'; import { SignedXml } from "xml-crypto"; // 加载智能卡PKCS#11驱动(路径根据实际驱动调整) const pkcs11Module = pkcs11.Module.load('C:/Windows/System32/smartcard-driver.dll'); pkcs11Module.initialize(); app.get("/cert", async (req: Request, res: Response) => { // 获取客户端证书(仅用于后续验证身份,不用于签名) const clientCert = (req.socket as TLSSocket).getPeerCertificate(true); // 打开智能卡会话 const slots = pkcs11Module.getSlots(true); // 获取已插入智能卡的读卡器 const session = slots[0].open(); session.login('用户PIN码'); // 需处理PIN输入逻辑,禁止硬编码 // 查找智能卡内的私钥 const privateKeys = session.find({ class: pkcs11.ObjectClass.PRIVATE_KEY, label: '对应证书的私钥标签' // 需根据智能卡配置调整 }); const privateKey = privateKeys.items[0]; // 获取待签名的XML axios.get("https://ptest-auth.his.bg/token") .catch((error) => { const xmlData = error.response.data; // 初始化XML签名对象 const signature = new SignedXml(); signature.addReference({ xpath: "/*", digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha512", transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], }); signature.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; signature.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"; // 自定义签名函数,调用智能卡完成签名 signature.getSignature = (digest: Buffer) => { return session.sign(privateKey, digest, { mechanism: pkcs11.Mechanism.RSA_PKCS }); }; // 计算签名并获取已签名XML signature.computeSignature(xmlData); const signedXml = signature.getSignedXml(); console.log(signedXml); // 后续提交signedXml换取accessToken }); // 关闭会话 session.logout(); session.close(); pkcs11Module.finalize(); res.status(200).json({ success: true, message: "Client certificate retrieved", certSubject: clientCert.subject }); });
额外注意事项
- 必须安装智能卡的官方驱动,获取正确的PKCS#11模块路径(Windows为
.dll,Linux为.so) - 实现安全的PIN输入流程,禁止硬编码或明文存储PIN
- 测试时确保智能卡已插入读卡器,且处于激活状态
内容的提问来源于stack exchange,提问作者vmvelev
相关产品推荐
相关产品推荐

