Reputation: 1
Anyone done TicketBAI XML Signing using .NET Core?
After sending request to TicketBAI, I get this error:
The signature does not meet the requirements of the TicketBAI signature policy. (The message has been modified in transit or the signature is not well executed)” is returned, it means that the document hash is different from the signature hash, which indicates that it has been modified after the signature was executed. Please review this process and make sure that you do not modify anything after signing the TicketBAI file.
This is the method that I have used:
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);
}
}
}
}
But when I checked the signature, it's not valid.
Any idea how to resolve this? Does anyone have a working method for TicketBAI signing?
Upvotes: 0
Views: 24