3

It is possible to sign a XML document without passing the URI attribute to the <Reference> tag?

I was able to sign the entire XML document of type <Invoice> using KeyInfoX509Data certificate and XAdEs format using C#. The signature is valid, SignedXml.CheckSignature() returns true. The problem is that the broker who is responsible to validate the signature doesn't support empty attributes, such as:

<ds:Reference URI=""> // Here is the problem
            <ds:Transforms>
                <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>rDCBSbPc1QFATbQeP+77oskLJ3Tw6aog61bqGtyglXs=</ds:DigestValue>
        </ds:Reference>

I decided to remove the URI attribute before sending the document to broker, but SignedXml.CheckSignature() returns false without it. The signature is not valid without the URI.

I tried to remove the URI attribute before calling the SignedXml.ComputeSignature(), but I got the following Exception:

An XmlDocument context is required for enveloped transforms.

Which means that the URI attribute is mandatory when using the XmlDsigEnvelopedSignatureTransform class to generate the signature.

Then, I tried to set a Id attribute to the <Invoice> tag:

<Invoice Id="id-dsig-123456"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../oasis/maindoc/UBL-Invoice-2.1.xsd"
     xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
     xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
     xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">

This means that the signature will still validate the entire <Invoice> document, by searching for the Id attribute on the <Reference>tag:

<ds:Reference URI="#id-dsig-123456">

The signature is generated and the SignedXml.CheckSignature() returns true, but the broker doesn't allow any attributes on <Invoice> tag and invalidates the signature.

I need to generate the signature like this

<ds:Signature Id="id-a086e4d3-ee02-458c-8429-eafe54eb6f6b"
              xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <ds:SignedInfo>
        <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
        <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
        <ds:Reference> ***// Without URI***
            <ds:Transforms>
                <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>rDCBSbPc1QFATbQeP+77oskLJ3Tw6aog61bqGtyglXs=</ds:DigestValue>
        </ds:Reference>
        <ds:Reference URI="#xades-id-a086e4d3-ee02-458c-8429-eafe54eb6f6b"
                      Type="http://uri.etsi.org/01903#SignedProperties">
            <ds:Transforms>
                <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>xBW/yL+uQJa3KrCXjdEPlFEJns7ZnM2pHs0Y5nLNvMM=</ds:DigestValue>
        </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue>XDTgRB3qyzLPMBPzfYeuwGSOz5JN52cdstIMHo3IXc8jGWp5JWFbOTD7Nj7QHZB0z5mULXgL7/eWfR5KkrT12aRHWPHocCbLfBgAi1xjNYGW1aNNxhnn4fSZSg4KrWS9oMoxkEKM2IrbJ++PgIM4+89MZA4kEtyih4WKkENnlE+w4RDnstjirA5viLZQEBDegPtGqS9ybrVej5QkF5/fy8HpZKsEl4oWPIWSrTQk7G3d4oDo/d2AU8XQgvcht+LkttJF4PJRq3AiyqwlJjoNSbt1R7NWYEJp+IulWEU0pdvW7LcVjrLO8yIkoHTWtyhbP20Zi3zLHxP9uPpMD37tzQ==</ds:SignatureValue>
    <ds:KeyInfo>
        <ds:X509Data>
            <ds:X509Certificate>Certificate raw content...</ds:X509Certificate>
        </ds:X509Data>
    </ds:KeyInfo>
    <ds:Object>
        <xades:QualifyingProperties Target="#id-a086e4d3-ee02-458c-8429-eafe54eb6f6b"
                                    xmlns:xades="http://uri.etsi.org/01903/v1.3.2#">
            <xades:SignedProperties Id="xades-id-a086e4d3-ee02-458c-8429-eafe54eb6f6b">
                <xades:SignedSignatureProperties>
                    <xades:SigningTime>2021-06-05T02:52:49Z</xades:SigningTime>
                    <xades:SigningCertificate>
                        <xades:Cert>
                            <xades:CertDigest>
                                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                                <ds:DigestValue>LgjnhFUgsN4oTt4wjtzF+/7GYHEkTLVijMpjlMjfZ2E=</ds:DigestValue>
                            </xades:CertDigest>
                            <xades:IssuerSerial>
                                <ds:X509IssuerName>Certificate data....</ds:X509IssuerName>
                                <ds:X509SerialNumber>Serial number...</ds:X509SerialNumber>
                            </xades:IssuerSerial>
                        </xades:Cert>
                    </xades:SigningCertificate>
                </xades:SignedSignatureProperties>
            </xades:SignedProperties>
        </xades:QualifyingProperties>
    </ds:Object>
</ds:Signature>

To generate the signature, I'm using the following code. The PrefixedSignedXml class inherits from SignedXml and is used to set the "ds:" prefix when using the SignedXmlmethods:

