Search code examples
c#signaturesignsignedxml

C# how to properly sign message with XAdES using SignedXml?


I'm struggling with message signatures using XAdES. I've searched for the solution which brought me to the code below. But the service that I'm trying to connect to is telling me that the signature is wrong. What am I doing wrong?

public static class Signature
    {
        #region Private fields
        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 XadesPrefix = "xades";
        public const string SignaturePrefix = "ds";
        public const string SignatureNamespace = "http://www.w3.org/2000/09/xmldsig#";
        public const string XadesNamespaceUrl = "http://uri.etsi.org/01903/v1.3.2#";

        private const string SignatureId = "Signature";
        private const string SignaturePropertiesId = "SignedProperties";
        #endregion Private fields

        #region Public methods
        public static XmlElement SignWithXades(X509Certificate2 signingCertificate, string xml)
        {
            var xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(xml);

            AddSignatureProperties(xmlDocument, signingCertificate);

            var signedXml = new SignedXml(xmlDocument);
            signedXml.Signature.Id = SignatureId;
            signedXml.SigningKey = signingCertificate.PrivateKey;

            var signatureReference = new Reference { Uri = "", };
            signatureReference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
            signedXml.AddReference(signatureReference);

            var parametersSignature = new Reference
            {
                Uri = $"#{SignaturePropertiesId}",
                Type = XmlDsigSignatureProperties,
            };
            signedXml.AddReference(parametersSignature);

            var keyInfo = new KeyInfo();
            keyInfo.AddClause(new KeyInfoX509Data(signingCertificate));
            signedXml.KeyInfo = keyInfo;

            signedXml.ComputeSignature();

            var signatureNode = signedXml.GetXml();

            AssignNameSpacePrefixToElementTree(signatureNode, SignaturePrefix);

            var signedInfoNode = signatureNode.SelectSingleNode("//*[local-name()='SignedInfo']");
            var signatureValue = signatureNode.SelectSingleNode("//*[local-name()='SignatureValue']");
            var keyInfoNode = xmlDocument.SelectSingleNode("//*[local-name()='KeyInfo']");

            var finalSignatureNode = xmlDocument.SelectSingleNode("//*[local-name()='Signature']");
            finalSignatureNode.InsertBefore(signatureValue, keyInfoNode);
            finalSignatureNode.InsertBefore(signedInfoNode, signatureValue);

            return (XmlElement)finalSignatureNode;
        }
        #endregion Public methods

        #region Private methods
        private static void AddSignatureProperties(XmlDocument document, X509Certificate2 signingCertificate)
        {
            // <Signature>
            var signatureNode = document.CreateElement(SignaturePrefix, "Signature", SignatureNamespace);
            var signatureIdAttribute = document.CreateAttribute("Id");
            signatureIdAttribute.InnerText = SignatureId;
            signatureNode.Attributes.Append(signatureIdAttribute);
            document.DocumentElement.AppendChild(signatureNode);

            AddKeyInfo(document, signatureNode, signingCertificate);
            AddCertificateObject(document, signatureNode, signingCertificate);
        }

        private static void AddKeyInfo(XmlDocument document, XmlElement signatureNode, X509Certificate2 signingCertificate)
        {
            // <KeyInfo>
            var keyInfoNode = document.CreateElement(SignaturePrefix, "KeyInfo", SignatureNamespace);
            signatureNode.AppendChild(keyInfoNode);

            // <KeyInfo><X509Data>
            var x509DataNode = document.CreateElement(SignaturePrefix, "X509Data", SignatureNamespace);
            keyInfoNode.AppendChild(x509DataNode);

            var x509CertificateNode = document.CreateElement(SignaturePrefix, "X509Certificate", SignatureNamespace);
            x509CertificateNode.InnerText = Convert.ToBase64String(signingCertificate.GetRawCertData());
            x509DataNode.AppendChild(x509CertificateNode);
        }

        private static void AddCertificateObject(XmlDocument document, XmlElement signatureNode, X509Certificate2 signingCertificate)
        {
            // <Object>
            var objectNode = document.CreateElement(SignaturePrefix, "Object", SignatureNamespace);
            signatureNode.AppendChild(objectNode);

            // <Object><QualifyingProperties>
            var qualifyingPropertiesNode = document.CreateElement(XadesPrefix, "QualifyingProperties", XadesNamespaceUrl);
            qualifyingPropertiesNode.SetAttribute("Target", $"#{SignatureId}");
            objectNode.AppendChild(qualifyingPropertiesNode);

            // <Object><QualifyingProperties><SignedProperties>
            var signedPropertiesNode = document.CreateElement(XadesPrefix, "SignedProperties", XadesNamespaceUrl);
            signedPropertiesNode.SetAttribute("Id", SignaturePropertiesId);
            qualifyingPropertiesNode.AppendChild(signedPropertiesNode);

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

            // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties> </SigningTime>
            var signingTime = document.CreateElement(XadesPrefix, "SigningTime", XadesNamespaceUrl);
            signingTime.InnerText = $"{DateTime.UtcNow.ToString("s")}Z";
            signedSignaturePropertiesNode.AppendChild(signingTime);

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

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

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

            // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest> </DigestMethod>
            var digestMethod = document.CreateElement("ds", "DigestMethod", SignedXml.XmlDsigNamespaceUrl);
            var digestMethodAlgorithmAtribute = document.CreateAttribute("Algorithm");
            digestMethodAlgorithmAtribute.InnerText = SignedXml.XmlDsigSHA1Url;
            digestMethod.Attributes.Append(digestMethodAlgorithmAtribute);
            certDigestNode.AppendChild(digestMethod);

            // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest> </DigestMethod>
            var digestValue = document.CreateElement("ds", "DigestValue", SignedXml.XmlDsigNamespaceUrl);
            digestValue.InnerText = Convert.ToBase64String(signingCertificate.GetCertHash());
            certDigestNode.AppendChild(digestValue);

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

            // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial> </X509IssuerName>
            var x509IssuerName = document.CreateElement("ds", "X509IssuerName", SignedXml.XmlDsigNamespaceUrl);
            x509IssuerName.InnerText = signingCertificate.Issuer;
            issuerSerialNode.AppendChild(x509IssuerName);

            // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial> </X509SerialNumber>
            var x509SerialNumber = document.CreateElement("ds", "X509SerialNumber", SignedXml.XmlDsigNamespaceUrl);
            x509SerialNumber.InnerText = ToDecimalString(signingCertificate.SerialNumber);
            issuerSerialNode.AppendChild(x509SerialNumber);

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

            // <Object><QualifyingProperties><SignedProperties><SignedDataObjectProperties><CommitmentTypeIndication>
            var commitmentTypeIndicationNode = document.CreateElement(XadesPrefix, "CommitmentTypeIndication", XadesNamespaceUrl);
            signedDataObjectPropertiesNode.AppendChild(commitmentTypeIndicationNode);

            // <Object><QualifyingProperties><SignedProperties><SignedDataObjectProperties><CommitmentTypeIndication><CommitmentTypeId>
            var commitmentTypeIdNode = document.CreateElement(XadesPrefix, "CommitmentTypeId", XadesNamespaceUrl);
            commitmentTypeIndicationNode.AppendChild(commitmentTypeIdNode);

            // <Object><QualifyingProperties><SignedProperties><SignedDataObjectProperties><CommitmentTypeIndication><CommitmentTypeId><Identifier>
            var identifierNode = document.CreateElement(XadesPrefix, "Identifier", XadesNamespaceUrl);
            identifierNode.InnerText = XadesProofOfApproval;
            commitmentTypeIdNode.AppendChild(identifierNode);

            // <Object><QualifyingProperties><SignedProperties><SignedDataObjectProperties><CommitmentTypeIndication><AllSignedDataObjects>
            var allSignedDataObjectsNode = document.CreateElement(XadesPrefix, "AllSignedDataObjects", XadesNamespaceUrl);
            commitmentTypeIndicationNode.AppendChild(allSignedDataObjectsNode);
        }

        private static string ToDecimalString(string serialNumber)
        {
            BigInteger bi;

            if (BigInteger.TryParse(serialNumber, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out bi))
            {
                return bi.ToString(CultureInfo.InvariantCulture);
            }
            else
            {
                return serialNumber;
            }
        }

        private static void AssignNameSpacePrefixToElementTree(XmlElement element, string prefix)
        {
            element.Prefix = prefix;

            foreach (var child in element.ChildNodes)
            {
                if (child is XmlElement)
                    AssignNameSpacePrefixToElementTree(child as XmlElement, prefix);
            }
        }
        #endregion Private methods
    }

