Skip to content

Verify XML with expired certificate #284

@davisylvestre

Description

@davisylvestre

Hi,

I'm having problems using an expired certificate to validate an XML document.
I was able to avoid date issues by providing a specific time and resolving the date issue based on issue #221.
But I've run into a new issue.

From the code snippet below, the message “candidates exhausted: all candidates exhausted with no interior errors” is displayed.

verified_data = verifier.verify_with_verification_time(
    root,
    verification_time=cert.not_valid_before_utc, # hack
    expect_config=expect_config,
        )

Although I attempted to provide the public certificate that was included in the XML, I did not receive an error message.

verified_data = verifier.verify_with_verification_time(
    root,
    verification_time=cert.not_valid_before_utc, # hack
    expect_config=expect_config,
    x509_cert=cert, # Public Certificate
        )

Full code

import base64
from collections.abc import Callable
from datetime import datetime
from typing import Any, TypedDict

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from lxml import etree
from signxml import (
    CanonicalizationMethod,
    DigestAlgorithm,
    SignatureConfiguration,
    SignatureMethod,
    VerifyResult,
    XMLVerifier,
)
from signxml.util import X509CertChainVerifier


class Output(TypedDict):
    status: bool
    message: str
    data: Any | None
    type_error: str | None


class XMLVerifierWithVerificationTime(XMLVerifier):
    # Using a hack to verify a certificate that has expired
    def get_cert_chain_verifier(self, ca_pem_file):
        return X509CertChainVerifier(
            ca_pem_file=ca_pem_file, verification_time=self._verification_time
        )

    # Adding a new method to accept verification_time parameter
    def verify_with_verification_time(
        self,
        data,
        *,
        x509_cert: str | x509.Certificate | None = None,
        cert_subject_name: str | None = None,
        cert_resolver: Callable | None = None,
        ca_pem_file: str | bytes | None = None,
        hmac_key: bytes | None = None,
        validate_schema: bool = True,
        parser=None,
        uri_resolver: Callable | None = None,
        id_attribute: str | None = None,
        expect_config: SignatureConfiguration = SignatureConfiguration(),
        verification_time: datetime | None = None,
        **deprecated_kwargs,
    ) -> VerifyResult | list[VerifyResult]:
        self._verification_time = verification_time
        # Call the original verify method
        return self.verify(
            data=data,
            x509_cert=x509_cert,
            cert_subject_name=cert_subject_name,
            cert_resolver=cert_resolver,
            ca_pem_file=ca_pem_file,
            hmac_key=hmac_key,
            validate_schema=validate_schema,
            parser=parser,
            uri_resolver=uri_resolver,
            id_attribute=id_attribute,
            expect_config=expect_config,
            **deprecated_kwargs,
        )


verifier = XMLVerifierWithVerificationTime()
expect_config = SignatureConfiguration(
    signature_methods=frozenset({SignatureMethod.DSA_SHA1, SignatureMethod.RSA_SHA1}),
    digest_algorithms=frozenset({DigestAlgorithm.SHA1}),
    default_reference_c14n_method=CanonicalizationMethod.CANONICAL_XML_1_0,
)

ns = {
    "nfe": "http://www.portalfiscal.inf.br/nfe",
    "ds": "http://www.w3.org/2000/09/xmldsig#",
}


def validar_assinatura_nfe(caminho_xml) -> Output:
    parser = etree.XMLParser(remove_blank_text=False, huge_tree=True)
    tree = etree.parse(caminho_xml, parser)
    root = tree.getroot()

    signature_node = root.find(".//ds:Signature", namespaces=ns)
    if signature_node is None:
        return {
            "status": False,
            "message": "Erro: Digital signatures are not present in XML.",
            "data": None,
            "type_error": None,
        }

    nfe_node = root.find(".//nfe:NFe", namespaces=ns)
    if nfe_node is not None:
        root = nfe_node
    try:
        cert_element = root.find(".//ds:X509Certificate", namespaces=ns)
        if cert_element is None:
            raise Exception("The certificate could not be found in the XML.")
        cert_bytes = base64.b64decode(cert_element.text)
        cert = x509.load_der_x509_certificate(cert_bytes, default_backend())

        # Validation
        verified_data = verifier.verify_with_verification_time(
            root,
            verification_time=cert.not_valid_before_utc,
            expect_config=expect_config,
            x509_cert=cert,
        )
        return {
            "status": True,
            "message": "Valid Signature",
            "data": verified_data,
            "type_error": None,
        }
    except Exception as error:
        return {
            "status": False,
            "message": f"Oh no: {str(error)}",
            "data": None,
            "type_error": str(type(error)),
        }

if __name__ == "__main__":
    from pprint import pprint

    filename = "tecnospeed.xml"
    pprint(validar_assinatura_nfe(filename))

Data
Operating system: Windows
SignXML version: 4.2
XML: The public can access it at https://validador.nfe.tecnospeed.com.br/

