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 SignedXml
methods:
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.
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.