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
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.I have a similar problem. I get this exception:
It doesnt help if I try to use this class:
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.