private PrefixedSignedXml GeneratePrefixedSignedXml(XmlDocument xmlDocument, X509Certificate2 certificate)
    {
        Log.Information("[Begin] - GeneratePrefixedSignedXml");

        Sha256SignatureDescription.Register();

        var keyInfo = new KeyInfo();
        var cspParams = new CspParameters(24) { KeyContainerName = PrefixedSignedXml.XmlKeyContainerName };
        var rsaKey = new RSACryptoServiceProvider(cspParams) { PersistKeyInCsp = false };

        keyInfo.AddClause(new KeyInfoX509Data(certificate));

        var signedXml = new PrefixedSignedXml(xmlDocument) { SigningKey = rsaKey };

        signedXml.KeyInfo = keyInfo;
        signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
        signedXml.SignedInfo.SignatureMethod = PrefixedSignedXml.XmlRsaSignatureMethod;

        var signatureReference = new Reference
        {
            Uri = "", // This needs to be null
            DigestMethod = PrefixedSignedXml.XmlRsaDigestMethod,
        };

        signatureReference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
        signedXml.AddReference(signatureReference);

        Log.Information("[End] - GeneratePrefixedSignedXml");

        return signedXml;
    }

This is the PrefixedSignedXml:

public class PrefixedSignedXml : SignedXml
{
    public const string XmlDsigSignatureProperties = "http://uri.etsi.org/01903#SignedProperties";
    public const string XadesProofOfApproval = "http://uri.etsi.org/01903/v1.2.2#ProofOfApproval";
    public const string XadesNamespaceUrl = "http://uri.etsi.org/01903/v1.3.2#";
    public const string XmlRsaSignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
    public const string XmlRsaDigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
    public const string DsaSignatureMethod = "http://www.w3.org/2000/09/xmldsig#dsa-sha1";
    public const string XmlKeyContainerName = "XML_DSIG_RSA_KEY";

    private const string CryptographicXmlLoadKeyFailedException = "Cryptography_Xml_LoadKeyFailed";
    private const string CryptographicXmlCreatedKeyFailedException = "Cryptography_Xml_CreatedKeyFailed";
    private const string CryptographicXmlSignatureDescriptionNotCreatedException = "Cryptography_Xml_SignatureDescriptionNotCreated";
    private const string CryptographicExceptionXmlCreateHashAlgorithmFailed = "Cryptography_Xml_CreateHashAlgorithmFailed";
    private const string MethodInfoName = "BuildDigestedReferences";

    public XmlElement PropertiesNode { get; set; }

    private readonly List<DataObject> _dataObjects = new List<DataObject>();

    public PrefixedSignedXml(XmlDocument document)
        : base(document) { }

    public override XmlElement GetIdElement(XmlDocument document, string idValue)
    {
        if (string.IsNullOrEmpty(idValue))
            return null;

        var xmlElement = base.GetIdElement(document, idValue);

        if (xmlElement == null)
        {
            XmlNamespaceManager nsManager = new XmlNamespaceManager(document.NameTable);
            nsManager.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

            xmlElement = document.SelectSingleNode("//*[@Id=\"" + idValue + "\"]", nsManager) as XmlElement;
        }

        if (xmlElement != null)
            return xmlElement;

        if (_dataObjects.Count == 0)
            return null;

        foreach (var dataObject in _dataObjects)
        {
            var nodeWithSameId = XmlHelper.FindNodeWithAttributeValueIn(dataObject.Data, "Id", idValue);

            if (nodeWithSameId != null)
                return nodeWithSameId;
        }

        return null;
    }

    public new void AddObject(DataObject dataObject)
    {
        base.AddObject(dataObject);
        _dataObjects.Add(dataObject);
    }

    public void ComputeSignature(string prefix)
    {
        this.BuildDigestedReferences();

        var signingKey = this.SigningKey;

        if (signingKey == null)
            throw new CryptographicException(CryptographicXmlLoadKeyFailedException);

        if (this.SignedInfo.SignatureMethod == null)
        {
            if ((signingKey is DSA))
                this.SignedInfo.SignatureMethod = DsaSignatureMethod;

            if (!(signingKey is RSA))
                throw new CryptographicException(CryptographicXmlCreatedKeyFailedException);

            if (this.SignedInfo.SignatureMethod == null)
                this.SignedInfo.SignatureMethod = XmlRsaSignatureMethod;
        }

        if (!(CryptoConfig.CreateFromName(this.SignedInfo.SignatureMethod) is SignatureDescription description))
            throw new CryptographicException(CryptographicXmlSignatureDescriptionNotCreatedException);

        var hash = description.CreateDigest();

        if (hash == null)
            throw new CryptographicException(CryptographicExceptionXmlCreateHashAlgorithmFailed);

        this.GetC14NDigest(hash, prefix);
        this.m_signature.SignatureValue = description.CreateFormatter(signingKey).CreateSignature(hash);
    }

