求.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
相关产品推荐
相关产品推荐

