如何在TypeScript中用xml-crypto为XML签名添加KeyInfo引用?
解决xml-crypto中XML签名包含KeyInfo引用的问题
问题分析
你的核心矛盾在于:KeyInfo是签名过程中动态生成的元素,在第一次computeSignature之前它并不存在于原始XML中,所以直接用XPath查找会失败;而重复调用computeSignature会生成新的签名节点,导致重复签名。
解决方案
1. 正确添加KeyInfo引用的核心思路
要在同一个签名中包含KeyInfo的引用,不能在签名后补加引用,而是要预先构造KeyInfo并嵌入到待签名XML中,或者直接通过URI引用KeyInfo的ID(无需XPath),确保所有待签名元素在签名计算前都已存在。
2. 无重复签名的实现代码
private localElemName: string; private xmlBody: any; private xmlBuilder = new XMLBuilder({ attributeNamePrefix: '@_', ignoreAttributes: false, format: true, }); createDigitalSignature(xmlObj: any){ this.xmlBody = this.xmlBuilder.build(xmlObj); this.localElemName = this.getKey(xmlObj); const keyInfoId = '_8401036a-cd29-4f5b-a48a-9ecf4d515d98'; // 临时生成KeyInfo内容 const tempSig = new SignedXml({ publicCert: fs.readFileSync(path.join(__dirname, "../../certificates_keys", "BANK0201_TRANSPORT_TEST.pem")) }); const keyInfoContent = tempSig.getKeyInfoContent({ prefix: 'ds' }); // 将KeyInfo嵌入原始XML(需根据你的XML结构调整嵌入位置) const parser = new DOMParser(); const xmlDoc = parser.parseFromString(this.xmlBody, 'application/xml'); const targetParentNode = xmlDoc.querySelector(`//*[local-name(.)='Document']`); const keyInfoNode = parser.parseFromString(keyInfoContent, 'application/xml').firstChild; keyInfoNode.setAttribute('Id', keyInfoId); targetParentNode.appendChild(keyInfoNode); // 生成带KeyInfo的待签名XML const xmlWithKeyInfo = new XMLSerializer().serializeToString(xmlDoc); // 创建正式签名实例,添加所有引用 const sig = new SignedXml({ privateKey: fs.readFileSync(path.join(__dirname, "../../certificates_keys", "BANK0201.key")), publicCert: fs.readFileSync(path.join(__dirname, "../../certificates_keys", "BANK0201_TRANSPORT_TEST.pem")) }); // 引用业务目标元素 sig.addReference({ xpath: `//*[local-name(.)='${this.localElemName}']`, isEmptyUri: true, transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature","http://www.w3.org/TR/2001/REC-xml-c14n-20010315"], digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256", }); // 引用KeyInfo(通过URI直接关联ID,无需XPath) sig.addReference({ uri: `#${keyInfoId}`, transforms: ["http://www.w3.org/TR/2001/REC-xml-c14n-20010315"], digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256', }); sig.canonicalizationAlgorithm = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; // 一次性完成签名计算 sig.computeSignature(xmlWithKeyInfo, { prefix: "ds", location: { reference: `//*[local-name(.)='${this.localElemName}']`, action: "after" }, }); return sig.getSignedXml(); } getKey(xmlObj: xmlObject){ return Object.keys(xmlObj.Document).pop(); }
关键注意点
- 提前嵌入KeyInfo:通过临时签名实例生成标准KeyInfo内容,手动添加全局唯一ID后嵌入原始XML,确保签名前元素已存在,解决XPath查找失败问题
- URI替代XPath:对KeyInfo的引用直接使用
#ID格式,符合XML-DSig标准,比XPath定位更可靠 - 单次签名计算:所有引用添加完成后只调用一次
computeSignature,避免生成重复签名节点
排查建议
- 若签名后XML结构不符合预期,需调整KeyInfo的嵌入位置(修改
targetParentNode的选择逻辑) - 确保KeyInfo的
Id属性全局唯一,避免与XML中其他元素ID冲突 - 检查证书、密钥文件路径是否正确,程序是否有读取权限
内容的提问来源于stack exchange,提问作者Dennis Koech Kipkorir
相关产品推荐
相关产品推荐

