Anyone done TicketBAI XML Signing using .NET Core?

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

Answers (0)

Related Questions