    public XmlElement GetXml(string prefix)
    {
        var xmlElement = this.GetXml();

        XmlHelper.SetPrefix(prefix, xmlElement);

        return xmlElement;
    }

    private void BuildDigestedReferences()
    {
        var type = typeof(SignedXml);

        var methodInfo = type.GetMethod(MethodInfoName, BindingFlags.NonPublic | BindingFlags.Instance);

        methodInfo.Invoke(this, new object[] { });
    }

    private byte[] GetC14NDigest(HashAlgorithm hash, string prefix)
    {
        var canonicalizationMethodObject = this.SignedInfo.CanonicalizationMethodObject;

        var document = new XmlDocument
        {
            PreserveWhitespace = true
        };

        document.AppendChild(document.ImportNode(this.SignedInfo.GetXml(), true));

        XmlHelper.SetPrefix(prefix, document.DocumentElement);

        canonicalizationMethodObject.LoadInput(document);

        return canonicalizationMethodObject.GetDigestedOutput(hash);
    }
}

And this is the method used to add the XAdEs attributes:

private void AddXadesProperties(XmlDocument document, PrefixedSignedXml xadesSignedXml, X509Certificate2 signingCertificate)
    {
        Log.Information("[Begin] - AddXadesProperties");

        var parametersSignature = new Reference
        {
            Uri = $"#{SignaturePropertiesId}{SignatureId}",
            Type = PrefixedSignedXml.XmlDsigSignatureProperties,
            DigestMethod = PrefixedSignedXml.XmlRsaDigestMethod
        };

        parametersSignature.AddTransform(new XmlDsigExcC14NTransform());
        xadesSignedXml.AddReference(parametersSignature);

        // <Object>
        var objectNode = document.CreateElement(XmlDsPrefix, "Object", SignedXml.XmlDsigNamespaceUrl);

        // <Object><QualifyingProperties>
        var qualifyingPropertiesNode = document.CreateElement(XadesPrefix, "QualifyingProperties", PrefixedSignedXml.XadesNamespaceUrl);

        qualifyingPropertiesNode.SetAttribute("Target", $"#{SignatureId}");
        objectNode.AppendChild(qualifyingPropertiesNode);

        // <Object><QualifyingProperties><SignedProperties>
        var signedPropertiesNode = document.CreateElement(XadesPrefix, "SignedProperties", PrefixedSignedXml.XadesNamespaceUrl);

        signedPropertiesNode.SetAttribute("Id", $"{SignaturePropertiesId}{SignatureId}");
        qualifyingPropertiesNode.AppendChild(signedPropertiesNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties>
        var signedSignaturePropertiesNode = document.CreateElement(XadesPrefix, "SignedSignatureProperties", PrefixedSignedXml.XadesNamespaceUrl);

        signedPropertiesNode.AppendChild(signedSignaturePropertiesNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties></SigningTime>
        var signingTime = document.CreateElement(XadesPrefix, "SigningTime", PrefixedSignedXml.XadesNamespaceUrl);

        signingTime.InnerText = $"{DateTime.UtcNow:s}Z";
        signedSignaturePropertiesNode.AppendChild(signingTime);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate>
        var signingCertificateNode = document.CreateElement(XadesPrefix, "SigningCertificate", PrefixedSignedXml.XadesNamespaceUrl);

        signedSignaturePropertiesNode.AppendChild(signingCertificateNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert>
        var certNode = document.CreateElement(XadesPrefix, "Cert", PrefixedSignedXml.XadesNamespaceUrl);

        signingCertificateNode.AppendChild(certNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest>
        var certDigestNode = document.CreateElement(XadesPrefix, "CertDigest", PrefixedSignedXml.XadesNamespaceUrl);

        certNode.AppendChild(certDigestNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest></DigestMethod>
        var digestMethod = document.CreateElement(XmlDsPrefix, "DigestMethod", SignedXml.XmlDsigNamespaceUrl);
        var digestMethodAlgorithmAtribute = document.CreateAttribute("Algorithm");

        digestMethodAlgorithmAtribute.InnerText = PrefixedSignedXml.XmlRsaDigestMethod;
        digestMethod.Attributes.Append(digestMethodAlgorithmAtribute);
        certDigestNode.AppendChild(digestMethod);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest></DigestMethod>
        var digestValue = document.CreateElement(XmlDsPrefix, "DigestValue", SignedXml.XmlDsigNamespaceUrl);

        digestValue.InnerText = GenerateCertificateHash256Value(signingCertificate);
        certDigestNode.AppendChild(digestValue);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial>
        var issuerSerialNode = document.CreateElement(XadesPrefix, "IssuerSerial", PrefixedSignedXml.XadesNamespaceUrl);

        certNode.AppendChild(issuerSerialNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial></X509IssuerName>
        var x509IssuerName = document.CreateElement(XmlDsPrefix, "X509IssuerName", SignedXml.XmlDsigNamespaceUrl);

        x509IssuerName.InnerText = signingCertificate.IssuerName.Name;
        issuerSerialNode.AppendChild(x509IssuerName);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial></X509SerialNumber>
        var x509SerialNumber = document.CreateElement(XmlDsPrefix, "X509SerialNumber", SignedXml.XmlDsigNamespaceUrl);

        x509SerialNumber.InnerText = ToDecimalSerialNumberString(signingCertificate.SerialNumber);
        issuerSerialNode.AppendChild(x509SerialNumber);

        var dataObject = new DataObject { Data = qualifyingPropertiesNode.SelectNodes(".") };

        xadesSignedXml.AddObject(dataObject);

        Log.Information("[End] - AddXadesProperties");
    }

After that, the ComputeSignature() generates the digests and the initial signature, then I pass the <ds:SignedInfo> tag to another server who generates the final <ds:SignatureValue>. Finally, I pass the <SignatureValue> to the <ds:Signature> itself, append to the XmlDocument and send to the broker.

Does anyone knows if it's possible to sign a XML document without passing the URI attribute to the <Reference> tag?

Project is a ClassLibrary using .NetFramework 4.6.

I'm facing this problem for a while, any help would be grateful.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
  • Why? The hash for the signature is include the namespace. The namespace indicates the encryption method (hash) that is used to encrypt so it can be checked. In you case it is "http://www.w3.org/2000/09/xmldsig#" – jdweng Jun 05 '21 at 15:54
  • When the URI has "" or some Id value, it indicates the content that will be signed. The indicates that the whole document will be signed (and this is what I want to do). If defined like this , it will search for this reference in the XML to sign. It seems that .NET doesn't allow the URI defined to null, and this is what the broker needs to receive: . If I signed the document and remove the URI before send to broker, the signature is not valid anymore, so I want to find a way to sign the document without the URI reference. – Danilo Souza Jun 05 '21 at 16:18
  • You shouldn't try to remove the URI, it is needed. – jdweng Jun 05 '21 at 18:10
  • Is there any scenario or example of not having the URI? W3C documentation says that the URI is optional ("") and not null. MS documentation always have examples passing the URI too. The guys from the broker service send me a example of a XML signed with without the URI. I have no idea how they managed to generate a signature like this. The example they send me managed to pass on XML sign validators like https://weryfikacjapodpisu.pl/verification/#dropzone and https://tools.chilkat.io/xmlDsigVerify.cshtml. I managed to pass too, but only with the URI attribute. – Danilo Souza Jun 05 '21 at 19:34
  • Uri is needed in some case like this. Show me where W3C says optional. – jdweng Jun 05 '21 at 21:36
  • https://www.w3.org/TR/2002/REC-xmldsig-core-20020212/#sec-Reference, the URI is defined as optional in the ReferenceType. It says this definition too "If the URI attribute is omitted altogether, the receiving application is expected to know the identity of the object. For example, a lightweight data protocol might omit this attribute given the identity of the object is part of the application context. This attribute may be omitted from at most one Reference in any particular SignedInfo, or Manifest." – Danilo Souza Jun 05 '21 at 22:02
  • Not when there is a schema that requires the namespace. – jdweng Jun 06 '21 at 07:11

2 Answers2

1

Turns out that I was right this whole time. The *<Reference URI="#id-value">* tag is necessary on this case and the broker was updated to support this format.

Thanks to @jdweng for the support.

0

I got Reference without URI attribute, but in my case, resulted signed xml still could be validated on the other side. May be my code can help.

  1. Create a transform class, that is inherited from XmlDsigExcC14NTransform

    public class MyXmlDsigExcC14NTransform : XmlDsigExcC14NTransform
    {
    
      public override void LoadInput(object obj)
      {
        if(obj == null)
            obj = this.Context.OwnerDocument;
    
        base.LoadInput(obj);
      }
    }
    
  2. Add this transform to reference instance:

    var documentReference = new Reference();
    documentReference.DigestMethod = "http://www.w3.org/2000/09/xmldsig#sha1";
    documentReference.AddTransform(new MyXmlDsigExcC14NTransform());
    references.Add(documentReference);
    

After I had signed xml, I received needed Reference tag in signature.

<ds:Reference>
    <ds:Transforms>
        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
    </ds:Transforms>
    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
    <ds:DigestValue>DigestValue</ds:DigestValue>
</ds:Reference>

Hope it will help

Nikolai MD
  • 11
  • 2