<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe Id="NFe41180508187168000160550010020000001020000009" versao="4.00"><ide><cUF>41</cUF><cNF>02000000</cNF><natOp>VENDA DE MERCADORIA ADQ. DE TERCEIRO - PF E PJ NAO CONTRIBUI</natOp><mod>55</mod><serie>1</serie><nNF>2000000</nNF><dhEmi>2018-05-28T17:00:00-03:00</dhEmi><dhSaiEnt>2018-05-28T17:00:00-03:00</dhSaiEnt><tpNF>1</tpNF><idDest>1</idDest><cMunFG>4115200</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>9</cDV><tpAmb>2</tpAmb><finNFe>1</finNFe><indFinal>1</indFinal><indPres>1</indPres><procEmi>0</procEmi><verProc>TecnoERP - 1.2.3</verProc></ide><emit><CNPJ>08187168000160</CNPJ><xNome>TECNOSPEED &amp; TECNOLOGIA</xNome><xFant>TECNOSPEED &amp; TECNOLOGIA</xFant><enderEmit><xLgr>AVENIDA DUQUE DE CAXIAS</xLgr><nro>882</nro><xBairro>ZONA 01</xBairro><cMun>4115200</cMun><xMun>MARINGA</xMun><UF>PR</UF><CEP>87020025</CEP><cPais>1058</cPais><xPais>BRASIL</xPais><fone>4430379500</fone></enderEmit><IE>9044016688</IE><CRT>3</CRT></emit><dest><CNPJ>08187168000160</CNPJ><xNome>NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL</xNome><enderDest><xLgr>AVENIDA DUQUE DE CAXIAS</xLgr><nro>882</nro><xBairro>ZONA 01</xBairro><cMun>4115200</cMun><xMun>MARINGA</xMun><UF>PR</UF><CEP>87020025</CEP><cPais>1058</cPais><xPais>BRASIL</xPais><fone>4430379500</fone></enderDest><indIEDest>1</indIEDest><IE>9044016688</IE></dest><det nItem="1"><prod><cProd>0999</cProd><cEAN/><xProd>AMIDO DE MILHO</xProd><NCM>11081200</NCM><CEST>0123456</CEST><indEscala>S</indEscala><CFOP>5102</CFOP><uCom>CX</uCom><qCom>1</qCom><vUnCom>0.0100</vUnCom><vProd>0.01</vProd><cEANTrib/><uTrib>CX</uTrib><qTrib>1</qTrib><vUnTrib>0.0100</vUnTrib><indTot>1</indTot></prod><imposto><ICMS><ICMS00><orig>0</orig><CST>00</CST><modBC>0</modBC><vBC>0.01</vBC><pICMS>12.00</pICMS><vICMS>0.01</vICMS></ICMS00></ICMS><PIS><PISAliq><CST>01</CST><vBC>0.01</vBC><pPIS>1.65</pPIS><vPIS>0.00</vPIS></PISAliq></PIS><COFINS><COFINSAliq><CST>01</CST><vBC>0.01</vBC><pCOFINS>7.60</pCOFINS><vCOFINS>0.01</vCOFINS></COFINSAliq></COFINS></imposto></det><total><ICMSTot><vBC>0.01</vBC><vICMS>0.01</vICMS><vICMSDeson>0.00</vICMSDeson><vFCP>0.00</vFCP><vBCST>0.00</vBCST><vST>0.00</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>0.01</vProd><vFrete>0.00</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>0.00</vPIS><vCOFINS>0.01</vCOFINS><vOutro>0.00</vOutro><vNF>0.01</vNF></ICMSTot></total><transp><modFrete>0</modFrete></transp><cobr><fat><nFat>123</nFat><vOrig>0.01</vOrig><vLiq>0.01</vLiq></fat></cobr><pag><detPag><tPag>15</tPag><vPag>0.01</vPag></detPag></pag></infNFe><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#NFe41180508187168000160550010020000001020000009"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>QptSZtOaO3h0wlX5oRCZwDraOCo=</DigestValue></Reference></SignedInfo><SignatureValue>gMr87sT9Ru5MLGTYex6Bzqa1DDW1lwghCJQ440adN0BLGl/eY823QG0GWzHc8j4dyBmtYBt1Dt4HKCj1BFn9NPE0zWh4Jd8Nvo2WrVR2dq0BOct9FjkZuRK42kgZbFl5Oz1a0ROb52nhxIAwkJyRwkXzo/XmdleRML2x6X+PPCf3oZVo0GSr54jSU7M+yHQb0CESalpqCgzPw6bLYscD5gdy7dPyZmIxykOCvud3ChkuB7glwLcpq7cT7r8jb7i8WVz098TWyoMUQfapXRu8/TGmkHjV0JBWfZbzSgVYe5al8/LLUu2J35nggcFMhKbhHMq5jstTCKhq9RgjMGCv4Q==</SignatureValue><KeyInfo><X509Data><X509Certificate>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</X509Certificate></X509Data></KeyInfo></Signature></NFe>

Please help me figure out how to solve this problem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions