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

求.NET Core实现TicketBAI XML签名的有效方案及错误排查

.NET Core实现TicketBAI XML签名问题求助

请问有人用.NET Core实现过TicketBAI XML签名吗?我给TicketBAI发送请求后收到错误提示:签名不符合TicketBAI签名政策要求(消息在传输中被修改或签名执行不当),这说明文档哈希与签名哈希不一致,大概率是签名后修改了文件。我使用了自定义的SignXml方法(代码如下),但签名无效,希望得到解决思路或可用的实现方案。

public (XmlDocument, string) SignXml(XDocument xmlDoc, X509Certificate2 uidCert, int? region)
{
    try
    {
        // Convert XDocument to XmlDocument and preserve whitespace
        var xmlDocument = XmlMethods.ConvertToXmlDocument(xmlDoc);

        // Extract the RSA private key from the certificate
        var rsaKey = uidCert.GetRSAPrivateKey();

        // Generate unique IDs for various elements
        var id = Guid.NewGuid();
        var referenceId = Guid.NewGuid();

        // Initialize the SignedXml object with custom namespace
        CustomSignedXml signedXml = new CustomSignedXml(xmlDocument, "ds");
        signedXml.SigningKey = rsaKey;
        signedXml.Signature.Id = "Signature-" + id + "-Signature";
        signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationUrl;
        signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url;

        // Create and add KeyInfo element to SignedXml
        KeyInfo keyInfo = new KeyInfo
            {
                Id = "Signature-" + id + "-KeyInfo"
            };
        keyInfo.AddClause(new RSAKeyValue(rsaKey));

        // Add certificate details to KeyInfo
        KeyInfoX509Data clause = new KeyInfoX509Data();
        clause.AddCertificate(uidCert);

        keyInfo.AddClause(clause);
        signedXml.KeyInfo = keyInfo;

        #region References

        var references = new List<Reference>();

        // Create reference to the main XML document
        var referenceObject = new Reference
            {
                Uri = "",
                Id = "Reference-" + referenceId,
                Type = "http://www.w3.org/2000/09/xmldsig#Object",
                DigestMethod = SignedXml.XmlDsigSHA512Url
            };

        referenceObject.AddTransform(new XmlDsigC14NTransform());
        referenceObject.AddTransform(new XmlDsigEnvelopedSignatureTransform());

        // Add XPath transform to exclude the Signature element
        XmlDsigXPathTransform XPathTransform = CreateXPathTransform("not(ancestor-or-self::Signature)");
        referenceObject.AddTransform(XPathTransform);
        references.Add(referenceObject);

        // Create reference to the SignedProperties
        var referenceSignedProperties = new Reference
            {
                Uri = "#Signature-" + id + "-SignedProperties",
                Type = "http://uri.etsi.org/01903#SignedProperties",
                DigestMethod = SignedXml.XmlDsigSHA512Url
            };
        references.Add(referenceSignedProperties);

        // Create reference to the KeyInfo element
        var referenceKeyInfo = new Reference
            {
                Uri = "#Signature-" + id + "-KeyInfo",
                DigestMethod = SignedXml.XmlDsigSHA512Url
            };
        references.Add(referenceKeyInfo);

        // Add all references to the SignedXml object
        foreach (var _reference in references)
        {
            signedXml.AddReference(_reference);
        }

        #endregion References

        #region Add Object Element Values

        // Define namespaces and URIs
        XmlNamespaceManager nsManager = new XmlNamespaceManager(xmlDocument.NameTable);
        nsManager.AddNamespace("xades", "http://uri.etsi.org/01903/v1.3.2#");
        nsManager.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");

        var URI = "http://uri.etsi.org/01903/v1.3.2#";
        var W3URI = "http://www.w3.org/2000/09/xmldsig#";

        // Create Object element for the signature
        XmlElement objectElement = xmlDocument.CreateElement("ds", "Object", W3URI);

        // Create and populate the QualifyingProperties element
        XmlElement qualifyingProperties = xmlDocument.CreateElement("xades", "QualifyingProperties", URI);
        qualifyingProperties.SetAttribute("xmlns:ds", W3URI);
        qualifyingProperties.SetAttribute("Id", "Signature-" + id + "-QualifyingProperties");
        qualifyingProperties.SetAttribute("Target", "#Signature" + id + "-Signature");

        // Create and populate the SignedProperties element
        XmlElement signedProperties = xmlDocument.CreateElement("xades", "SignedProperties", URI);
        signedProperties.SetAttribute("Id", "Signature-" + id + "-SignedProperties");

        // Create SignedSignatureProperties and populate it with required elements
        XmlElement signedSignatureProperties = xmlDocument.CreateElement("xades", "SignedSignatureProperties", URI);

        // Add SigningTime element
        XmlElement signingTime = xmlDocument.CreateElement("xades", "SigningTime", URI);
        signingTime.InnerText = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
        signedSignatureProperties.AppendChild(signingTime);

        // Add SigningCertificate element with Cert details
        XmlElement signingCertificate = xmlDocument.CreateElement("xades", "SigningCertificate", URI);
        XmlElement cert = xmlDocument.CreateElement("xades", "Cert", URI);

        // Add CertDigest with DigestMethod and DigestValue
        XmlElement certDigest = xmlDocument.CreateElement("xades", "CertDigest", URI);
        XmlElement digestMethod = xmlDocument.CreateElement("ds", "DigestMethod", W3URI);
        digestMethod.SetAttribute("Algorithm", SignedXml.XmlDsigSHA512Url);
        certDigest.AppendChild(digestMethod);

        XmlElement digestValue = xmlDocument.CreateElement("ds", "DigestValue", W3URI);
        digestValue.InnerText = CalculateCertificateDigestValue(uidCert);
        certDigest.AppendChild(digestValue);
        cert.AppendChild(certDigest);

        // Add IssuerSerial details
        XmlElement issuerSerial = xmlDocument.CreateElement("xades", "IssuerSerial", URI);
        XmlElement x509IssuerName = xmlDocument.CreateElement("ds", "X509IssuerName", W3URI);
        x509IssuerName.InnerText = uidCert.Issuer;
        issuerSerial.AppendChild(x509IssuerName);

        XmlElement x509SerialNumber = xmlDocument.CreateElement("ds", "X509SerialNumber", W3URI);
        x509SerialNumber.InnerText = uidCert.SerialNumber;
        issuerSerial.AppendChild(x509SerialNumber);

        cert.AppendChild(issuerSerial);
        signingCertificate.AppendChild(cert);
        signedSignatureProperties.AppendChild(signingCertificate);

        // Add SignaturePolicyIdentifier with required sub-elements
        XmlElement signaturePolicyIdentifier = xmlDocument.CreateElement("xades", "SignaturePolicyIdentifier", URI);
        XmlElement signaturePolicyId = xmlDocument.CreateElement("xades", "SignaturePolicyId", URI);
        XmlElement sigPolicyId = xmlDocument.CreateElement("xades", "SigPolicyId", URI);

        XmlElement identifier = xmlDocument.CreateElement("xades", "Identifier", URI);
        identifier.InnerText = GetPolicyIdentifier(region);
        sigPolicyId.AppendChild(identifier);

        XmlElement sigPolicyHash = xmlDocument.CreateElement("xades", "SigPolicyHash", URI);
        XmlElement digestMethodPolicy = xmlDocument.CreateElement("ds", "DigestMethod", W3URI);
        digestMethodPolicy.SetAttribute("Algorithm", GetPolicyDigestMethod());
        sigPolicyHash.AppendChild(digestMethodPolicy);

        XmlElement digestValuePolicy = xmlDocument.CreateElement("ds", "DigestValue", W3URI);
        digestValuePolicy.InnerText = GetPolicyDigestValue(region);
        sigPolicyHash.AppendChild(digestValuePolicy);

        XmlElement sigPolicyQualifier = xmlDocument.CreateElement("xades", "SigPolicyQualifier", URI);
        XmlElement spuri = xmlDocument.CreateElement("xades", "SPURI", URI);
        spuri.InnerText = GetPolicyIdentifier(region);
        sigPolicyQualifier.AppendChild(spuri);

        XmlElement sigPolicyQualifiers = xmlDocument.CreateElement("xades", "SigPolicyQualifiers", URI);
        sigPolicyQualifiers.AppendChild(sigPolicyQualifier);

        signaturePolicyId.AppendChild(sigPolicyId);
        signaturePolicyId.AppendChild(sigPolicyHash);
        signaturePolicyId.AppendChild(sigPolicyQualifiers);

        signaturePolicyIdentifier.AppendChild(signaturePolicyId);
        signedSignatureProperties.AppendChild(signaturePolicyIdentifier);
        signedProperties.AppendChild(signedSignatureProperties);

        // Create SignedDataObjectProperties with DataObjectFormat and ObjectIdentifier
        XmlElement signedDataObjectProperties = xmlDocument.CreateElement("xades", "SignedDataObjectProperties", URI);
        XmlElement dataObjectFormat = xmlDocument.CreateElement("xades", "DataObjectFormat", URI);
        dataObjectFormat.SetAttribute("ObjectReference", "#Reference-" + referenceId);

        XmlElement objectIdentifier = xmlDocument.CreateElement("xades", "ObjectIdentifier", URI);
        XmlElement identifierFormat = xmlDocument.CreateElement("xades", "Identifier", URI);
        identifierFormat.SetAttribute("Qualifier", "OIDAsURN");
        identifierFormat.InnerText = "urn:oid:1.2.840.10003.5.109.10";
        objectIdentifier.AppendChild(identifierFormat);
        dataObjectFormat.AppendChild(objectIdentifier);

        // Add MimeType element
        XmlElement mimeType = xmlDocument.CreateElement("xades", "MimeType", URI);
        mimeType.InnerText = "text/xml";
        dataObjectFormat.AppendChild(mimeType);

        signedDataObjectProperties.AppendChild(dataObjectFormat);
        signedProperties.AppendChild(signedDataObjectProperties);
        qualifyingProperties.AppendChild(signedProperties);
        objectElement.AppendChild(qualifyingProperties);

        // Add the Object element to the signature
        DataObject dataObject = new DataObject();
        dataObject.LoadXml(objectElement);
        signedXml.AddObject(dataObject);

        #endregion Add Object Element Values

        #region Refactored and Finalized Signing Process

        // Create a temporary SignedXml object to compute the signature
        SignedXml tmp = new SignedXml(xmlDocument)
            {
                SigningKey = signedXml.SigningKey,
                KeyInfo = signedXml.KeyInfo,
            };
        tmp.Signature.Id = "Signature-" + id + "-Signature";
        tmp.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationUrl;
        tmp.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url;

        // Add the existing Objects to the temporary SignedXml object
        foreach (DataObject obj in signedXml.Signature.ObjectList)
        {
            tmp.AddObject(obj);
        }

        // Add a reference to the empty string for the main document and compute the signature
        tmp.AddReference(new Reference(""));
        tmp.ComputeSignature();

        // Append the computed signature to the document
        XmlElement elem = tmp.GetXml();
        xmlDocument.DocumentElement?.AppendChild(elem);

        // Compute the final signature with the original SignedXml object
        signedXml.ComputeSignature();

        // Replace the temporary signature element with the final one, ensuring correct namespace
        if (xmlDocument.DocumentElement != null)
        {
            xmlDocument.DocumentElement.RemoveChild(elem);
            var signedXElement = signedXml.GetPrefixedXml();
            signedXElement.SetAttribute("xmlns:ds", W3URI);
            xmlDocument.DocumentElement.AppendChild(signedXElement);
        }

        #endregion Refactored and Finalized Signing Process

        // Get signature value
        var signatureValue = Convert.ToBase64String(signedXml.SignatureValue);

        return (xmlDocument, signatureValue);
    }
    catch (Exception ex)
    {
        throw;
    }
}

