xmlsec 签名后验证失败,如何排查?
结论
从这段代码看,可以先调整签名节点的插入顺序:将 signature_node 加入 XML 树后,再调用 ctx.sign(signature_node)。当前代码则是在签名完成后才执行 root.append(signature_node)。签名时节点还不在最终文档上下文中,验证时却已位于根节点下。命名空间上下文的变化可能影响 SignedInfo 的规范化结果,进而导致 SignatureValue 校验失败。
可以按下面的顺序处理:
signature_node = xmlsec.template.create(
root,
c14n_method=xmlsec.Transform.C14N,
sign_method=xmlsec.Transform.RSA_SHA256,
ns="ns3",
)
# 在签名前将签名节点放入最终文档位置
root.append(signature_node)
# 在此处添加 Reference、Transform、KeyInfo 等模板内容
# ...
ctx.sign(signature_node)
在调用 ctx.sign() 前,应完成模板节点、Reference、Transform 和 KeyInfo 的配置。签名后不要再移动签名节点,也不要修改它或其祖先的命名空间上下文。
为什么插入时机可能影响签名
XML Signature 会先按照指定的 canonicalization 算法处理 SignedInfo,再计算签名。示例中使用的是 xmlsec.Transform.C14N。规范化结果可能受到节点位置和可见命名空间声明的影响。
原始 XML 的根元素声明了默认命名空间 http://xxx.xxx.com/shipment。如果签名模板在脱离最终树上下文时完成签名,之后才被插入根元素下,那么签名和验证阶段看到的命名空间上下文可能不同。即使验证时使用的是同一个内存 XML 对象,节点追加后执行的验证仍处于新的上下文中,因此不能排除命名空间变化造成的问题。
先判断失败发生在哪一步
错误中的 xmlSecOpenSSLEvpSignatureVerify 和 Signature verification failed 指向 RSA 签名值校验阶段。通常,这表示验证端根据 SignedInfo 计算出的签名输入与 SignatureValue 不匹配,也可能是验证时使用的公钥与签名私钥不匹配。
这和 Reference 摘要校验失败是两类问题。若 Reference 摘要不匹配,应检查被引用的节点、Transform、签名后是否修改了 XML 内容,以及 DigestValue 能否通过同一处理流程重新计算。当前日志明确显示签名值校验失败,可以先检查签名节点的最终位置、规范化上下文,以及私钥和证书是否配对。
建议的排查步骤
- 将
root.append(signature_node)移到ctx.sign(signature_node)之前,确保签名时模板已经处于文档中的最终位置。 - 签名后不要移动签名节点,也不要修改可能影响
SignedInfo规范化的命名空间或内容。 - 重新解析写出的
raw_signed.xml后再执行验证,确认序列化并重新解析后仍能通过,而不只是当前内存对象验证成功。 - 确认
myprivatekey.pem对应test.crt中的公钥。验证时从X509Certificate取出的证书也必须与私钥配对。证书能够被解析,并不能证明它与签名私钥匹配。 - 如果调整顺序后错误变成 Reference 摘要校验失败,再检查
URI=""、XPath2 Filter2 Transform、选中的 Header 和 Body,以及签名后 XML 是否发生变化。local-name()可以匹配默认命名空间下的元素名称。若要严格限制命名空间,也可以使用带命名空间前缀的 XPath,并确认 XPath2 Transform 的处理方式符合接收方要求。
注意事项
ctx.verify() 执行成功,只能说明提供的公钥能够验证签名值,并且签名引用的内容通过了相应校验。它不能代替证书链、有效期或业务身份验证。若要验证证书信任,还需要在应用层单独验证证书。
备注:内容仅供参考。