Message that service is required should look like this

<Tag1>
  <Tag2>...</Tag2>
  <ds:Signature ...> ... </ds:Signature>
</Tag1>

And the signed part of the message has to be:

<Tag1>
    <Tag2>...</Tag2>
</Tag1>

Solution

  • I finally solved my problem. The issue was adding prefix "ds" after signing process which was breaking reference digest value. It terns out that this prefix is not required. I'm posting working code below.

    This is overridden class of SignedXml which I need to access XAdES QualifyingProperties, that are stored in DataObject instead of the actual message.

    public class XadesSignedXml : SignedXml
        {
            #region Public fields
            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 XadesPrefix = "xades";
            public const string XadesNamespaceUrl = "http://uri.etsi.org/01903/v1.3.2#";
            public XmlElement PropertiesNode { get; set; }
            #endregion Public fields
    
            #region Private fields
            private readonly List<DataObject> _dataObjects = new List<DataObject>();
            #endregion Private fields
    
            #region Constructor
            public XadesSignedXml(XmlDocument document) : base(document) { }
            #endregion Constructor
    
            #region SignedXml
            public override XmlElement GetIdElement(XmlDocument document, string idValue)
            {
                if (string.IsNullOrEmpty(idValue))
                    return null;
    
                var xmlElement = base.GetIdElement(document, idValue);
                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);
            }
            #endregion SignedXml
        }
    

    And this is signing method.

    public static class Signature
        {
            #region Private fields
            private const string SignatureId = "Signature";
            private const string SignaturePropertiesId = "SignedProperties";
            #endregion Private fields
    
            #region Public methods
            public static XmlElement SignWithXAdES(X509Certificate2 signingCertificate, XmlDocument xmlDocument)
            {
                var signedXml = new XadesSignedXml(xmlDocument);
                signedXml.Signature.Id = SignatureId;
                signedXml.SigningKey = signingCertificate.PrivateKey;
    
                var signatureReference = new Reference { Uri = "", };
                signatureReference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
                signedXml.AddReference(signatureReference);
    
                var keyInfo = new KeyInfo();
                keyInfo.AddClause(new KeyInfoX509Data(signingCertificate));
                signedXml.KeyInfo = keyInfo;
    
                AddXAdESProperties(xmlDocument, signedXml, signingCertificate);
    
                signedXml.ComputeSignature();
    
                return signedXml.GetXml();
            }
            #endregion Public methods
    
            #region Private methods
            private static void AddXAdESProperties(XmlDocument document, XadesSignedXml xadesSignedXml, X509Certificate2 signingCertificate)
            {
                var parametersSignature = new Reference
                {
                    Uri = $"#{SignaturePropertiesId}",
                    Type = XadesSignedXml.XmlDsigSignatureProperties,
                };
                xadesSignedXml.AddReference(parametersSignature);
    
                // <Object>
                var objectNode = document.CreateElement("Object", SignedXml.XmlDsigNamespaceUrl);
    
                // <Object><QualifyingProperties>
                var qualifyingPropertiesNode = document.CreateElement(XadesSignedXml.XadesPrefix, "QualifyingProperties", XadesSignedXml.XadesNamespaceUrl);
                qualifyingPropertiesNode.SetAttribute("Target", $"#{SignatureId}");
                objectNode.AppendChild(qualifyingPropertiesNode);
    
                // <Object><QualifyingProperties><SignedProperties>
                var signedPropertiesNode = document.CreateElement(XadesSignedXml.XadesPrefix, "SignedProperties", XadesSignedXml.XadesNamespaceUrl);
                signedPropertiesNode.SetAttribute("Id", SignaturePropertiesId);
                qualifyingPropertiesNode.AppendChild(signedPropertiesNode);
    
                // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties>
                var signedSignaturePropertiesNode = document.CreateElement(XadesSignedXml.XadesPrefix, "SignedSignatureProperties", XadesSignedXml.XadesNamespaceUrl);
                signedPropertiesNode.AppendChild(signedSignaturePropertiesNode);
    
                // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties> </SigningTime>
                var signingTime = document.CreateElement(XadesSignedXml.XadesPrefix, "SigningTime", XadesSignedXml.XadesNamespaceUrl);
                signingTime.InnerText = $"{DateTime.UtcNow.ToString("s")}Z";
                signedSignaturePropertiesNode.AppendChild(signingTime);
    
                // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate>
                var signingCertificateNode = document.CreateElement(XadesSignedXml.XadesPrefix, "SigningCertificate", XadesSignedXml.XadesNamespaceUrl);
                signedSignaturePropertiesNode.AppendChild(signingCertificateNode);
    
                // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert>
                var certNode = document.CreateElement(XadesSignedXml.XadesPrefix, "Cert", XadesSignedXml.XadesNamespaceUrl);
                signingCertificateNode.AppendChild(certNode);
    
                // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest>
                var certDigestNode = document.CreateElement(XadesSignedXml.XadesPrefix, "CertDigest", XadesSignedXml.XadesNamespaceUrl);
                certNode.AppendChild(certDigestNode);
    
                // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest> </DigestMethod>
                var digestMethod = document.CreateElement("DigestMethod", SignedXml.XmlDsigNamespaceUrl);
                var digestMethodAlgorithmAtribute = document.CreateAttribute("Algorithm");
                digestMethodAlgorithmAtribute.InnerText = SignedXml.XmlDsigSHA1Url;
                digestMethod.Attributes.Append(digestMethodAlgorithmAtribute);
                certDigestNode.AppendChild(digestMethod);
    
                // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest> </DigestMethod>
                var digestValue = document.CreateElement("DigestValue", SignedXml.XmlDsigNamespaceUrl);
                digestValue.InnerText = Convert.ToBase64String(signingCertificate.GetCertHash());
                certDigestNode.AppendChild(digestValue);
    
                // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial>
                var issuerSerialNode = document.CreateElement(XadesSignedXml.XadesPrefix, "IssuerSerial", XadesSignedXml.XadesNamespaceUrl);
                certNode.AppendChild(issuerSerialNode);
    
                // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial> </X509IssuerName>
                var x509IssuerName = document.CreateElement("X509IssuerName", SignedXml.XmlDsigNamespaceUrl);
                x509IssuerName.InnerText = signingCertificate.Issuer;
                issuerSerialNode.AppendChild(x509IssuerName);
    
                // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial> </X509SerialNumber>
                var x509SerialNumber = document.CreateElement("X509SerialNumber", SignedXml.XmlDsigNamespaceUrl);
                x509SerialNumber.InnerText = ToDecimalString(signingCertificate.SerialNumber);
                issuerSerialNode.AppendChild(x509SerialNumber);
    
                // <Object><QualifyingProperties><SignedProperties><SignedDataObjectProperties>
                var signedDataObjectPropertiesNode = document.CreateElement(XadesSignedXml.XadesPrefix, "SignedDataObjectProperties", XadesSignedXml.XadesNamespaceUrl);
                signedPropertiesNode.AppendChild(signedDataObjectPropertiesNode);
    
                // <Object><QualifyingProperties><SignedProperties><SignedDataObjectProperties><CommitmentTypeIndication>
                var commitmentTypeIndicationNode = document.CreateElement(XadesSignedXml.XadesPrefix, "CommitmentTypeIndication", XadesSignedXml.XadesNamespaceUrl);
                signedDataObjectPropertiesNode.AppendChild(commitmentTypeIndicationNode);
    
                // <Object><QualifyingProperties><SignedProperties><SignedDataObjectProperties><CommitmentTypeIndication><CommitmentTypeId>
                var commitmentTypeIdNode = document.CreateElement(XadesSignedXml.XadesPrefix, "CommitmentTypeId", XadesSignedXml.XadesNamespaceUrl);
                commitmentTypeIndicationNode.AppendChild(commitmentTypeIdNode);
    
                // <Object><QualifyingProperties><SignedProperties><SignedDataObjectProperties><CommitmentTypeIndication><CommitmentTypeId><Identifier>
                var identifierNode = document.CreateElement(XadesSignedXml.XadesPrefix, "Identifier", XadesSignedXml.XadesNamespaceUrl);
                identifierNode.InnerText = XadesSignedXml.XadesProofOfApproval;
                commitmentTypeIdNode.AppendChild(identifierNode);
    
                // <Object><QualifyingProperties><SignedProperties><SignedDataObjectProperties><CommitmentTypeIndication><AllSignedDataObjects>
                var allSignedDataObjectsNode = document.CreateElement(XadesSignedXml.XadesPrefix, "AllSignedDataObjects", XadesSignedXml.XadesNamespaceUrl);
                commitmentTypeIndicationNode.AppendChild(allSignedDataObjectsNode);
    
                var dataObject = new DataObject();
                dataObject.Data = qualifyingPropertiesNode.SelectNodes(".");
                xadesSignedXml.AddObject(dataObject);
            }
    
            private static string ToDecimalString(string serialNumber)
            {
                BigInteger bi;
    
                if (BigInteger.TryParse(serialNumber, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out bi))
                {
                    return bi.ToString(CultureInfo.InvariantCulture);
                }
                else
                {
                    return serialNumber;
                }
            }
            #endregion Private methods
        }