public class CustomSignedXml : SignedXml
{
    private readonly string _prefix;

    public CustomSignedXml(XmlDocument document, string prefix = "ds") : base(document)
    {
        _prefix = prefix;
    }

    public XmlElement GetPrefixedXml()
    {
        // Generate the signature XML
        XmlElement xmlElement = this.GetXml();

        // Add the prefix to all elements
        AddPrefix(xmlElement, _prefix);

        return xmlElement;
    }

    private void AddPrefix(XmlElement element, string prefix)
    {
        // Add prefix to the current element
        if (!string.IsNullOrEmpty(prefix))
        {
            element.Prefix = prefix;
        }

        // Recursively add prefixes to child elements
        foreach (XmlNode childNode in element.ChildNodes)
        {
            if (childNode is XmlElement childElement && childElement.LocalName != "QualifyingProperties")
            {
                AddPrefix(childElement, prefix);
            }
        }
    }
}

排查与解决思路

  • 移除冗余的临时签名逻辑:代码中临时SignedXml对象的操作完全多余,添加再删除签名节点的过程极可能修改XML结构,直接删除整个临时签名代码块,只用CustomSignedXml完成所有配置后执行ComputeSignature,再将结果添加到文档中。
  • 严格控制XML格式:确认XmlMethods.ConvertToXmlDocument转换时完全保留原始XML的空格、换行和命名空间,签名后直接输出原始字节流,禁止任何二次格式化操作,TicketBAI对XML规范化要求极高,微小格式变化都会导致哈希不匹配。
  • 修正XPath转换的命名空间:当前XPath转换未指定命名空间上下文,TicketBAI要求排除Signature节点时需明确命名空间,修改XPath转换的初始化代码:
    var xpathExpression = "not(ancestor-or-self::ds:Signature)";
    XmlDsigXPathTransform xpathTransform = new XmlDsigXPathTransform();
    xpathTransform.LoadInnerXml(XmlNodeReader.Create(new StringReader($"<XPath xmlns:ds=\"{W3URI}\">{xpathExpression}</XPath>")));
    
  • 核对引用配置与官方规范:检查各个Reference的Type和Uri是否完全符合TicketBAI官方文档,比如主文档引用的Type应为http://uri.etsi.org/01903#SignedDataObjectProperties而非http://www.w3.org/2000/09/xmldsig#Object,需严格匹配政策要求。
  • 验证证书哈希计算:确认CalculateCertificateDigestValue是对证书的DER编码计算SHA512哈希,代码示例:
    private string CalculateCertificateDigestValue(X509Certificate2 cert)
    {
        using var sha512 = SHA512.Create();
        byte[] hash = sha512.ComputeHash(cert.RawData);
        return Convert.ToBase64String(hash);
    }
    
  • 检查命名空间前缀处理:确保CustomSignedXml中的前缀添加逻辑正确,所有ds命名空间元素都带有正确前缀,QualifyingProperties的xades命名空间未被错误修改。

内容的提问来源于stack exchange,提问作者Nimantha Jayathilake

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 21:17:02