facho: clip.py se adiciona nuevo comand 'sign-xml'.
* facho/cli.py: adiciona nuevo comando 'sign-xml' para firmar directamente un xml. * facho/fe/fe.py (DianXMLExtensionSigner.sign_xml_string): Nuevo metodo. FossilOrigin-Name: 61920c40da14a134de6392845b3e4d98ad2b1b683093038d6161c147669127e9
This commit is contained in:
parent
643191a615
commit
153d577100
39
facho/cli.py
39
facho/cli.py
@ -29,6 +29,15 @@ logging.config.dictConfig({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def disable_ssl():
|
||||||
|
# MACHETE
|
||||||
|
import ssl
|
||||||
|
if getattr(ssl, '_create_unverified_context', None):
|
||||||
|
ssl._create_default_https_context = ssl._create_unverified_context
|
||||||
|
warnings.warn("be sure!! ssl disable")
|
||||||
|
else:
|
||||||
|
warnings.warn("can't disable ssl")
|
||||||
|
|
||||||
|
|
||||||
# MACHETE se corrige
|
# MACHETE se corrige
|
||||||
# lxml.etree.DocumentInvalid: Element '{http://www.w3.org/2000/09/xmldsig#}X509SerialNumber': '34255301462796514282327995225552892834' is not a valid value of the atomic type 'xs:integer'.
|
# lxml.etree.DocumentInvalid: Element '{http://www.w3.org/2000/09/xmldsig#}X509SerialNumber': '34255301462796514282327995225552892834' is not a valid value of the atomic type 'xs:integer'.
|
||||||
@ -200,6 +209,20 @@ def validate_invoice(invoice_path):
|
|||||||
XSD.validate(content, XSD.UBLInvoice)
|
XSD.validate(content, XSD.UBLInvoice)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option('--private-key', type=click.Path(exists=True))
|
||||||
|
@click.option('--passphrase')
|
||||||
|
@click.option('--ssl/--no-ssl', default=False)
|
||||||
|
@click.argument('xmlfile', type=click.Path(exists=True), required=True)
|
||||||
|
def sign_xml(private_key, passphrase, xmlfile, ssl=True):
|
||||||
|
if not ssl:
|
||||||
|
disable_ssl()
|
||||||
|
|
||||||
|
from facho import fe
|
||||||
|
signer = fe.DianXMLExtensionSigner(private_key, passphrase=passphrase)
|
||||||
|
document = open(xmlfile, 'r').read().encode('utf-8')
|
||||||
|
print(signer.sign_xml_string(document))
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option('--private-key', type=click.Path(exists=True))
|
@click.option('--private-key', type=click.Path(exists=True))
|
||||||
@click.option('--generate/--validate', default=False)
|
@click.option('--generate/--validate', default=False)
|
||||||
@ -214,15 +237,9 @@ def generate_invoice(private_key, passphrase, scriptname, generate=False, ssl=Tr
|
|||||||
def extensions(form.Invoice): -> List[facho.FachoXMLExtension]
|
def extensions(form.Invoice): -> List[facho.FachoXMLExtension]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# MACHETE
|
|
||||||
if not ssl:
|
if not ssl:
|
||||||
import ssl
|
disable_ssl()
|
||||||
if getattr(ssl, '_create_unverified_context', None):
|
|
||||||
ssl._create_default_https_context = ssl._create_unverified_context
|
|
||||||
warnings.warn("be sure!! ssl disable")
|
|
||||||
else:
|
|
||||||
warnings.warn("can't disable ssl")
|
|
||||||
|
|
||||||
import importlib.util
|
import importlib.util
|
||||||
|
|
||||||
spec = importlib.util.spec_from_file_location('invoice', scriptname)
|
spec = importlib.util.spec_from_file_location('invoice', scriptname)
|
||||||
@ -245,11 +262,6 @@ def generate_invoice(private_key, passphrase, scriptname, generate=False, ssl=Tr
|
|||||||
extensions = module.extensions(invoice)
|
extensions = module.extensions(invoice)
|
||||||
for extension in extensions:
|
for extension in extensions:
|
||||||
xml.add_extension(extension)
|
xml.add_extension(extension)
|
||||||
|
|
||||||
if private_key:
|
|
||||||
signer = fe.DianXMLExtensionSigner(private_key, passphrase=passphrase)
|
|
||||||
xml.add_extension(signer)
|
|
||||||
|
|
||||||
|
|
||||||
print(xml.tostring(xml_declaration=True))
|
print(xml.tostring(xml_declaration=True))
|
||||||
|
|
||||||
@ -267,3 +279,4 @@ main.add_command(soap_get_status_zip)
|
|||||||
main.add_command(soap_get_numbering_range)
|
main.add_command(soap_get_numbering_range)
|
||||||
main.add_command(generate_invoice)
|
main.add_command(generate_invoice)
|
||||||
main.add_command(validate_invoice)
|
main.add_command(validate_invoice)
|
||||||
|
main.add_command(sign_xml)
|
||||||
|
@ -98,6 +98,7 @@ class LXMLBuilder:
|
|||||||
def set_attribute(self, elem, key, value):
|
def set_attribute(self, elem, key, value):
|
||||||
elem.attrib[key] = value
|
elem.attrib[key] = value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def tostring(self, elem, **attrs):
|
def tostring(self, elem, **attrs):
|
||||||
attrs['pretty_print'] = attrs.pop('pretty_print', False)
|
attrs['pretty_print'] = attrs.pop('pretty_print', False)
|
||||||
attrs['encoding'] = attrs.pop('encoding', 'UTF-8')
|
attrs['encoding'] = attrs.pop('encoding', 'UTF-8')
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# This file is part of facho. The COPYRIGHT file at the top level of
|
# This file is part of facho. The COPYRIGHT file at the top level of
|
||||||
# this repository contains the full copyright notices and license terms.
|
# this repository contains the full copyright notices and license terms.
|
||||||
|
|
||||||
from ..facho import FachoXML, FachoXMLExtension
|
from ..facho import FachoXML, FachoXMLExtension, LXMLBuilder
|
||||||
import xmlsig
|
import xmlsig
|
||||||
import xades
|
import xades
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -187,8 +187,9 @@ class DianXMLExtensionSigner(FachoXMLExtension):
|
|||||||
def from_pkcs12(self, filepath, password=None):
|
def from_pkcs12(self, filepath, password=None):
|
||||||
p12 = OpenSSL.crypto.load_pkcs12(open(filepath, 'rb').read(), password)
|
p12 = OpenSSL.crypto.load_pkcs12(open(filepath, 'rb').read(), password)
|
||||||
|
|
||||||
# return (xpath, xml.Element)
|
def sign_xml_string(self, document):
|
||||||
def build(self, fachoxml):
|
xml = LXMLBuilder.from_string(document)
|
||||||
|
|
||||||
signature = xmlsig.template.create(
|
signature = xmlsig.template.create(
|
||||||
xmlsig.constants.TransformInclC14N,
|
xmlsig.constants.TransformInclC14N,
|
||||||
xmlsig.constants.TransformRsaSha256,
|
xmlsig.constants.TransformRsaSha256,
|
||||||
@ -217,31 +218,8 @@ class DianXMLExtensionSigner(FachoXMLExtension):
|
|||||||
# TODO assert with http://www.sic.gov.co/hora-legal-colombiana
|
# TODO assert with http://www.sic.gov.co/hora-legal-colombiana
|
||||||
props = xades.template.create_signed_properties(qualifying, datetime=datetime.now())
|
props = xades.template.create_signed_properties(qualifying, datetime=datetime.now())
|
||||||
xades.template.add_claimed_role(props, "supplier")
|
xades.template.add_claimed_role(props, "supplier")
|
||||||
#signed_do = xades.template.ensure_signed_data_object_properties(props)
|
|
||||||
#xades.template.add_data_object_format(
|
|
||||||
# signed_do, "#R1",
|
|
||||||
# identifier=xades.ObjectIdentifier("Idenfitier0", "Description")
|
|
||||||
#)
|
|
||||||
#xades.template.add_commitment_type_indication(
|
|
||||||
# signed_do,
|
|
||||||
# xades.ObjectIdentifier("Idenfitier0", "Description"),
|
|
||||||
# qualifiers_type=["Tipo"],
|
|
||||||
#)
|
|
||||||
|
|
||||||
#xades.template.add_commitment_type_indication(
|
xml.append(signature)
|
||||||
# signed_do,
|
|
||||||
# xades.ObjectIdentifier("Idenfitier1", references=["#R1"]),
|
|
||||||
# references=["#R1"],
|
|
||||||
#)
|
|
||||||
#xades.template.add_data_object_format(
|
|
||||||
# signed_do,
|
|
||||||
# "#RKI",
|
|
||||||
# description="Desc",
|
|
||||||
# mime_type="application/xml",
|
|
||||||
# encoding="UTF-8",
|
|
||||||
#)
|
|
||||||
|
|
||||||
fachoxml.root.append(signature)
|
|
||||||
|
|
||||||
policy = xades.policy.GenericPolicyId(
|
policy = xades.policy.GenericPolicyId(
|
||||||
self.POLICY_ID,
|
self.POLICY_ID,
|
||||||
@ -254,12 +232,20 @@ class DianXMLExtensionSigner(FachoXMLExtension):
|
|||||||
ctx.sign(signature)
|
ctx.sign(signature)
|
||||||
ctx.verify(signature)
|
ctx.verify(signature)
|
||||||
#xmlsig take parent root
|
#xmlsig take parent root
|
||||||
fachoxml.root.remove(signature)
|
xml.remove(signature)
|
||||||
|
|
||||||
|
fachoxml = FachoXML(xml,nsmap=NAMESPACES)
|
||||||
ublextension = fachoxml.fragment('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension', append_not_exists=True)
|
ublextension = fachoxml.fragment('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension', append_not_exists=True)
|
||||||
extcontent = ublextension.find_or_create_element('/ext:UBLExtension:/ext:ExtensionContent')
|
extcontent = ublextension.find_or_create_element('/ext:UBLExtension:/ext:ExtensionContent')
|
||||||
fachoxml.append_element(extcontent, signature)
|
fachoxml.append_element(extcontent, signature)
|
||||||
|
return fachoxml.tostring()
|
||||||
|
|
||||||
|
# return (xpath, xml.Element)
|
||||||
|
def build(self, fachoxml):
|
||||||
|
xmlsigned = self.sign_xml_string(fachoxml.tostring())
|
||||||
|
xml = LXMLBuilder.from_string(xmlsigned)
|
||||||
|
fachoxml.root = xml
|
||||||
|
return fachoxml
|
||||||
|
|
||||||
|
|
||||||
class DianXMLExtensionAuthorizationProvider(FachoXMLExtension):
|
class DianXMLExtensionAuthorizationProvider(FachoXMLExtension):
|
||||||
|
@ -94,5 +94,15 @@ def test_dian_invoice_with_fe():
|
|||||||
assert "<Invoice" in xml.tostring()
|
assert "<Invoice" in xml.tostring()
|
||||||
|
|
||||||
|
|
||||||
|
def test_xml_sign_dian(monkeypatch):
|
||||||
|
xml = fe.FeXML('Invoice',
|
||||||
|
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
|
||||||
|
xmlstring = xml.tostring()
|
||||||
|
signer = fe.DianXMLExtensionSigner('./tests/example.p12')
|
||||||
|
|
||||||
|
with monkeypatch.context() as m:
|
||||||
|
helpers.mock_urlopen(m)
|
||||||
|
xmlsigned = signer.sign_xml_string(xmlstring)
|
||||||
|
assert "Signature" in xmlsigned
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user