You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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'
}

核心疑问

  1. 获取的客户端证书是否足够用于XML签名?
  2. 若足够,为何出现该错误?
  3. 请求可行的解决思路

问题分析与解决思路

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 21:38:10