Malformed reference element signing a xml file of

2019-08-02 06:59发布

I'm developing a program to digitally sign invoices in xml. I followed this guide https://www.profissionaisti.com.br/2010/07/assinando-digitalmente-um-xml-usando-c/#comment-197297. However, i'm getting an error Malformed reference element. The code is :

static void Main(string[] args)
    {
        //open certificates of current user
        var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly);

        //Open screen to choose certificate
        var selectedCertificate = X509Certificate2UI.SelectFromCollection(
            store.Certificates,
            "Title",
            "MSG",
            X509SelectionFlag.SingleSelection);

        //Gets the x509 object of the selected certificate
        foreach (X509Certificate2 x509 in selectedCertificate)
        {
            try
            {

                //==============================
                // Start reading xml files
                //==============================
                var txtFiles = Directory.EnumerateFiles("./", "*.xml");

                foreach (string currentFile in txtFiles)
                {

                    Console.WriteLine("Reading file " + currentFile + ":");
                    var originalDoc = XDocument.Load(currentFile);
                    XmlDocument doc = DocumentExtensions.ToXmlDocument(originalDoc);

                    //==============================
                    // Start reading bills
                    //==============================

Get the xml node InfRps, which is a representation of the bill in xml:

                    XmlNodeList ListInfRps = doc.GetElementsByTagName("InfRps");

                    int NodeCounter = 1;

                    foreach (XmlElement InfRps in ListInfRps)
                    {

There is a class on namespace System.Security.Cryptography.Xml of the .NET framework called SignedXml, that implements the W3C standard for signature of documents and verification of signed documents. The code below initiate this class.

                        string id = InfRps.Attributes.GetNamedItem("Id").Value;
                        SignedXml signedXml = new SignedXml(InfRps);
                        signedXml.SigningKey = x509.PrivateKey;

Acording to the IRS, the XML must be put in canonical form before processing. The class Reference takes care of this part of the process, including the identification of the node infNFE and the demanded transformation:

                        // Transformation for DigestValue
                        Reference reference = new Reference("#" + id);
                        //Reference reference = new Reference("#" + "lote");

                        reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
                        reference.AddTransform(new XmlDsigC14NTransform());
                        signedXml.AddReference(reference);

Before computing the signature, we must configure the treatment of the information of the digital certificate used. Based of this data the IRS is able to validate the signature and be certain that no information was modified after the sender of the bill signed it. We must include a clause with the data of the certificate.

                        KeyInfo keyInfo = new KeyInfo();
                        keyInfo.AddClause(new KeyInfoX509Data(x509));
                        signedXml.KeyInfo = keyInfo;

Now, we should compute the signature: And here is the error.

                        signedXml.ComputeSignature();

If computing the signature works, than we can create the element signature on the xml:

                        XmlElement xmlSignature = doc.CreateElement("Signature", "http://www.w3.org/2000/09/xmldsig#");

                        XmlAttribute attr = doc.CreateAttribute("Id");
                        attr.Value = "Ass_" + id;

                        //Add the attribute to the node     
                        xmlSignature.Attributes.SetNamedItem(attr);

                        XmlElement xmlSignedInfo = signedXml.SignedInfo.GetXml();
                        XmlElement xmlKeyInfo = signedXml.KeyInfo.GetXml();

                        XmlElement xmlSignatureValue = doc.CreateElement("SignatureValue", xmlSignature.NamespaceURI);
                        string signBase64 = Convert.ToBase64String(signedXml.Signature.SignatureValue);
                        XmlText text = doc.CreateTextNode(signBase64);
                        xmlSignatureValue.AppendChild(text);

                        xmlSignature.AppendChild(doc.ImportNode(xmlSignedInfo, true));
                        xmlSignature.AppendChild(xmlSignatureValue);
                        xmlSignature.AppendChild(doc.ImportNode(xmlKeyInfo, true));

                        XmlNodeList ListRps = doc.GetElementsByTagName("Rps");

                        int RpsCounter = 1;

                        foreach (XmlElement Rps in ListRps)
                        {

                            if (RpsCounter == NodeCounter)
                            {
                                Rps.AppendChild(xmlSignature);
                            }
                            RpsCounter++;
                        }

                        Console.WriteLine("Ok");
                        NodeCounter++;
                    }
                    (...)

I get CryptographicException: Malformed reference element:

System.Security.Cryptography.CryptographicException: Malformed reference element.
at System.Security.Cryptography.Xml.Reference.CalculateHashValue(XmlDocument
document, CanonicalXmlNodeList refList)
at System.Security.Cryptography.Xml.SignedXml.BuildDigestedReferences()
at System.Security.Cryptography.Xml.SignedXml.ComputeSignature()
at escolhercertificadosimples.Program.Main(String[] args) in
C:\Users\user\Do
cuments\Visual Studio
2015\Projects\assinaturalote\assinaturalote\Program.cs:line 143
Pressione qualquer tecla para continuar. . .

An example of xml to sign is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<EnviarLoteRpsEnvio xmlns="http://www.abrasf.org.br/nfse.xsd">
    <LoteRps Id="lote" versao="1.00">
        <NumeroLote>8</NumeroLote>
        <Cnpj>09419261123115</Cnpj>
        <InscricaoMunicipal>51624621</InscricaoMunicipal>
        <QuantidadeRps>1</QuantidadeRps>
        <ListaRps>
            <Rps xmlns="http://www.abrasf.org.br/nfse.xsd">
                <InfRps Id="rps:8201603150148">
                    <IdentificacaoRps>
                        <Numero>8201613150148</Numero>
                        <Serie>248</Serie>
                        <Tipo>2</Tipo>
                    </IdentificacaoRps>
                    <DataEmissao>2016-03-15T18:18:39</DataEmissao>
                    <NaturezaOperacao>1</NaturezaOperacao>
                    <OptanteSimplesNacional>2</OptanteSimplesNacional>
                    <IncentivadorCultural>2</IncentivadorCultural>
                    <Status>1</Status>
                    <Servico>
                        <Valores>
                            <ValorServicos>20.00</ValorServicos>
                            <ValorDeducoes>0.00</ValorDeducoes>
                            <ValorPis>1.60</ValorPis>
                            <ValorCofins>2.00</ValorCofins>
                            <ValorInss>0.00</ValorInss>
                            <ValorIr>3.00</ValorIr>
                            <ValorCsll>2.00</ValorCsll>
                            <IssRetido>1</IssRetido>
                            <OutrasRetencoes>0.00</OutrasRetencoes>
                            <DescontoIncondicionado>0.00</DescontoIncondicionado>
                            <DescontoCondicionado>0.00</DescontoCondicionado>
                        </Valores>
                        <ItemListaServico>1.07</ItemListaServico>
                        <CodigoTributacaoMunicipio>10700100</CodigoTributacaoMunicipio>
                        <Discriminacao>test.</Discriminacao>
                        <CodigoMunicipio>4314902</CodigoMunicipio>
                    </Servico>
                    <Prestador>
                        <Cnpj>09419261000115</Cnpj>
                        <InscricaoMunicipal>51624621</InscricaoMunicipal>
                    </Prestador>
                    <Tomador>
                        <IdentificacaoTomador>
                            <CpfCnpj>
                                <Cnpj>14525684000150</Cnpj>
                            </CpfCnpj>
                        </IdentificacaoTomador>
                        <RazaoSocial>test S.A.</RazaoSocial>
                        <Endereco>
                            <Endereco>Rua test</Endereco>
                            <Numero>83</Numero>
                            <Complemento>Sala test</Complemento>
                            <Bairro>Centro</Bairro>
                            <CodigoMunicipio>3304557</CodigoMunicipio>
                            <Uf>RJ</Uf>
                            <Cep>20091007</Cep>
                        </Endereco>
                        <Contato>
                            <Telefone>2136261100</Telefone>
                            <Email>test@test.com.br</Email>
                        </Contato>
                    </Tomador>
                </InfRps>
            </Rps>
        </ListaRps>
    </LoteRps>
</EnviarLoteRpsEnvio>

Anybody has any idea? Any idea would be apreciated

2条回答
ら.Afraid
2楼-- · 2019-08-02 07:17

You have a colon in your Id value (rps:8201603150148), which is illegal for an identifier attribute. (Per https://www.w3.org/TR/xml-id/#processing, The normalized value of the attribute is an NCName..., where the "NC" part of "NCName" is "no-colon")

Since there's a colon in the Id value SignedXml won't resolve it, so it says your reference points to nowhere.

If you are writing new code and have no requirement on your (technically malformed) identifier attribute values, the best answer is to get rid of the colons (underscore usually serves well in this role).

Since conforming to the xml:id constraints makes for the most interoperable document, that's definitely the best answer.

The next best answer is to extend the SignedXml class and override GetIdElement. You should make your match logic as strict as possible. Note that this logic has to be performed on both the signer and reciever... and is only valid as long as both sides have the ability to accept loose conformance documents.

internal class RpsSignedXml : SignedXml
{
    // ctors and other members as appropriate

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

        if (!idValue.StartsWith("rps:"))
            return base.GetIdElement(document, idValue);

        string xPath = $"//InfRps[@Id=\"{idValue}\"]";
        XmlNodeList nodeList = document.SelectNodes(xPath);

        if (nodeList == null || nodeList.Count != 1)
            return null;

        return nodeList[0] as XmlElement;
    }
}
查看更多
老娘就宠你
3楼-- · 2019-08-02 07:27

I have a similar problem. I get this exception:

Unable to resolve Uri cid:Part-0986dfc9-2748-41a4-8624-e505e98b29be.
System.Security
   at System.Security.Cryptography.Xml.Reference.CalculateHashValue(XmlDocument document, CanonicalXmlNodeList refList)
   at System.Security.Cryptography.Xml.SignedXml.BuildDigestedReferences()
   at System.Security.Cryptography.Xml.SignedXml.ComputeSignature()
   at EmpTestWeb.BL.ebXMLSigner.ComputeSig(X509Certificate2 cert, XmlDocument doc, String externalReferenceUri) in C:\Websites\TestWeb\BL\ebXMLSigner.cs:line 67
   at ASP.ebxml_aspx.GenerateHeaderXML() in c:\Websites\TestWeb\ebXML.aspx:line 227
   at ASP.ebxml_aspx.btnSubmit_Click(Object sender, EventArgs e) in c:\Websites\TestWeb\ebXML.aspx:line 89

It doesnt help if I try to use this class:

class ebXMLSignedXml : SignedXml
{
    // ctors and other members as appropriate
    public ebXMLSignedXml(XmlDocument doc) : base(doc) { }

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

        if (!idValue.StartsWith("cid:"))
            return base.GetIdElement(document, idValue);

        string xPath = $"//InfRps[@Id=\"{idValue}\"]";
        XmlNodeList nodeList = document.SelectNodes(xPath);

        if (nodeList == null || nodeList.Count != 1)
            return null;

        return nodeList[0] as XmlElement;
    }
}

it never reaches where it should select the element with Id="cid:...."

SOLUTION

I ended up passing a stream of the content instead of the URI instead and added the URI="cpa:13131312" attribute manually to the generated XML.

查看更多
登录 后发表回答