From 7453fe34e4f69683fee43d31c4d568db40deaf85 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Thu, 17 Oct 2019 11:08:56 -0500 Subject: [PATCH 01/19] Add schemas and readme --- signxml/xades/README.rst | 43 ++ signxml/xades/schemas/v1.1.1/XAdES.xsd | 544 +++++++++++++++++ signxml/xades/schemas/v1.2.2/XAdES.xsd | 552 ++++++++++++++++++ signxml/xades/schemas/v1.3.2/XAdES.xsd | 466 +++++++++++++++ .../schemas/v1.3.2/XAdES01903v132-201506.xsd | 537 +++++++++++++++++ .../schemas/v1.3.2/XAdES01903v132-201601.xsd | 532 +++++++++++++++++ signxml/xades/schemas/v1.4.1/XAdES.xsd | 15 + .../schemas/v1.4.1/XAdES01903v141-201506.xsd | 53 ++ .../schemas/v1.4.1/XAdES01903v141-201601.xsd | 63 ++ 9 files changed, 2805 insertions(+) create mode 100644 signxml/xades/README.rst create mode 100644 signxml/xades/schemas/v1.1.1/XAdES.xsd create mode 100644 signxml/xades/schemas/v1.2.2/XAdES.xsd create mode 100644 signxml/xades/schemas/v1.3.2/XAdES.xsd create mode 100644 signxml/xades/schemas/v1.3.2/XAdES01903v132-201506.xsd create mode 100644 signxml/xades/schemas/v1.3.2/XAdES01903v132-201601.xsd create mode 100644 signxml/xades/schemas/v1.4.1/XAdES.xsd create mode 100644 signxml/xades/schemas/v1.4.1/XAdES01903v141-201506.xsd create mode 100644 signxml/xades/schemas/v1.4.1/XAdES01903v141-201601.xsd diff --git a/signxml/xades/README.rst b/signxml/xades/README.rst new file mode 100644 index 0000000..7955355 --- /dev/null +++ b/signxml/xades/README.rst @@ -0,0 +1,43 @@ +############# +XAdES by ETSI +############# + +`W3C Note: XML Advanced Electronic Signatures (XAdES) `_ is outdated and has aparently been superseeded by standard development by `ETSI `_. + +This implementation is guided by the corresponding ETSI documents wich norm and inform that matter. + +*********************************************** +Electronic Signatures and Infrastructures (ESI) +*********************************************** + +The framework for standardization of signatures +=============================================== +* `ETSI TR 119 000 V1.2.1 (2016-04) | Overview `_ + +XAdES digital signatures +======================== + +* `ETSI EN 319 132-1 V1.1.1 (2016-04) | Part 1: Building blocks and XAdES baseline signatures `_ + +* `ETSI EN 319 132-2 V1.1.1 (2016-04) | Part 2: Extended XAdES signatures `_ + + +Part 1 V1.1.1 (2016-04), in chapter "4.2 XML Namespaces", informs the application of the 201601 releases of the schema files: + +* `XAdES01903v132-201601.xsd `_ +* `XAdES01903v141-201601.xsd `_ + +Moreover, the following definitions in Part 1 V1.1.1 (2016-04), in chapter "3.1 Definitions", shall be highlighted: + +* **legacy XAdES 101 903 signature:** digital signature generated according to ETSI TS 101 903 [i.2] +* **legacy XAdES baseline signature:** digital signature generated according to ETSI TS 103 171 [i.3] +* **legacy XAdES signature:** legacy XAdES 101 903 signature or legacy XAdES baseline signature + +Testing Conformance and Interoperability +---------------------------------------- + +* `ETSI TR 119 134-1 V1.1.1 (2016-06) | Part 1: Overview `_ +* `ETSI TS 119 134-2 V1.1.1 (2016-06) | Part 2: Test suites for testing interoperability of XAdES baseline signatures `_ +* `ETSI TS 119 134-3 V1.1.1 (2016-06) | Part 3: Test suites for testing interoperability of extended XAdES signatures `_ +* `ETSI TS 119 134-4 V1.1.1 (2016-06) | Part 4: Testing Conformance of XAdES baseline signatures `_ +* `ETSI TS 119 134-5 V2.1.1 (2016-06) | Part 5: Testing Conformance of extended XAdES signatures `_ diff --git a/signxml/xades/schemas/v1.1.1/XAdES.xsd b/signxml/xades/schemas/v1.1.1/XAdES.xsd new file mode 100644 index 0000000..41adcfe --- /dev/null +++ b/signxml/xades/schemas/v1.1.1/XAdES.xsd @@ -0,0 +1,544 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/signxml/xades/schemas/v1.2.2/XAdES.xsd b/signxml/xades/schemas/v1.2.2/XAdES.xsd new file mode 100644 index 0000000..27eedac --- /dev/null +++ b/signxml/xades/schemas/v1.2.2/XAdES.xsd @@ -0,0 +1,552 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/signxml/xades/schemas/v1.3.2/XAdES.xsd b/signxml/xades/schemas/v1.3.2/XAdES.xsd new file mode 100644 index 0000000..4b74692 --- /dev/null +++ b/signxml/xades/schemas/v1.3.2/XAdES.xsd @@ -0,0 +1,466 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/signxml/xades/schemas/v1.3.2/XAdES01903v132-201506.xsd b/signxml/xades/schemas/v1.3.2/XAdES01903v132-201506.xsd new file mode 100644 index 0000000..712b88e --- /dev/null +++ b/signxml/xades/schemas/v1.3.2/XAdES01903v132-201506.xsd @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/signxml/xades/schemas/v1.3.2/XAdES01903v132-201601.xsd b/signxml/xades/schemas/v1.3.2/XAdES01903v132-201601.xsd new file mode 100644 index 0000000..12301cf --- /dev/null +++ b/signxml/xades/schemas/v1.3.2/XAdES01903v132-201601.xsd @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/signxml/xades/schemas/v1.4.1/XAdES.xsd b/signxml/xades/schemas/v1.4.1/XAdES.xsd new file mode 100644 index 0000000..8cb5279 --- /dev/null +++ b/signxml/xades/schemas/v1.4.1/XAdES.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/signxml/xades/schemas/v1.4.1/XAdES01903v141-201506.xsd b/signxml/xades/schemas/v1.4.1/XAdES01903v141-201506.xsd new file mode 100644 index 0000000..993f2e0 --- /dev/null +++ b/signxml/xades/schemas/v1.4.1/XAdES01903v141-201506.xsd @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/signxml/xades/schemas/v1.4.1/XAdES01903v141-201601.xsd b/signxml/xades/schemas/v1.4.1/XAdES01903v141-201601.xsd new file mode 100644 index 0000000..ad1c0db --- /dev/null +++ b/signxml/xades/schemas/v1.4.1/XAdES01903v141-201601.xsd @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 958f0d03d24e80c6b692a851a85f772b4975658e Mon Sep 17 00:00:00 2001 From: David Arnold Date: Thu, 17 Oct 2019 19:22:02 -0500 Subject: [PATCH 02/19] WIP: Implement skeleton execution flow - sign (params) vs _sign (option struct) interface - high level element sceleton - legacy mode ETSI TS 101 903 [i.2] - level concept --- signxml/xades/__init__.py | 320 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 signxml/xades/__init__.py diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py new file mode 100644 index 0000000..710ac18 --- /dev/null +++ b/signxml/xades/__init__.py @@ -0,0 +1,320 @@ +from __future__ import absolute_import, division, print_function, unicode_literals + +from base64 import b64encode, b64decode +from enum import Enum +from uuid import uuid4 +from datetime import datetime +import pytz + +from eight import str, bytes +from lxml import etree +from lxml.etree import Element, SubElement +from lxml.builder import ElementMaker +from defusedxml.lxml import fromstring + +from cryptography.hazmat.primitives.asymmetric import rsa, dsa, ec +from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 +from cryptography.hazmat.primitives.hashes import Hash, SHA1, SHA224, SHA256, SHA384, SHA512 +from cryptography.hazmat.backends import default_backend + +from .. import XMLSignatureProcessor, XMLSigner, XMLVerifier, VerifyResult, namespaces + +from ..exceptions import InvalidSignature, InvalidDigest, InvalidInput, InvalidCertificate # noqa +from ..util import (bytes_to_long, long_to_bytes, strip_pem_header, add_pem_header, ensure_bytes, ensure_str, Namespace, + XMLProcessor, iterate_pem, verify_x509_cert_chain) +from collections import namedtuple + +levels = Enum("Levels", "B T LT LTA") + + +# Retrieved on 17. Oct. 2019: https://portal.etsi.org/pnns/uri-list +namespaces.update(Namespace( + # xades111="https://uri.etsi.org/01903/v1.1.1#", # superseeded + # xades122="https://uri.etsi.org/01903/v1.2.2#", # superseeded + xades132="https://uri.etsi.org/01903/v1.3.2#", + xades141="https://uri.etsi.org/01903/v1.4.1#", +)) + +XADES132 = ElementMaker(namespace=namespaces.xades132) +XADES141 = ElementMaker(namespace=namespaces.xades141) +DS = ElementMaker(namespace=namespaces.ds) + +def _gen_id(suffix): + return "{}-{suffix}".format(uuid4(), suffix=suffix) + + + +class XAdESProcessor(XMLSignatureProcessor): + schema_file = "v1.4.1/XAdES01903v141-201601.xsd" + + def _resolve_target(self, doc_root, qualifying_properties): + uri = qualifying_properties.get("Target") + if not uri: + return doc_root + elif uri.startswith("#xpointer("): + raise InvalidInput("XPointer references are not supported") + # doc_root.xpath(uri.lstrip("#"))[0] + elif uri.startswith("#"): + for id_attribute in self.id_attributes: + xpath_query = "//*[@*[local-name() = '{}']=$uri]".format(id_attribute) + results = doc_root.xpath(xpath_query, uri=uri.lstrip("#")) + if len(results) > 1: + raise InvalidInput("Ambiguous reference URI {} resolved to {} nodes".format(uri, len(results))) + elif len(results) == 1: + return results[0] + raise InvalidInput("Unable to resolve reference URI: {}".format(uri)) + + +class XAdESSignerOptions(namedtuple("XAdESSignerOptions", "cert_chain signed_xml signature_xml")): + """ + A containter to hold the XAdES Signer runtime options. It can be provided by + directly calling ``signxml.XAdESSigner._sign()``. + + :param cert_chain: + OpenSSL.crypto.X509 objects containing the certificate and a chain of + intermediate certificates. + :type cert_chain: array of OpenSSL.crypto.X509 objects + """ + + +class XAdESSigner(XAdESProcessor, XMLSigner): + """ + ... + """ + + def __init__(self, level=levels.LTA, legacy=False, tz=pytz.utc): + self.xades_legacy = legacy + self.xades_level = level + self.xades_tz = tz + + def sign(self, *args, **kwargs): + options_struct = XAdESSignerOptions('foo', 'bar', 'baz') + self._sign(options_struct) + return super().sign(*args, **kwargs) + + def _sign(self, options_struct): + ... + + def _add_xades_reference(self, id): + """ + This ds:Reference element shall include the Type attribute with its + value set to: + """ + DS.Reference( + Type="http://uri.etsi.org/01903#SignedProperties" + ) + pass + + def _generate_xades_ssp_elements(self): + """ + Deprecation as listed in ETSI EN 319 132-1 V1.1.1 (2016-04), Annex D + """ + elements = [] + + elements.append(XADES132.SigningTime(datetime.now(self.xades_tz).isoformat())) + + if self.xades_legacy: + elements.append(XADES132.SigningCertificate()) # deprecated (legacy) + else: + elements.append(XADES132.SigningCertificateV2()) + + if self.xades_legacy: + elements.append(XADES132.SignatureProductionPlace()) # deprecated (legacy) + else: + elements.append(XADES132.SignatureProductionPlaceV2()) + + elements.append(XADES132.SignaturePolicyIdentifier()) + + if self.xades_legacy: + elements.append(XADES132.SignerRole()) # deprecated (legacy) + else: + elements.append(XADES132.SignerRoleV2()) + + # any ##other + + return elements + + def _generate_xades_sdop_elements(self): + elements = [] + + elements.append(XADES132.DataObjectFormat()) + + elements.append(XADES132.CommitmentTypeIndication()) + + elements.append(XADES132.AllDataObjectsTimeStamp()) + + elements.append(XADES132.IndividualDataObjectsTimeStamp()) + + # any ##other + + return elements + + def _generate_xades_usp_elements(self): + elements = [] + + elements.append(XADES132.CounterSignature()) + + elements.append(XADES132.SignatureTimeStamp()) + + if self.xades_legacy: + elements.append(XADES132.CompleteCertificateRefs()) # deprecated (legacy) + else: + elements.append(XADES141.CompleteCertificateRefsV2()) + + elements.append(XADES132.CompleteRevocationRefs()) + + if self.xades_legacy: + elements.append(XADES132.AttributeCertificateRefs()) # deprecated (legacy) + else: + elements.append(XADES141.AttributeCertificateRefsV2()) + + elements.append(XADES132.AttributeRevocationRefs()) + + if self.xades_legacy: + elements.append(XADES132.SigAndRefsTimeStamp()) # deprecated (legacy) + else: + elements.append(XADES141.SigAndRefsTimeStampV2()) + + if self.xades_legacy: + elements.append(XADES132.RefsOnlyTimeStamp()) # deprecated (legacy) + else: + elements.append(XADES141.RefsOnlyTimeStampV2()) + + elements.append(XADES132.CertificateValues()) + + elements.append(XADES132.RevocationValues()) + + elements.append(XADES132.AttrAuthoritiesCertValues()) + + elements.append(XADES132.AttributeRevocationValues()) + + elements.append(XADES132.ArchiveTimeStamp()) + + # any ##other + + return elements + + def _generate_xades_udop_elements(self): + elements = [] + + elements.append(XADES132.UnsignedDataObjectProperty()) + + return elements + + def _generate_xades(self): + + """ + Acronyms used in this method + ---------------------------- + Defined by ETSI EN 319 132-1 V1.1.1 (2016-04) + qp := QualifyingProperties, c. 4.3.1 + sp := SignedProperties, c. 4.3.2 + ssp := SignedSignatureProperties, c. 4.3.4 + sdop := SignedDataObjectProperties, c. 4.3.5 + up := UnsignedProperties, c. 4.3.3 + usp := UnignedSignatureProperties, c. 4.3.6 + udop := UnignedDataObjectProperties, c. 4.3.7 + """ + + ssp_elements = self._generate_xades_ssp_elements() + sdop_elements = self._generate_xades_sdop_elements() + + usp_elements = self._generate_xades_usp_elements() + udop_elements = self._generate_xades_udop_elements() + + # Step -3: Construction of SignedProperties + sp_elements = [] + """ + A XAdES signature shall not incorporate empty SignedSignatureProperties element. + """ + if ssp_elements: + """ + The Id attribute shall be used to reference the SignedSignatureProperties element. + """ + sp_elements.append(*ssp_elements, + Id=_gen_id("signedsigprops") # optional + ) + """ + A XAdES signature shall not incorporate empty SignedDataObjectProperties element. + """ + if sdop_elements: + """ + The Id attribute shall be used to reference the SignedDataObjectProperties element. + """ + sp_elements.append(*sdop_elements, + Id=_gen_id("signeddataobjprops") # optional + ) + + # Step -2: Construction of UnsignedProperties + up_elements = [] + """ + A XAdES signature shall not incorporate empty UnignedSignatureProperties element. + """ + if usp_elements: + """ + The Id attribute shall be used to reference the UnignedSignatureProperties element. + """ + sp_elements.append(*usp_elements, + Id=_gen_id("unsignedsigprops") # optional + ) + """ + A XAdES signature shall not incorporate empty UnignedDataObjectProperties element. + """ + if udop_elements: + """ + The Id attribute shall be used to reference the UnignedDataObjectProperties element. + """ + sp_elements.append(*udop_elements, + Id=_gen_id("unsigneddataobjprops") # optional + ) + + # Step -1: Construction of QualifyingProperties + qp_elements = [] + + """ + A XAdES signature shall not incorporate empty SignedProperties element. + """ + if sp_elements: + """ + The Id attribute shall be used to reference the SignedProperties element. + """ + qp_elements.append( + XADES132.SignedProperties(*sp_elements, + Id=_gen_id("signedprops") # optional + ) + ) + """ + In order to protect the qualifying properties with the signature, + a ds:Reference element shall be added to the XML signature. + """ + self._add_xades_reference(sp_attributes.get('Id')) + + """ + A XAdES signature shall not incorporate empty UnsignedProperties elements. + """ + if up_elements: + """ + The Id attribute shall be used to reference the UnsignedProperties element. + """ + qp_elements.append( + XADES132.UnsignedProperties(*up_elements, + Id=_gen_id("unsignedprops") # optional + ) + ) + + """ + The Target attribute shall refer to the Id attribute of the + corresponding ds:Signature. The Id attribute shall be used to reference + the QualifyingProperties container. + """ + qp_attributes = { + "Target": '', # required + "Id": _gen_id("qualifyingprops"), # optional + } + + """ + A XAdES signature shall not incorporate empty QualifyingProperties elements. + """ + if not qp_elements: + return None + return XADES132.QualifyingProperties(*qp_elements, **qp_attributes) From 1dcacd74ad4c64847554d83f9eb47568cd6d567e Mon Sep 17 00:00:00 2001 From: David Arnold Date: Thu, 17 Oct 2019 22:21:59 -0500 Subject: [PATCH 03/19] Add link to conformance testing tools --- signxml/xades/README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/signxml/xades/README.rst b/signxml/xades/README.rst index 7955355..311e8f1 100644 --- a/signxml/xades/README.rst +++ b/signxml/xades/README.rst @@ -41,3 +41,9 @@ Testing Conformance and Interoperability * `ETSI TS 119 134-3 V1.1.1 (2016-06) | Part 3: Test suites for testing interoperability of extended XAdES signatures `_ * `ETSI TS 119 134-4 V1.1.1 (2016-06) | Part 4: Testing Conformance of XAdES baseline signatures `_ * `ETSI TS 119 134-5 V2.1.1 (2016-06) | Part 5: Testing Conformance of extended XAdES signatures `_ + +Conformance Checker Tools +^^^^^^^^^^^^^^^^^^^^^^^^^ + +* `XAdES baseline and XAdES extended signatures Conformance Testing Tools Documentation `_ +* `Free Online Tool `_ From 9340e9e63e8b1010e9a6b426355e2f5ac0d05783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Wed, 20 Nov 2019 10:36:39 -0500 Subject: [PATCH 04/19] Update setup dependencies --- setup.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 2d5fc6b..881dd43 100755 --- a/setup.py +++ b/setup.py @@ -12,13 +12,13 @@ description='Python XML Signature library', long_description=open('README.rst').read(), install_requires=[ - 'lxml >= 4.2.1, < 5', - 'defusedxml >= 0.5.0, < 1', + 'lxml >= 4.4.1, < 5', + 'defusedxml >= 0.6.0, < 1', 'eight >= 0.4.2, < 1', - 'cryptography >= 2.1.4, < 3', - 'asn1crypto >= 0.24.0', - 'pyOpenSSL >= 17.5.0, < 19', - 'certifi >= 2018.1.18' + 'cryptography >= 2.8, < 3', + 'asn1crypto >= 1.2.0', + 'pyOpenSSL >= 18.0.0, < 19', + 'certifi >= 2019.9.11' ], extras_require={ ':python_version == "2.7"': [ From f53f975dca839f4d5f412671f8a2a2c6dd6e66b1 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 6 Nov 2019 16:18:34 -0500 Subject: [PATCH 05/19] WIP WIP --- signxml/__init__.py | 6 + signxml/xades/__init__.py | 509 ++++++++++++++++++++++++++++++++++---- 2 files changed, 462 insertions(+), 53 deletions(-) diff --git a/signxml/__init__.py b/signxml/__init__.py index 48db3f1..c27d605 100644 --- a/signxml/__init__.py +++ b/signxml/__init__.py @@ -279,6 +279,12 @@ def __init__(self, method=methods.enveloped, signature_algorithm="rsa-sha256", d self.c14n_alg = c14n_algorithm self.namespaces = dict(ds=namespaces.ds) self._parser = None + """ + List of built out elements to incorporate + such as XADES scheme or any other + :type to_append: py:class: list + """ + self.to_append = [] def sign(self, data, key=None, passphrase=None, cert=None, reference_uri=None, key_name=None, key_info=None, id_attribute=None): diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index 710ac18..b606956 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -5,6 +5,7 @@ from uuid import uuid4 from datetime import datetime import pytz +import requests from eight import str, bytes from lxml import etree @@ -15,6 +16,7 @@ from cryptography.hazmat.primitives.asymmetric import rsa, dsa, ec from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.hashes import Hash, SHA1, SHA224, SHA256, SHA384, SHA512 +from cryptography.hazmat.primitives.serialization import Encoding from cryptography.hazmat.backends import default_backend from .. import XMLSignatureProcessor, XMLSigner, XMLVerifier, VerifyResult, namespaces @@ -22,15 +24,28 @@ from ..exceptions import InvalidSignature, InvalidDigest, InvalidInput, InvalidCertificate # noqa from ..util import (bytes_to_long, long_to_bytes, strip_pem_header, add_pem_header, ensure_bytes, ensure_str, Namespace, XMLProcessor, iterate_pem, verify_x509_cert_chain) -from collections import namedtuple +import collections + + +def namedtuple_with_defaults(typename, field_names, default_values=()): + T = collections.namedtuple(typename, field_names) + T.__new__.__defaults__ = (None,) * len(T._fields) + if isinstance(default_values, collections.Mapping): + prototype = T(**default_values) + else: + prototype = T(*default_values) + T.__new__.__defaults__ = tuple(prototype) + return T + +namedtuple = namedtuple_with_defaults levels = Enum("Levels", "B T LT LTA") # Retrieved on 17. Oct. 2019: https://portal.etsi.org/pnns/uri-list namespaces.update(Namespace( - # xades111="https://uri.etsi.org/01903/v1.1.1#", # superseeded - # xades122="https://uri.etsi.org/01903/v1.2.2#", # superseeded + # xades111="https://uri.etsi.org/01903/v1.1.1#", # superseded + # xades122="https://uri.etsi.org/01903/v1.2.2#", # superseded xades132="https://uri.etsi.org/01903/v1.3.2#", xades141="https://uri.etsi.org/01903/v1.4.1#", )) @@ -39,14 +54,28 @@ XADES141 = ElementMaker(namespace=namespaces.xades141) DS = ElementMaker(namespace=namespaces.ds) -def _gen_id(suffix): - return "{}-{suffix}".format(uuid4(), suffix=suffix) +# helper functions +def _gen_id(prefix, suffix): + return "{prefix}-{uid}-{suffix}".format(prefix=prefix, uid=uuid4(), suffix=suffix) + + +def resolve_uri(uri): + """ + Returns the content of given uri + """ + try: + return requests.get(uri).content + except: + raise InvalidInput(f"Unable to resolve reference URI: {uri}") class XAdESProcessor(XMLSignatureProcessor): schema_file = "v1.4.1/XAdES01903v141-201601.xsd" + def _get_cert_encoded(self, cert): + return ensure_str(b64encode(cert.public_bytes(Encoding.DER))) + def _resolve_target(self, doc_root, qualifying_properties): uri = qualifying_properties.get("Target") if not uri: @@ -60,20 +89,109 @@ def _resolve_target(self, doc_root, qualifying_properties): results = doc_root.xpath(xpath_query, uri=uri.lstrip("#")) if len(results) > 1: raise InvalidInput("Ambiguous reference URI {} resolved to {} nodes".format(uri, len(results))) - elif len(results) == 1: + elif len(results) == 1: return results[0] raise InvalidInput("Unable to resolve reference URI: {}".format(uri)) +class ProductionPlace(namedtuple( + "ProductionPlace", + "City StreetAddress StateOrProvince PostalCode CountryName" +)): + """ + A containter to hold the XAdES ProductionPlace values. + + :param City: the city + :type City: str + :param StreetAddress: the street adress field + :type StreetAddress: str + :param StateOrProvince: the state or province + :type StateOrProvince: str + :param PostalCode: the postal code + :type PostalCode: str + :param CountryName: the country name + :type CountryName: str + """ -class XAdESSignerOptions(namedtuple("XAdESSignerOptions", "cert_chain signed_xml signature_xml")): +class CertifiedRoleV2(namedtuple( + "CertifiedRole", + "X509AttributeCertificate OtherAttributeCertificate" +)): + """ + A containter to hold a XAdES CertifiedRoleV2 object. + + :param X509AttributeCertificate: + attribute certificate conformant to Recommendation ITU-T X.509 + issued to the signer + :type X509AttributeCertificate: + :py:class:`cryptography.x509.Certificate` object + :param OtherAttributeCertificate: + attribute certificate (issued, in consequence, by Attribute Authorities) + in different syntax than the one specified in Recommendation ITU-T X.509 + :type OtherAttributeCertificate: + :py:class:`cryptography.x509.Certificate` object + + .. note:: + Either specify X509AttributeCertificate OR OtherAttributeCertificate. + """ + +class SignaturePolicy(namedtuple( + "SignaturePolicy", "Identifier Description" + )): + """ + A container to hold the XADES SignaturePolicy values + + :param Identifier: the identifier of the policy (URI) + :param Description: the description of the policy + i.e: for colombia + 'Política de firma para facturas electrónicas de la República de Colombia' + """ + +class SignerOptions(namedtuple( + "SignerOptions", "CertChain ProductionPlace SignaturePolicy ClaimedRoles CertifiedRoles SignedAssertions", + { + 'ClaimedRoles': [], + 'CertifiedRoles': [], + 'SignedAssertions': [], + } +)): """ A containter to hold the XAdES Signer runtime options. It can be provided by directly calling ``signxml.XAdESSigner._sign()``. - :param cert_chain: - OpenSSL.crypto.X509 objects containing the certificate and a chain of - intermediate certificates. - :type cert_chain: array of OpenSSL.crypto.X509 objects + :param CertChain: + the certificate and a chain of intermediate certificates. + :type CertChain: array of :py:class:`cryptography.x509.Certificate` objects + :param ProductionPlace: + A named tuple containing the values to generate the production place + signed property. + :type ProductionPlace: ProductionPlace namedtuple + :param SignaturePolicy: + A named tuple containing the values to generate the Signature Policy + :type SignaturePolicy: SignaturePolicy namedtuple + :param ClaimedRoles: + The claimed signer roles, this imlementation limits to string values. + Submit a placerholder string of your choice for domain application + definitions for representation of claimed roles and post-process the + final element tree. + :type ClaimedRoles: + :py:class:`list` of :py:class:`str` clamined roles + :param CertifiedRoles: + The certified roles as issued by an attribute certification authority + to the signer + :type CertifiedRoles: + array of :py:class:`sgnxml.xades.CertifiedRoleV2` objects + :param CertifiedRoles: + The certified roles as issued by an attribute certification authority + to the signer + :type CertifiedRoles: + array of :py:class:`sgnxml.xades.CertifiedRoleV2` objects + :param SignedAssertions: + Signed assertions are stronger than a claimed attribute but less + restrictive than an attribute certificate. As they are not typed in the + spec, transparently submit an array of :py:class:`lxml.etree.Element` + :type SignedAssertions: + array of :py:class:`lxml.etree.Element` objects according to domain + application definition for representation of signed assertions """ @@ -86,55 +204,321 @@ def __init__(self, level=levels.LTA, legacy=False, tz=pytz.utc): self.xades_legacy = legacy self.xades_level = level self.xades_tz = tz + self.refs = [] + self.dsig_prefix = "xmldsig" + super().__init__() def sign(self, *args, **kwargs): - options_struct = XAdESSignerOptions('foo', 'bar', 'baz') - self._sign(options_struct) + """ + building the SignerOptions using kwargs values + """ + place = ProductionPlace( + kwargs.get("city"), + kwargs.get("address"), + kwargs.get("state"), + kwargs.get("postal_code"), + kwargs.get("country"), + ) + policy = SignaturePolicy( + kwargs.get("pidentifier"), + kwargs.get("pdescription"), + ) + cert = kwargs.get("cert") + if isinstance(cert, (str, bytes)): + cert = list(iterate_pem(cert)) + else: + cert = [cert] + # keeping the original kwargs to be sent to the original method 'sign' + vnames = XMLSigner.sign.__code__.co_varnames + old_kws = kwargs + kwargs = dict() + kwargs["cert"] = cert + vnames = list(vnames) + vnames.remove("cert") + vnames = tuple(vnames) + for i in range(0,len(vnames)): + key = vnames[i] + if old_kws.get(key) is not None: + kwargs[key] = old_kws.get(key) + options_struct = SignerOptions(cert, place, policy) + qualified_properties = self._sign(options_struct) return super().sign(*args, **kwargs) def _sign(self, options_struct): - ... + """ + Returns the qualified properties + """ + self.refs #TODO: include refs in the signature info + return DS.Object(self._generate_xades(options_struct)) - def _add_xades_reference(self, id): + def _add_xades_reference(self, sp): """ + The refs depends the xmldsig scheme,(https://www.w3.org/TR/xmldsig-core2/#sec-Overview) + not the XADES scheme, therefore, those refs have to wait for the general element to be append to + the element ds:SignedInfo within the signature element ds:Signature + This ds:Reference element shall include the Type attribute with its value set to: + (https://www.w3.org/TR/xmldsig-core2/#sec-o-Reference) + This reference is made for compatibility mode v1 anv v2 + (https://www.w3.org/TR/xmldsig-core2/#sec-Compatibility-Mode-Examples) """ - DS.Reference( - Type="http://uri.etsi.org/01903#SignedProperties" + self.refs.append( + DS.Reference( + DS.Transforms(DS.Transform(Algorithm=self.default_c14n_algorithm)), + DS.DigestMethod( + Algorithm=self.known_digest_tags[self.digest_alg] + ), + DS.DigestValue( + self._get_digest( + self._c14n(sp, algorithm=self.c14n_alg), + self._get_digest_method_by_tag(self.digest_alg) + ) + ), + Type="http://uri.etsi.org/01903#SignedProperties", + URI="#" + sp.get("Id") + ) ) - pass - def _generate_xades_ssp_elements(self): + + def _generate_xades_ssp_elements(self, options_struct): """ - Deprecation as listed in ETSI EN 319 132-1 V1.1.1 (2016-04), Annex D + Generates the sequence of SignedSignatureProperty elements for + inclusion into the SignedProperty element. + + Deprecations as listed in ETSI EN 319 132-1 V1.1.1 (2016-04), Annex D + + :param options_struct: + carries the runtime options of the current signing run. + :type options_struct: A :py:class:`signxml.xades.SignerOptions` object + + + :returns: + A :py:class:`list` of effectively generated SignedSignatureProperty + elements.` """ elements = [] + # Item 1: SigningTime elements.append(XADES132.SigningTime(datetime.now(self.xades_tz).isoformat())) - if self.xades_legacy: - elements.append(XADES132.SigningCertificate()) # deprecated (legacy) - else: - elements.append(XADES132.SigningCertificateV2()) + # Item 2: SigningCertificate or SigningCertificateV2 + """ + The SigningCertificateV2 qualifying property shall be a signed + qualifying property that qualifies the signature. + The SigningCertificateV2 qualifying property shall contain one reference + to the signing certificate. + The SigningCertificateV2 qualifying property may contain references to + some of or all the certificates within + the signing certificate path, including one reference to the trust + anchor when this is a certificate. + """ + cert_elements = [] + for cert in options_struct.CertChain: + if self.xades_legacy: + serial_element = XADES132.IssuerSerial( + DS.X509IssuerName(cert.issuer.rfc4514_string()), + DS.X509SerialNumber(str(cert.serial_number)), + ) + else: + """ + The content of IssuerSerialV2 element shall be the the base-64 + encoding of one DER-encoded instance of type IssuerSerial type + defined in IETF RFC 5035 [17]. + """ + raise NotImplementedError( + "Please make a PR if you know how to obtain in python a " + "'DER-encoded instance of type IssuerSerial type defined in " + "IETF RFC 5035'") + serial_element = XADES132.IssuerSerialV2( + # TODO implement, wtf? + ) + """ + The element CertDigest shall contain the digest of the referenced + certificate. + CertDigest‘s children elements satisfy the following requirements: + 1) ds:DigestMethod element shall identify the digest algorithm. And + 2) ds:DigestValue element shall contain the base-64 encoded value + of the digest computed on the DERencoded certificate. + """ + cert_digest = XADES132.CertDigest( + DS.DigestMethod( + Algorithm=self.known_digest_tags[self.digest_alg] + ), + DS.DigestValue( + self._get_digest( + cert.public_bytes(Encoding.DER), + self._get_digest_method_by_tag(self.digest_alg) + ) + ) + ) + cert_elements.append(XADES132.Cert(cert_digest, serial_element)) if self.xades_legacy: - elements.append(XADES132.SignatureProductionPlace()) # deprecated (legacy) + elements.append(XADES132.SigningCertificate(*cert_elements)) # deprecated (legacy) else: - elements.append(XADES132.SignatureProductionPlaceV2()) + elements.append(XADES132.SigningCertificateV2(*cert_elements)) - elements.append(XADES132.SignaturePolicyIdentifier()) + # Item 3: SignatureProductionPlace or SignatureProductionPlaceV2 + """ + The SignatureProductionPlaceV2 qualifying property shall be a signed + qualifying property that qualifies the signer. + The SignatureProductionPlaceV2 qualifying property shall specify an + address associated with the signer at a particular geographical + (e.g. city) location. + """ + pp_elements = [] + PP = options_struct.ProductionPlace + pa = pp_elements.append + pa(XADES132.City(PP.City)) if PP.City else None + if not self.xades_legacy: + pa(XADES132.StreetAddress(PP.StreetAddress)) if PP.StreetAddress else None + pa(XADES132.StateOrProvince(PP.StateOrProvince)) if PP.StateOrProvince else None + pa(XADES132.PostalCode(PP.PostalCode)) if PP.PostalCode else None + pa(XADES132.CountryName(PP.CountryName)) if PP.CountryName else None - if self.xades_legacy: - elements.append(XADES132.SignerRole()) # deprecated (legacy) + """ + Empty SignatureProductionPlaceV2 qualifying properties shall not be generated. + """ + if self.xades_legacy and pp_elements: + elements.append(XADES132.SignatureProductionPlace(*pp_elements)) # deprecated (legacy) + elif pp_elements: + elements.append(XADES132.SignatureProductionPlaceV2(*pp_elements)) + + # Item 4: SignaturePolicyIdentifier + """ + The SignaturePolicyIdentifier qualifying property shall be a signed + qualifying property qualifying the signature. + The SignaturePolicyIdentifier qualifying property shall contain either + an explicit identifier of a signature policy or an indication that + there is an implied signature policy that the relying party should + be aware of. + + ETSI TS 119 172-1 specifies a framework for signature policies. + """ + spid_elements = [] + sp = options_struct.SignaturePolicy + # digest method + pdm = self.known_digest_tags.get(self.digest_alg) + # digest value + pv = resolve_uri(sp.Identifier) + + spid_elements.append( + XADES132.SigPolicyId( + XADES132.Identifier(sp.Identifier), + XADES132.Description(sp.Description) + ) + ) + spid_elements.append( + XADES132.SigPolicyHash( + DS.DigestMethod(Algorithm=pdm), + DS.DigestValue( + self._get_digest(pv,self._get_digest_method_by_tag(self.digest_alg)) + ) + ) + ) + spid = XADES132.SignaturePolicyId(*spid_elements) + spi_elements = [] + spi_elements.append(spid) + elements.append(XADES132.SignaturePolicyIdentifier(*spi_elements)) + + # Item 5: SignerRole or SignerRoleV2 + """ + The SignerRoleV2 qualifying property shall be a signed qualifying + property that qualifies the signer. + The SignerRoleV2 qualifying property shall encapsulate signer attributes + (e.g. role). This qualifying property may encapsulate the following + types of attributes: + • attributes claimed by the signer; + • attributes certified in attribute certificates issued by an + Attribute Authority; or/and + • assertions signed by a third party. + """ + clr_elements = [] + """ + The ClaimedRoles element shall contain a non-empty sequence of roles + claimed by the signer but which are not certified. + """ + for role in options_struct.ClaimedRoles: + if not isinstance(role, str): + """ + Additional content types *may* be defined on a domain application + basis and be part of this element. + """ + NotImplementedError( + "Role types different than strings, although permitted by " + "the schema, are not implemented. Use a placeholder and " + "post process the resulting element tree, instead!") + clr_elements.append(XADES132.ClaimedRole(role)) + + ctr_elements = [] + """ + The CertifiedRolesV2 element shall contain a non-empty sequence of + certified attributes, which shall be one of the following: + • the base-64 encoding of DER-encoded X509 attribute certificates + conformant to Recommendation ITU-T X.509 [4] issued to the signer, + within the X509AttributeCertificate element; or + • attribute certificates (issued, in consequence, by Attribute + Authorities) in different syntax than the one specified in + Recommendation ITU-T X.509 [4], within the + OtherAttributeCertificate element. The definition of specific + OtherAttributeCertificate is outside of the scope of the present + document + """ + if options_struct.CertifiedRoles and self.xades_legacy: + NotImplementedError( + "Legay certified roles wired as objects encoded in " + "EncapsulatedPKIDataType are not implemented.") + + for role in options_struct.CertifiedRoles: + ctr_elements.append(XADES132.CertifiedRolesV2( + XADES132.X509AttributeCertificate( + self._get_cert_encoded(role.X509AttributeCertificate) + ) if role.X509AttributeCertificate else + XADES132.OtherAttributeCertificate( + self._get_cert_encoded(role.OtherAttributeCertificate) + ) + )) + + """ + The SignedAssertions element shall contain a non-empty sequence of + assertions signed by a third party. + The definition of specific content types for SignedAssertions is outside + of the scope of the present document. + """ + sas_elements = options_struct.SignedAssertions + if not all(['SignedAssertions' in e.tag for e in sas_elements]): + raise InvalidInput( + "Input for signed assertions shall be all elements of type " + "'{https://uri.etsi.org/01903/v1.3.2#}SignedAssertions'") + + sr_elements = [] + if clr_elements: + sr_elements.append(XADES132.ClaimedRoles(*clr_elements)) + + if ctr_elements and self.xades_legacy: + raise # already cached above, never hit + elif ctr_elements: + sr_elements.append(XADES132.CertifiedRolesV2(*clr_elements)) + + if sas_elements and not self.xades_legacy: + sr_elements.append(XADES132.SignedAssertions(*sas_elements)) + + """ + Empty SignerRoleV2 qualifying properties shall not be generated. + """ + if not sr_elements: + pass + elif self.xades_legacy: + elements.append(XADES132.SignerRole(*sr_elements)) # deprecated (legacy) else: - elements.append(XADES132.SignerRoleV2()) + elements.append(XADES132.SignerRoleV2(*sr_elements)) # any ##other return elements - def _generate_xades_sdop_elements(self): + def _generate_xades_sdop_elements(self, options_struct): elements = [] elements.append(XADES132.DataObjectFormat()) @@ -149,7 +533,10 @@ def _generate_xades_sdop_elements(self): return elements - def _generate_xades_usp_elements(self): + def _generate_xades_usp_elements(self, options_struct): + """ + Deprecation as listed in ETSI EN 319 132-1 V1.1.1 (2016-04), Annex D + """ elements = [] elements.append(XADES132.CounterSignature()) @@ -194,14 +581,14 @@ def _generate_xades_usp_elements(self): return elements - def _generate_xades_udop_elements(self): + def _generate_xades_udop_elements(self, options_struct): elements = [] elements.append(XADES132.UnsignedDataObjectProperty()) return elements - def _generate_xades(self): + def _generate_xades(self, options_struct): """ Acronyms used in this method @@ -216,11 +603,11 @@ def _generate_xades(self): udop := UnignedDataObjectProperties, c. 4.3.7 """ - ssp_elements = self._generate_xades_ssp_elements() - sdop_elements = self._generate_xades_sdop_elements() + ssp_elements = self._generate_xades_ssp_elements(options_struct) + sdop_elements = self._generate_xades_sdop_elements(options_struct) - usp_elements = self._generate_xades_usp_elements() - udop_elements = self._generate_xades_udop_elements() + usp_elements = self._generate_xades_usp_elements(options_struct) + udop_elements = self._generate_xades_udop_elements(options_struct) # Step -3: Construction of SignedProperties sp_elements = [] @@ -231,8 +618,10 @@ def _generate_xades(self): """ The Id attribute shall be used to reference the SignedSignatureProperties element. """ - sp_elements.append(*ssp_elements, - Id=_gen_id("signedsigprops") # optional + sp_elements.append( + XADES132.SignedSignatureProperties( + *ssp_elements, Id=_gen_id(self.dsig_prefix, "signedsigprops") # optional + ) ) """ A XAdES signature shall not incorporate empty SignedDataObjectProperties element. @@ -241,8 +630,10 @@ def _generate_xades(self): """ The Id attribute shall be used to reference the SignedDataObjectProperties element. """ - sp_elements.append(*sdop_elements, - Id=_gen_id("signeddataobjprops") # optional + sp_elements.append( + XADES132.SignedDataObjectProperties( + *sdop_elements, Id=_gen_id(self.dsig_prefix, "signeddataobjprops") # optional + ) ) # Step -2: Construction of UnsignedProperties @@ -254,8 +645,10 @@ def _generate_xades(self): """ The Id attribute shall be used to reference the UnignedSignatureProperties element. """ - sp_elements.append(*usp_elements, - Id=_gen_id("unsignedsigprops") # optional + usp_elements.append( + XADES132.UnsignedSignatureProperties( + *usp_elements, Id=_gen_id(self.dsig_prefix, "unsignedsigprops") # optional + ) ) """ A XAdES signature shall not incorporate empty UnignedDataObjectProperties element. @@ -264,8 +657,10 @@ def _generate_xades(self): """ The Id attribute shall be used to reference the UnignedDataObjectProperties element. """ - sp_elements.append(*udop_elements, - Id=_gen_id("unsigneddataobjprops") # optional + usp_elements.append( + XADES132.UnsignedDataObjectProperties( + *udop_elements, Id=_gen_id(self.dsig_prefix, "unsigneddataobjprops") # optional + ) ) # Step -1: Construction of QualifyingProperties @@ -278,16 +673,24 @@ def _generate_xades(self): """ The Id attribute shall be used to reference the SignedProperties element. """ - qp_elements.append( - XADES132.SignedProperties(*sp_elements, - Id=_gen_id("signedprops") # optional - ) + sp = XADES132.SignedProperties(*sp_elements, + Id=_gen_id(self.dsig_prefix, "signedprops") # optional ) + qp_elements.append(sp) """ In order to protect the qualifying properties with the signature, a ds:Reference element shall be added to the XML signature. + At this point, the qp_elements only should contains the SignedProperties element, + therefore, it can be referenced by the index 0. + The refs depend the xmldsig scheme,(https://www.w3.org/TR/xmldsig-core2/#sec-Overview) + not the XADES scheme, therefore, those refs have to wait for the general element to be appended to + the element ds:SignedInfo within the signature element ds:Signature + + This ds:Reference element shall include the Type attribute with its + value set to: """ - self._add_xades_reference(sp_attributes.get('Id')) + + self._add_xades_reference(sp) """ A XAdES signature shall not incorporate empty UnsignedProperties elements. @@ -298,7 +701,7 @@ def _generate_xades(self): """ qp_elements.append( XADES132.UnsignedProperties(*up_elements, - Id=_gen_id("unsignedprops") # optional + Id=_gen_id(self.dsig_prefix, "unsignedprops") # optional ) ) @@ -309,7 +712,7 @@ def _generate_xades(self): """ qp_attributes = { "Target": '', # required - "Id": _gen_id("qualifyingprops"), # optional + "Id": _gen_id(self.dsig_prefix, "qualifyingprops"), # optional } """ From 40c24728bd85e8bcb2190851b9b2d9428c1f0d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Tue, 14 Jan 2020 02:18:43 -0500 Subject: [PATCH 06/19] Add nsmap parameter to build namespaces --- signxml/xades/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index b606956..3ee0b70 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -50,13 +50,16 @@ def namedtuple_with_defaults(typename, field_names, default_values=()): xades141="https://uri.etsi.org/01903/v1.4.1#", )) -XADES132 = ElementMaker(namespace=namespaces.xades132) -XADES141 = ElementMaker(namespace=namespaces.xades141) -DS = ElementMaker(namespace=namespaces.ds) +XADES132 = ElementMaker(namespace=namespaces.xades132, nsmap=namespaces) +XADES141 = ElementMaker(namespace=namespaces.xades141, nsmap=namespaces) +DS = ElementMaker(namespace=namespaces.ds, nsmap=namespaces) # helper functions def _gen_id(prefix, suffix): + """ + Generates the id + """ return "{prefix}-{uid}-{suffix}".format(prefix=prefix, uid=uuid4(), suffix=suffix) From c9c02178ec9afec93b6e72a8472ac879e68f6cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Fri, 24 Jan 2020 20:22:01 -0500 Subject: [PATCH 07/19] Improvements This commit contains: - Added helper method `_pre_build_sig` - Added the attribute 'Id' to the element 'QualifyingProperties' Note: For more details check the comments in the methods --- signxml/__init__.py | 61 +++++++++++++++++++++++++++++---------- signxml/xades/__init__.py | 11 ++++--- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/signxml/__init__.py b/signxml/__init__.py index c27d605..07135e4 100644 --- a/signxml/__init__.py +++ b/signxml/__init__.py @@ -280,11 +280,9 @@ def __init__(self, method=methods.enveloped, signature_algorithm="rsa-sha256", d self.namespaces = dict(ds=namespaces.ds) self._parser = None """ - List of built out elements to incorporate - such as XADES scheme or any other - :type to_append: py:class: list + List of built references to incorporate to the element ds:SignedInfo """ - self.to_append = [] + self.refs = [] def sign(self, data, key=None, passphrase=None, cert=None, reference_uri=None, key_name=None, key_info=None, id_attribute=None): @@ -314,7 +312,7 @@ def sign(self, data, key=None, passphrase=None, cert=None, reference_uri=None, k Custom reference URI or list of reference URIs to incorporate into the signature. When ``method`` is set to ``detached`` or ``enveloped``, reference URIs are set to this value and only the referenced elements are signed. - :type reference_uri: string or list + :type reference_uri: string or list or XML ElementTree Element :param key_name: Add a KeyName element in the KeyInfo element that may be used by the signer to communicate a key identifier to the recipient. Typically, KeyName contains an identifier related to the key pair used to sign the message. @@ -349,6 +347,7 @@ def sign(self, data, key=None, passphrase=None, cert=None, reference_uri=None, k reference_uris = reference_uri sig_root, doc_root, c14n_inputs, reference_uris = self._unpack(data, reference_uris) + doc_root, sig_root, reference_uris = self._pre_build_sig(doc_root, sig_root, reference_uris) signed_info_element, signature_value_element = self._build_sig(sig_root, reference_uris, c14n_inputs) if key is None: @@ -473,6 +472,32 @@ def _unpack(self, data, reference_uris): reference_uris = ["#object"] return sig_root, doc_root, c14n_inputs, reference_uris + def _pre_build_sig(self, doc_root, sig_root, reference_uris): + """ + To overwrite + + By default, this method add self.refs to reference_uris, elements + ready to incorporate into sig_root without changes + + Suggested use case: + Set the attribute 'Id' of the element 'Signature' when the + in the _unpack function the attribute is deleted, this will override + the digest value of built references + exmple: + + query = self._findall(doc_root, "QualifyingProperties") + if len(query) > 1: + raise NotImplementedError("Multiple QualifyingProperties") + sig_id = query[0].attrib["Target"] + if sig_id.startswith("#"): + sig_id = sig_id.replace("#","") + sig_root.set("Id", sig_id) + """ + + reference_uris += self.refs + + return doc_root, sig_root, reference_uris + def _build_sig(self, sig_root, reference_uris, c14n_inputs): signed_info = SubElement(sig_root, ds_tag("SignedInfo"), nsmap=self.namespaces) c14n_method = SubElement(signed_info, ds_tag("CanonicalizationMethod"), Algorithm=self.c14n_alg) @@ -482,17 +507,21 @@ def _build_sig(self, sig_root, reference_uris, c14n_inputs): algorithm_id = self.known_signature_digest_tags[self.sign_alg] signature_method = SubElement(signed_info, ds_tag("SignatureMethod"), Algorithm=algorithm_id) for i, reference_uri in enumerate(reference_uris): - reference = SubElement(signed_info, ds_tag("Reference"), URI=reference_uri) - if self.method == methods.enveloped: - transforms = SubElement(reference, ds_tag("Transforms")) - SubElement(transforms, ds_tag("Transform"), Algorithm=namespaces.ds + "enveloped-signature") - SubElement(transforms, ds_tag("Transform"), Algorithm=self.c14n_alg) - digest_method = SubElement(reference, ds_tag("DigestMethod"), - Algorithm=self.known_digest_tags[self.digest_alg]) - digest_value = SubElement(reference, ds_tag("DigestValue")) - payload_c14n = self._c14n(c14n_inputs[i], algorithm=self.c14n_alg) - digest = self._get_digest(payload_c14n, self._get_digest_method_by_tag(self.digest_alg)) - digest_value.text = digest + if etree.iselement(reference_uri): + # adding built references + signed_info.append(reference_uri) + else: + reference = SubElement(signed_info, ds_tag("Reference"), URI=reference_uri) + if self.method == methods.enveloped: + transforms = SubElement(reference, ds_tag("Transforms")) + SubElement(transforms, ds_tag("Transform"), Algorithm=namespaces.ds + "enveloped-signature") + SubElement(transforms, ds_tag("Transform"), Algorithm=self.c14n_alg) + digest_method = SubElement(reference, ds_tag("DigestMethod"), + Algorithm=self.known_digest_tags[self.digest_alg]) + digest_value = SubElement(reference, ds_tag("DigestValue")) + payload_c14n = self._c14n(c14n_inputs[i], algorithm=self.c14n_alg) + digest = self._get_digest(payload_c14n, self._get_digest_method_by_tag(self.digest_alg)) + digest_value.text = digest signature_value = SubElement(sig_root, ds_tag("SignatureValue")) return signed_info, signature_value diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index 3ee0b70..e6a62bd 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -56,11 +56,13 @@ def namedtuple_with_defaults(typename, field_names, default_values=()): # helper functions -def _gen_id(prefix, suffix): +def _gen_id(prefix, suffix=None): """ Generates the id """ - return "{prefix}-{uid}-{suffix}".format(prefix=prefix, uid=uuid4(), suffix=suffix) + suffix = "-{suffix}".format(suffix) if suffix else "" + + return "{prefix}-{uid}{suffix}".format(prefix=prefix, uid=uuid4(), suffix=suffix) def resolve_uri(uri): @@ -251,7 +253,6 @@ def _sign(self, options_struct): """ Returns the qualified properties """ - self.refs #TODO: include refs in the signature info return DS.Object(self._generate_xades(options_struct)) def _add_xades_reference(self, sp): @@ -714,10 +715,12 @@ def _generate_xades(self, options_struct): the QualifyingProperties container. """ qp_attributes = { - "Target": '', # required + "Target": "#"+_gen_id(self.dsig_prefix,"signature"), # required "Id": _gen_id(self.dsig_prefix, "qualifyingprops"), # optional } + self._add_xades_reference(qp_attributes) + """ A XAdES signature shall not incorporate empty QualifyingProperties elements. """ From 914e7301c3d5d910d67fbef93114e7151b863f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Fri, 24 Jan 2020 20:34:34 -0500 Subject: [PATCH 08/19] Fix --- signxml/xades/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index e6a62bd..5ee4552 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -60,7 +60,7 @@ def _gen_id(prefix, suffix=None): """ Generates the id """ - suffix = "-{suffix}".format(suffix) if suffix else "" + suffix = "-{}".format(suffix) if suffix else "" return "{prefix}-{uid}{suffix}".format(prefix=prefix, uid=uuid4(), suffix=suffix) From 45da9ad745ebb4c103792e59153adc3c4d75da76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Tue, 28 Jan 2020 10:50:02 -0500 Subject: [PATCH 09/19] Improvements This commit contains: - add references to c14_inputs through the method `_pre_build_sig` - normalize xades schemas to http reference - add QualifyinProperties's reference to `XAdESSigner.refs` - add the Id to generated QualifyingProperties - remove the stupid generation of QualifyingProperties calling the method `sign` --- signxml/__init__.py | 2 +- signxml/xades/__init__.py | 46 +++++---------------------------------- 2 files changed, 6 insertions(+), 42 deletions(-) diff --git a/signxml/__init__.py b/signxml/__init__.py index 07135e4..898fa4d 100644 --- a/signxml/__init__.py +++ b/signxml/__init__.py @@ -347,7 +347,7 @@ def sign(self, data, key=None, passphrase=None, cert=None, reference_uri=None, k reference_uris = reference_uri sig_root, doc_root, c14n_inputs, reference_uris = self._unpack(data, reference_uris) - doc_root, sig_root, reference_uris = self._pre_build_sig(doc_root, sig_root, reference_uris) + doc_root, sig_root, reference_uris, c14n_inputs = self._pre_build_sig(doc_root, sig_root, reference_uris, c14n_inputs) signed_info_element, signature_value_element = self._build_sig(sig_root, reference_uris, c14n_inputs) if key is None: diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index 5ee4552..19c5918 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -46,8 +46,8 @@ def namedtuple_with_defaults(typename, field_names, default_values=()): namespaces.update(Namespace( # xades111="https://uri.etsi.org/01903/v1.1.1#", # superseded # xades122="https://uri.etsi.org/01903/v1.2.2#", # superseded - xades132="https://uri.etsi.org/01903/v1.3.2#", - xades141="https://uri.etsi.org/01903/v1.4.1#", + xades132="http://uri.etsi.org/01903/v1.3.2#", + xades141="http://uri.etsi.org/01903/v1.4.1#", )) XADES132 = ElementMaker(namespace=namespaces.xades132, nsmap=namespaces) @@ -213,42 +213,6 @@ def __init__(self, level=levels.LTA, legacy=False, tz=pytz.utc): self.dsig_prefix = "xmldsig" super().__init__() - def sign(self, *args, **kwargs): - """ - building the SignerOptions using kwargs values - """ - place = ProductionPlace( - kwargs.get("city"), - kwargs.get("address"), - kwargs.get("state"), - kwargs.get("postal_code"), - kwargs.get("country"), - ) - policy = SignaturePolicy( - kwargs.get("pidentifier"), - kwargs.get("pdescription"), - ) - cert = kwargs.get("cert") - if isinstance(cert, (str, bytes)): - cert = list(iterate_pem(cert)) - else: - cert = [cert] - # keeping the original kwargs to be sent to the original method 'sign' - vnames = XMLSigner.sign.__code__.co_varnames - old_kws = kwargs - kwargs = dict() - kwargs["cert"] = cert - vnames = list(vnames) - vnames.remove("cert") - vnames = tuple(vnames) - for i in range(0,len(vnames)): - key = vnames[i] - if old_kws.get(key) is not None: - kwargs[key] = old_kws.get(key) - options_struct = SignerOptions(cert, place, policy) - qualified_properties = self._sign(options_struct) - return super().sign(*args, **kwargs) - def _sign(self, options_struct): """ Returns the qualified properties @@ -719,11 +683,11 @@ def _generate_xades(self, options_struct): "Id": _gen_id(self.dsig_prefix, "qualifyingprops"), # optional } - self._add_xades_reference(qp_attributes) - """ A XAdES signature shall not incorporate empty QualifyingProperties elements. """ if not qp_elements: return None - return XADES132.QualifyingProperties(*qp_elements, **qp_attributes) + qp = XADES132.QualifyingProperties(*qp_elements, **qp_attributes) + self._add_xades_reference(qp) + return qp From 04fef11db4232141e2faec9645e0734dcd64ef68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Tue, 28 Jan 2020 12:25:38 -0500 Subject: [PATCH 10/19] Add black list This commit contains: - add method `_clean_elements_from_black_list` to prevent the inclusion of specified elements --- signxml/xades/__init__.py | 50 +++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index 19c5918..aa3b918 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -154,9 +154,10 @@ class SignaturePolicy(namedtuple( class SignerOptions(namedtuple( "SignerOptions", "CertChain ProductionPlace SignaturePolicy ClaimedRoles CertifiedRoles SignedAssertions", { - 'ClaimedRoles': [], - 'CertifiedRoles': [], - 'SignedAssertions': [], + "ClaimedRoles": [], + "CertifiedRoles": [], + "SignedAssertions": [], + "BlackList": [], } )): """ @@ -197,9 +198,12 @@ class SignerOptions(namedtuple( :type SignedAssertions: array of :py:class:`lxml.etree.Element` objects according to domain application definition for representation of signed assertions + :param BlackList: + The list of tag elements that SHOULD NOT be included. + i.e: [namespace.Element.tag] + :type BlackList:`list`of tag elements """ - class XAdESSigner(XAdESProcessor, XMLSigner): """ ... @@ -219,6 +223,25 @@ def _sign(self, options_struct): """ return DS.Object(self._generate_xades(options_struct)) + def _clean_elements_from_black_list(self, black_list, elements): + """ + :param black_list: + the list of tag elements that will be removed + :type black_list: + :py:class:`list` + :param elemnts: + the list of elements to be checked in the black list + :type elements: + :py:class:`list` of etree.Element + :return clean elements + """ + if not isinstance(elements, list): + elements = list(elements) + + return list(filter( + lambda x: isinstance(x, etree._Element) and x.tag not in black_list, elements + )) + def _add_xades_reference(self, sp): """ The refs depends the xmldsig scheme,(https://www.w3.org/TR/xmldsig-core2/#sec-Overview) @@ -348,6 +371,7 @@ def _generate_xades_ssp_elements(self, options_struct): """ Empty SignatureProductionPlaceV2 qualifying properties shall not be generated. """ + pp_elements = self._clean_elements_from_black_list(options_struct.BlackList, pp_elements) if self.xades_legacy and pp_elements: elements.append(XADES132.SignatureProductionPlace(*pp_elements)) # deprecated (legacy) elif pp_elements: @@ -467,7 +491,7 @@ def _generate_xades_ssp_elements(self, options_struct): if ctr_elements and self.xades_legacy: raise # already cached above, never hit elif ctr_elements: - sr_elements.append(XADES132.CertifiedRolesV2(*clr_elements)) + sr_elements.append(XADES132.CertifiedRolesV2(*ctr_elements)) if sas_elements and not self.xades_legacy: sr_elements.append(XADES132.SignedAssertions(*sas_elements)) @@ -477,14 +501,16 @@ def _generate_xades_ssp_elements(self, options_struct): """ if not sr_elements: pass - elif self.xades_legacy: - elements.append(XADES132.SignerRole(*sr_elements)) # deprecated (legacy) else: - elements.append(XADES132.SignerRoleV2(*sr_elements)) + sr_elements = self._clean_elements_from_black_list(options_struct.BlackList, sr_elements) + elif self.xades_legacy: + elements.append(XADES132.SignerRole(*sr_elements)) # deprecated (legacy) + else: + elements.append(XADES132.SignerRoleV2(*sr_elements)) # any ##other - return elements + return self._clean_elements_from_black_list(options_struct.BlackList, elements) def _generate_xades_sdop_elements(self, options_struct): elements = [] @@ -499,7 +525,7 @@ def _generate_xades_sdop_elements(self, options_struct): # any ##other - return elements + return self._clean_elements_from_black_list(options_struct.BlackList, elements) def _generate_xades_usp_elements(self, options_struct): """ @@ -547,14 +573,14 @@ def _generate_xades_usp_elements(self, options_struct): # any ##other - return elements + return self._clean_elements_from_black_list(options_struct.BlackList, elements) def _generate_xades_udop_elements(self, options_struct): elements = [] elements.append(XADES132.UnsignedDataObjectProperty()) - return elements + return self._clean_elements_from_black_list(options_struct.BlackList, elements) def _generate_xades(self, options_struct): From e69164566c6c5cf7ba3210cf2f9a1079485ee385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Tue, 28 Jan 2020 12:30:28 -0500 Subject: [PATCH 11/19] Fix --- signxml/xades/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index aa3b918..4e35fe4 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -503,7 +503,7 @@ def _generate_xades_ssp_elements(self, options_struct): pass else: sr_elements = self._clean_elements_from_black_list(options_struct.BlackList, sr_elements) - elif self.xades_legacy: + if self.xades_legacy: elements.append(XADES132.SignerRole(*sr_elements)) # deprecated (legacy) else: elements.append(XADES132.SignerRoleV2(*sr_elements)) From de01d9b38ecc4bbe4ff817a169a7eeb38be25f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Tue, 28 Jan 2020 13:00:00 -0500 Subject: [PATCH 12/19] Fix black list implementation --- signxml/xades/__init__.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index 4e35fe4..b4fb151 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -152,12 +152,12 @@ class SignaturePolicy(namedtuple( """ class SignerOptions(namedtuple( - "SignerOptions", "CertChain ProductionPlace SignaturePolicy ClaimedRoles CertifiedRoles SignedAssertions", + "SignerOptions", + "CertChain ProductionPlace SignaturePolicy ClaimedRoles CertifiedRoles SignedAssertions", { "ClaimedRoles": [], "CertifiedRoles": [], "SignedAssertions": [], - "BlackList": [], } )): """ @@ -198,10 +198,6 @@ class SignerOptions(namedtuple( :type SignedAssertions: array of :py:class:`lxml.etree.Element` objects according to domain application definition for representation of signed assertions - :param BlackList: - The list of tag elements that SHOULD NOT be included. - i.e: [namespace.Element.tag] - :type BlackList:`list`of tag elements """ class XAdESSigner(XAdESProcessor, XMLSigner): @@ -209,12 +205,13 @@ class XAdESSigner(XAdESProcessor, XMLSigner): ... """ - def __init__(self, level=levels.LTA, legacy=False, tz=pytz.utc): + def __init__(self, level=levels.LTA, legacy=False, tz=pytz.utc, black_list=list()): self.xades_legacy = legacy self.xades_level = level self.xades_tz = tz self.refs = [] self.dsig_prefix = "xmldsig" + self.black_list = black_list super().__init__() def _sign(self, options_struct): @@ -223,7 +220,7 @@ def _sign(self, options_struct): """ return DS.Object(self._generate_xades(options_struct)) - def _clean_elements_from_black_list(self, black_list, elements): + def _clean_elements_from_black_list(self, elements): """ :param black_list: the list of tag elements that will be removed @@ -239,7 +236,7 @@ def _clean_elements_from_black_list(self, black_list, elements): elements = list(elements) return list(filter( - lambda x: isinstance(x, etree._Element) and x.tag not in black_list, elements + lambda x: isinstance(x, etree._Element) and x.tag not in self.black_list, elements )) def _add_xades_reference(self, sp): @@ -371,7 +368,7 @@ def _generate_xades_ssp_elements(self, options_struct): """ Empty SignatureProductionPlaceV2 qualifying properties shall not be generated. """ - pp_elements = self._clean_elements_from_black_list(options_struct.BlackList, pp_elements) + pp_elements = self._clean_elements_from_black_list(pp_elements) if self.xades_legacy and pp_elements: elements.append(XADES132.SignatureProductionPlace(*pp_elements)) # deprecated (legacy) elif pp_elements: @@ -502,7 +499,7 @@ def _generate_xades_ssp_elements(self, options_struct): if not sr_elements: pass else: - sr_elements = self._clean_elements_from_black_list(options_struct.BlackList, sr_elements) + sr_elements = self._clean_elements_from_black_list(sr_elements) if self.xades_legacy: elements.append(XADES132.SignerRole(*sr_elements)) # deprecated (legacy) else: @@ -510,7 +507,7 @@ def _generate_xades_ssp_elements(self, options_struct): # any ##other - return self._clean_elements_from_black_list(options_struct.BlackList, elements) + return self._clean_elements_from_black_list(elements) def _generate_xades_sdop_elements(self, options_struct): elements = [] @@ -525,7 +522,7 @@ def _generate_xades_sdop_elements(self, options_struct): # any ##other - return self._clean_elements_from_black_list(options_struct.BlackList, elements) + return self._clean_elements_from_black_list(elements) def _generate_xades_usp_elements(self, options_struct): """ @@ -573,14 +570,14 @@ def _generate_xades_usp_elements(self, options_struct): # any ##other - return self._clean_elements_from_black_list(options_struct.BlackList, elements) + return self._clean_elements_from_black_list(elements) def _generate_xades_udop_elements(self, options_struct): elements = [] elements.append(XADES132.UnsignedDataObjectProperty()) - return self._clean_elements_from_black_list(options_struct.BlackList, elements) + return self._clean_elements_from_black_list(elements) def _generate_xades(self, options_struct): From a6856d570848cf444cb3ff115cb58f4e8d0065be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Tue, 28 Jan 2020 18:20:20 -0500 Subject: [PATCH 13/19] Improvements This commit contains: - add black list filter for missing elements - keep original namespace 'xades' from first version --- signxml/xades/__init__.py | 118 +++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index b4fb151..40d43c6 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -46,11 +46,11 @@ def namedtuple_with_defaults(typename, field_names, default_values=()): namespaces.update(Namespace( # xades111="https://uri.etsi.org/01903/v1.1.1#", # superseded # xades122="https://uri.etsi.org/01903/v1.2.2#", # superseded - xades132="http://uri.etsi.org/01903/v1.3.2#", + xades="http://uri.etsi.org/01903/v1.3.2#", xades141="http://uri.etsi.org/01903/v1.4.1#", )) -XADES132 = ElementMaker(namespace=namespaces.xades132, nsmap=namespaces) +XADES = ElementMaker(namespace=namespaces.xades, nsmap=namespaces) XADES141 = ElementMaker(namespace=namespaces.xades141, nsmap=namespaces) DS = ElementMaker(namespace=namespaces.ds, nsmap=namespaces) @@ -288,7 +288,7 @@ def _generate_xades_ssp_elements(self, options_struct): elements = [] # Item 1: SigningTime - elements.append(XADES132.SigningTime(datetime.now(self.xades_tz).isoformat())) + elements.append(XADES.SigningTime(datetime.now(self.xades_tz).isoformat())) # Item 2: SigningCertificate or SigningCertificateV2 """ @@ -304,7 +304,7 @@ def _generate_xades_ssp_elements(self, options_struct): cert_elements = [] for cert in options_struct.CertChain: if self.xades_legacy: - serial_element = XADES132.IssuerSerial( + serial_element = XADES.IssuerSerial( DS.X509IssuerName(cert.issuer.rfc4514_string()), DS.X509SerialNumber(str(cert.serial_number)), ) @@ -318,7 +318,7 @@ def _generate_xades_ssp_elements(self, options_struct): "Please make a PR if you know how to obtain in python a " "'DER-encoded instance of type IssuerSerial type defined in " "IETF RFC 5035'") - serial_element = XADES132.IssuerSerialV2( + serial_element = XADES.IssuerSerialV2( # TODO implement, wtf? ) """ @@ -329,7 +329,7 @@ def _generate_xades_ssp_elements(self, options_struct): 2) ds:DigestValue element shall contain the base-64 encoded value of the digest computed on the DERencoded certificate. """ - cert_digest = XADES132.CertDigest( + cert_digest = XADES.CertDigest( DS.DigestMethod( Algorithm=self.known_digest_tags[self.digest_alg] ), @@ -340,12 +340,12 @@ def _generate_xades_ssp_elements(self, options_struct): ) ) ) - cert_elements.append(XADES132.Cert(cert_digest, serial_element)) + cert_elements.append(XADES.Cert(cert_digest, serial_element)) if self.xades_legacy: - elements.append(XADES132.SigningCertificate(*cert_elements)) # deprecated (legacy) + elements.append(XADES.SigningCertificate(*cert_elements)) # deprecated (legacy) else: - elements.append(XADES132.SigningCertificateV2(*cert_elements)) + elements.append(XADES.SigningCertificateV2(*cert_elements)) # Item 3: SignatureProductionPlace or SignatureProductionPlaceV2 """ @@ -358,21 +358,21 @@ def _generate_xades_ssp_elements(self, options_struct): pp_elements = [] PP = options_struct.ProductionPlace pa = pp_elements.append - pa(XADES132.City(PP.City)) if PP.City else None + pa(XADES.City(PP.City)) if PP.City else None if not self.xades_legacy: - pa(XADES132.StreetAddress(PP.StreetAddress)) if PP.StreetAddress else None - pa(XADES132.StateOrProvince(PP.StateOrProvince)) if PP.StateOrProvince else None - pa(XADES132.PostalCode(PP.PostalCode)) if PP.PostalCode else None - pa(XADES132.CountryName(PP.CountryName)) if PP.CountryName else None + pa(XADES.StreetAddress(PP.StreetAddress)) if PP.StreetAddress else None + pa(XADES.StateOrProvince(PP.StateOrProvince)) if PP.StateOrProvince else None + pa(XADES.PostalCode(PP.PostalCode)) if PP.PostalCode else None + pa(XADES.CountryName(PP.CountryName)) if PP.CountryName else None """ Empty SignatureProductionPlaceV2 qualifying properties shall not be generated. """ pp_elements = self._clean_elements_from_black_list(pp_elements) if self.xades_legacy and pp_elements: - elements.append(XADES132.SignatureProductionPlace(*pp_elements)) # deprecated (legacy) + elements.append(XADES.SignatureProductionPlace(*pp_elements)) # deprecated (legacy) elif pp_elements: - elements.append(XADES132.SignatureProductionPlaceV2(*pp_elements)) + elements.append(XADES.SignatureProductionPlaceV2(*pp_elements)) # Item 4: SignaturePolicyIdentifier """ @@ -393,23 +393,23 @@ def _generate_xades_ssp_elements(self, options_struct): pv = resolve_uri(sp.Identifier) spid_elements.append( - XADES132.SigPolicyId( - XADES132.Identifier(sp.Identifier), - XADES132.Description(sp.Description) + XADES.SigPolicyId( + XADES.Identifier(sp.Identifier), + XADES.Description(sp.Description) ) ) spid_elements.append( - XADES132.SigPolicyHash( + XADES.SigPolicyHash( DS.DigestMethod(Algorithm=pdm), DS.DigestValue( self._get_digest(pv,self._get_digest_method_by_tag(self.digest_alg)) ) ) ) - spid = XADES132.SignaturePolicyId(*spid_elements) + spid = XADES.SignaturePolicyId(*spid_elements) spi_elements = [] spi_elements.append(spid) - elements.append(XADES132.SignaturePolicyIdentifier(*spi_elements)) + elements.append(XADES.SignaturePolicyIdentifier(*spi_elements)) # Item 5: SignerRole or SignerRoleV2 """ @@ -438,7 +438,7 @@ def _generate_xades_ssp_elements(self, options_struct): "Role types different than strings, although permitted by " "the schema, are not implemented. Use a placeholder and " "post process the resulting element tree, instead!") - clr_elements.append(XADES132.ClaimedRole(role)) + clr_elements.append(XADES.ClaimedRole(role)) ctr_elements = [] """ @@ -460,11 +460,11 @@ def _generate_xades_ssp_elements(self, options_struct): "EncapsulatedPKIDataType are not implemented.") for role in options_struct.CertifiedRoles: - ctr_elements.append(XADES132.CertifiedRolesV2( - XADES132.X509AttributeCertificate( + ctr_elements.append(XADES.CertifiedRolesV2( + XADES.X509AttributeCertificate( self._get_cert_encoded(role.X509AttributeCertificate) ) if role.X509AttributeCertificate else - XADES132.OtherAttributeCertificate( + XADES.OtherAttributeCertificate( self._get_cert_encoded(role.OtherAttributeCertificate) ) )) @@ -483,15 +483,15 @@ def _generate_xades_ssp_elements(self, options_struct): sr_elements = [] if clr_elements: - sr_elements.append(XADES132.ClaimedRoles(*clr_elements)) + sr_elements.append(XADES.ClaimedRoles(*clr_elements)) if ctr_elements and self.xades_legacy: raise # already cached above, never hit elif ctr_elements: - sr_elements.append(XADES132.CertifiedRolesV2(*ctr_elements)) + sr_elements.append(XADES.CertifiedRolesV2(*ctr_elements)) if sas_elements and not self.xades_legacy: - sr_elements.append(XADES132.SignedAssertions(*sas_elements)) + sr_elements.append(XADES.SignedAssertions(*sas_elements)) """ Empty SignerRoleV2 qualifying properties shall not be generated. @@ -501,9 +501,9 @@ def _generate_xades_ssp_elements(self, options_struct): else: sr_elements = self._clean_elements_from_black_list(sr_elements) if self.xades_legacy: - elements.append(XADES132.SignerRole(*sr_elements)) # deprecated (legacy) + elements.append(XADES.SignerRole(*sr_elements)) # deprecated (legacy) else: - elements.append(XADES132.SignerRoleV2(*sr_elements)) + elements.append(XADES.SignerRoleV2(*sr_elements)) # any ##other @@ -512,13 +512,13 @@ def _generate_xades_ssp_elements(self, options_struct): def _generate_xades_sdop_elements(self, options_struct): elements = [] - elements.append(XADES132.DataObjectFormat()) + elements.append(XADES.DataObjectFormat()) - elements.append(XADES132.CommitmentTypeIndication()) + elements.append(XADES.CommitmentTypeIndication()) - elements.append(XADES132.AllDataObjectsTimeStamp()) + elements.append(XADES.AllDataObjectsTimeStamp()) - elements.append(XADES132.IndividualDataObjectsTimeStamp()) + elements.append(XADES.IndividualDataObjectsTimeStamp()) # any ##other @@ -530,43 +530,43 @@ def _generate_xades_usp_elements(self, options_struct): """ elements = [] - elements.append(XADES132.CounterSignature()) + elements.append(XADES.CounterSignature()) - elements.append(XADES132.SignatureTimeStamp()) + elements.append(XADES.SignatureTimeStamp()) if self.xades_legacy: - elements.append(XADES132.CompleteCertificateRefs()) # deprecated (legacy) + elements.append(XADES.CompleteCertificateRefs()) # deprecated (legacy) else: elements.append(XADES141.CompleteCertificateRefsV2()) - elements.append(XADES132.CompleteRevocationRefs()) + elements.append(XADES.CompleteRevocationRefs()) if self.xades_legacy: - elements.append(XADES132.AttributeCertificateRefs()) # deprecated (legacy) + elements.append(XADES.AttributeCertificateRefs()) # deprecated (legacy) else: elements.append(XADES141.AttributeCertificateRefsV2()) - elements.append(XADES132.AttributeRevocationRefs()) + elements.append(XADES.AttributeRevocationRefs()) if self.xades_legacy: - elements.append(XADES132.SigAndRefsTimeStamp()) # deprecated (legacy) + elements.append(XADES.SigAndRefsTimeStamp()) # deprecated (legacy) else: elements.append(XADES141.SigAndRefsTimeStampV2()) if self.xades_legacy: - elements.append(XADES132.RefsOnlyTimeStamp()) # deprecated (legacy) + elements.append(XADES.RefsOnlyTimeStamp()) # deprecated (legacy) else: elements.append(XADES141.RefsOnlyTimeStampV2()) - elements.append(XADES132.CertificateValues()) + elements.append(XADES.CertificateValues()) - elements.append(XADES132.RevocationValues()) + elements.append(XADES.RevocationValues()) - elements.append(XADES132.AttrAuthoritiesCertValues()) + elements.append(XADES.AttrAuthoritiesCertValues()) - elements.append(XADES132.AttributeRevocationValues()) + elements.append(XADES.AttributeRevocationValues()) - elements.append(XADES132.ArchiveTimeStamp()) + elements.append(XADES.ArchiveTimeStamp()) # any ##other @@ -575,7 +575,7 @@ def _generate_xades_usp_elements(self, options_struct): def _generate_xades_udop_elements(self, options_struct): elements = [] - elements.append(XADES132.UnsignedDataObjectProperty()) + elements.append(XADES.UnsignedDataObjectProperty()) return self._clean_elements_from_black_list(elements) @@ -610,7 +610,7 @@ def _generate_xades(self, options_struct): The Id attribute shall be used to reference the SignedSignatureProperties element. """ sp_elements.append( - XADES132.SignedSignatureProperties( + XADES.SignedSignatureProperties( *ssp_elements, Id=_gen_id(self.dsig_prefix, "signedsigprops") # optional ) ) @@ -622,7 +622,7 @@ def _generate_xades(self, options_struct): The Id attribute shall be used to reference the SignedDataObjectProperties element. """ sp_elements.append( - XADES132.SignedDataObjectProperties( + XADES.SignedDataObjectProperties( *sdop_elements, Id=_gen_id(self.dsig_prefix, "signeddataobjprops") # optional ) ) @@ -636,8 +636,8 @@ def _generate_xades(self, options_struct): """ The Id attribute shall be used to reference the UnignedSignatureProperties element. """ - usp_elements.append( - XADES132.UnsignedSignatureProperties( + up_elements.append( + XADES.UnsignedSignatureProperties( *usp_elements, Id=_gen_id(self.dsig_prefix, "unsignedsigprops") # optional ) ) @@ -649,7 +649,7 @@ def _generate_xades(self, options_struct): The Id attribute shall be used to reference the UnignedDataObjectProperties element. """ usp_elements.append( - XADES132.UnsignedDataObjectProperties( + XADES.UnsignedDataObjectProperties( *udop_elements, Id=_gen_id(self.dsig_prefix, "unsigneddataobjprops") # optional ) ) @@ -664,7 +664,8 @@ def _generate_xades(self, options_struct): """ The Id attribute shall be used to reference the SignedProperties element. """ - sp = XADES132.SignedProperties(*sp_elements, + sp_elements = self._clean_elements_from_black_list(sp_elements) + sp = XADES.SignedProperties(*sp_elements, Id=_gen_id(self.dsig_prefix, "signedprops") # optional ) qp_elements.append(sp) @@ -680,7 +681,6 @@ def _generate_xades(self, options_struct): This ds:Reference element shall include the Type attribute with its value set to: """ - self._add_xades_reference(sp) """ @@ -690,8 +690,9 @@ def _generate_xades(self, options_struct): """ The Id attribute shall be used to reference the UnsignedProperties element. """ + up_elements = self._clean_elements_from_black_list(up_elements) qp_elements.append( - XADES132.UnsignedProperties(*up_elements, + XADES.UnsignedProperties(*up_elements, Id=_gen_id(self.dsig_prefix, "unsignedprops") # optional ) ) @@ -711,6 +712,7 @@ def _generate_xades(self, options_struct): """ if not qp_elements: return None - qp = XADES132.QualifyingProperties(*qp_elements, **qp_attributes) + qp_elements = self._clean_elements_from_black_list(qp_elements) + qp = XADES.QualifyingProperties(*qp_elements, **qp_attributes) self._add_xades_reference(qp) return qp From 0749e5171b8ea5090742999aef056d197c25d32e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Wed, 29 Jan 2020 15:38:31 -0500 Subject: [PATCH 14/19] Oversights --- signxml/__init__.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/signxml/__init__.py b/signxml/__init__.py index 898fa4d..4d559ec 100644 --- a/signxml/__init__.py +++ b/signxml/__init__.py @@ -472,31 +472,39 @@ def _unpack(self, data, reference_uris): reference_uris = ["#object"] return sig_root, doc_root, c14n_inputs, reference_uris - def _pre_build_sig(self, doc_root, sig_root, reference_uris): + def _pre_build_sig(self, doc_root, sig_root, reference_uris, c14n_inputs): """ To overwrite By default, this method add self.refs to reference_uris, elements ready to incorporate into sig_root without changes - Suggested use case: - Set the attribute 'Id' of the element 'Signature' when the - in the _unpack function the attribute is deleted, this will override - the digest value of built references + Use cases: + - Set the attribute 'Id' of the element 'Signature' when the + in the _unpack function the attribute is deleted, this will override + the digest value of built references + - Add the refs and c14_inputs to preserve the validation exmple: - query = self._findall(doc_root, "QualifyingProperties") + query = doc_root.xpath( + f"//{xades_ns}:QualifyingProperties", namespaces=namespaces + ) if len(query) > 1: raise NotImplementedError("Multiple QualifyingProperties") sig_id = query[0].attrib["Target"] if sig_id.startswith("#"): - sig_id = sig_id.replace("#","") + sig_id = sig_id.replace("#", "") sig_root.set("Id", sig_id) + for ref in self.refs: + reference_uris.append(ref) + uri = ref + if etree.iselement(ref): + uri = uri.attrib["Target"] + c14n_inputs.append( + self.get_root(self._resolve_reference(doc_root, {"URI": uri})) + ) """ - - reference_uris += self.refs - - return doc_root, sig_root, reference_uris + return doc_root, sig_root, reference_uris, c14n_inputs def _build_sig(self, sig_root, reference_uris, c14n_inputs): signed_info = SubElement(sig_root, ds_tag("SignedInfo"), nsmap=self.namespaces) From df3849bcdc32dc0808c7a515643b479845470a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Fri, 31 Jan 2020 17:21:24 -0500 Subject: [PATCH 15/19] Improvements --- signxml/__init__.py | 3 ++- signxml/xades/__init__.py | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/signxml/__init__.py b/signxml/__init__.py index 4d559ec..9249264 100644 --- a/signxml/__init__.py +++ b/signxml/__init__.py @@ -2,6 +2,7 @@ from base64 import b64encode, b64decode from enum import Enum +import re from eight import str, bytes from lxml import etree @@ -499,7 +500,7 @@ def _pre_build_sig(self, doc_root, sig_root, reference_uris, c14n_inputs): reference_uris.append(ref) uri = ref if etree.iselement(ref): - uri = uri.attrib["Target"] + uri = uri.attrib["URI"] c14n_inputs.append( self.get_root(self._resolve_reference(doc_root, {"URI": uri})) ) diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index 40d43c6..fbd7109 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -6,6 +6,7 @@ from datetime import datetime import pytz import requests +import re from eight import str, bytes from lxml import etree @@ -239,7 +240,7 @@ def _clean_elements_from_black_list(self, elements): lambda x: isinstance(x, etree._Element) and x.tag not in self.black_list, elements )) - def _add_xades_reference(self, sp): + def _add_xades_reference(self, el, attrs=dict()): """ The refs depends the xmldsig scheme,(https://www.w3.org/TR/xmldsig-core2/#sec-Overview) not the XADES scheme, therefore, those refs have to wait for the general element to be append to @@ -250,7 +251,13 @@ def _add_xades_reference(self, sp): (https://www.w3.org/TR/xmldsig-core2/#sec-o-Reference) This reference is made for compatibility mode v1 anv v2 (https://www.w3.org/TR/xmldsig-core2/#sec-Compatibility-Mode-Examples) + :param el: element + :type el: etree.Element + :param attrs: attributes to set in the element 'DigestValue' + :type attrs: py:class:dict """ + attrs["URI"] = "#" + el.get("Id") + attrs["Type"] = re.sub("[{}]","",el.tag) self.refs.append( DS.Reference( DS.Transforms(DS.Transform(Algorithm=self.default_c14n_algorithm)), @@ -259,12 +266,11 @@ def _add_xades_reference(self, sp): ), DS.DigestValue( self._get_digest( - self._c14n(sp, algorithm=self.c14n_alg), + self._c14n(el, algorithm=self.c14n_alg), self._get_digest_method_by_tag(self.digest_alg) ) ), - Type="http://uri.etsi.org/01903#SignedProperties", - URI="#" + sp.get("Id") + **attrs ) ) From 666bfc0e11a3d20a1f4b95c1e676d09e1eecc663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Mon, 3 Feb 2020 19:38:20 -0500 Subject: [PATCH 16/19] Improvements This commit contains: Description: in the sign process when the references are building, by default the transform with the algorithm for the namespace `ds` is added with the suffix "enveloped-signature" this suffix is only for the reference for the entire document corrupting the signature. To fix the above, the inclusion of the 'doc_root' as second argument in the method 'self._build_sig' helps to identify if the referenced element is the element root of the XML document through the added method `_check_brothers` inside the method `self._build_sig`. The method `_check_brothers` validate if two elements have the same tag returning `True or False` according to --- signxml/__init__.py | 16 +++++++++++++--- signxml/xades/__init__.py | 40 +++++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/signxml/__init__.py b/signxml/__init__.py index 9249264..b832330 100644 --- a/signxml/__init__.py +++ b/signxml/__init__.py @@ -349,7 +349,7 @@ def sign(self, data, key=None, passphrase=None, cert=None, reference_uri=None, k sig_root, doc_root, c14n_inputs, reference_uris = self._unpack(data, reference_uris) doc_root, sig_root, reference_uris, c14n_inputs = self._pre_build_sig(doc_root, sig_root, reference_uris, c14n_inputs) - signed_info_element, signature_value_element = self._build_sig(sig_root, reference_uris, c14n_inputs) + signed_info_element, signature_value_element = self._build_sig(doc_root, sig_root, reference_uris, c14n_inputs) if key is None: raise InvalidInput('Parameter "key" is required') @@ -507,7 +507,16 @@ def _pre_build_sig(self, doc_root, sig_root, reference_uris, c14n_inputs): """ return doc_root, sig_root, reference_uris, c14n_inputs - def _build_sig(self, sig_root, reference_uris, c14n_inputs): + def _build_sig(self, doc_root, sig_root, reference_uris, c14n_inputs): + def _check_brothers(element1, element2): + """helper method to determiante if two elements have the same tag + :param element1: element + :type element1: etree._Element + :param element2: elemnt + :type element2: etree._Element + """ + return doc_root.tag == doc_el.tag + signed_info = SubElement(sig_root, ds_tag("SignedInfo"), nsmap=self.namespaces) c14n_method = SubElement(signed_info, ds_tag("CanonicalizationMethod"), Algorithm=self.c14n_alg) if self.sign_alg.startswith("hmac-"): @@ -523,7 +532,8 @@ def _build_sig(self, sig_root, reference_uris, c14n_inputs): reference = SubElement(signed_info, ds_tag("Reference"), URI=reference_uri) if self.method == methods.enveloped: transforms = SubElement(reference, ds_tag("Transforms")) - SubElement(transforms, ds_tag("Transform"), Algorithm=namespaces.ds + "enveloped-signature") + if _check_brothers(doc_root, c14n_inputs[i]): + SubElement(transforms, ds_tag("Transform"), Algorithm=namespaces.ds + "enveloped-signature") SubElement(transforms, ds_tag("Transform"), Algorithm=self.c14n_alg) digest_method = SubElement(reference, ds_tag("DigestMethod"), Algorithm=self.known_digest_tags[self.digest_alg]) diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index fbd7109..96552ec 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -255,24 +255,28 @@ def _add_xades_reference(self, el, attrs=dict()): :type el: etree.Element :param attrs: attributes to set in the element 'DigestValue' :type attrs: py:class:dict - """ - attrs["URI"] = "#" + el.get("Id") - attrs["Type"] = re.sub("[{}]","",el.tag) - self.refs.append( - DS.Reference( - DS.Transforms(DS.Transform(Algorithm=self.default_c14n_algorithm)), - DS.DigestMethod( - Algorithm=self.known_digest_tags[self.digest_alg] - ), - DS.DigestValue( - self._get_digest( - self._c14n(el, algorithm=self.c14n_alg), - self._get_digest_method_by_tag(self.digest_alg) - ) - ), - **attrs - ) - ) + + Note: PLEASE!! take into account that the digest may differ in the + validation process caused by the transforms and the namespaces applied + """ + self.refs.append("#" + el.get("Id")) + # attrs["URI"] = "#" + el.get("Id") + # attrs["Type"] = re.sub("[{}]","",el.tag) + # self.refs.append( + # DS.Reference( + # DS.Transforms(DS.Transform(Algorithm=self.default_c14n_algorithm)), + # DS.DigestMethod( + # Algorithm=self.known_digest_tags[self.digest_alg] + # ), + # DS.DigestValue( + # self._get_digest( + # self._c14n(el, algorithm=self.c14n_alg), + # self._get_digest_method_by_tag(self.digest_alg) + # ) + # ), + # **attrs + # ) + # ) def _generate_xades_ssp_elements(self, options_struct): From 2a6d5f3c474c7f67b89683c28c17d4e89608562d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Fri, 7 Feb 2020 18:46:05 -0500 Subject: [PATCH 17/19] Checks and improvements This commit contains: - added method `_sort_elements` that ensure the right order of the Signature's child elements - added support for references type `dict` to `_buld_sig` and `_add_xades_reference` methods --- signxml/__init__.py | 38 +++++++++++++++++++++++++++++++++++--- signxml/xades/__init__.py | 28 +++++++--------------------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/signxml/__init__.py b/signxml/__init__.py index b832330..ffe600b 100644 --- a/signxml/__init__.py +++ b/signxml/__init__.py @@ -406,6 +406,10 @@ def sign(self, data, key=None, passphrase=None, cert=None, reference_uri=None, k x509_certificate.text = strip_pem_header(dump_certificate(FILETYPE_PEM, cert)) else: sig_root.append(key_info) + + #ensure the right order of elements + doc_root, sig_root, c14n_inputs = self._sort_elements( + doc_root, sig_root, c14n_inputs) else: raise NotImplementedError() @@ -507,7 +511,19 @@ def _pre_build_sig(self, doc_root, sig_root, reference_uris, c14n_inputs): """ return doc_root, sig_root, reference_uris, c14n_inputs + def _build_sig(self, doc_root, sig_root, reference_uris, c14n_inputs): + """ + :param reference_uris: the references to include o buld. + :cases: + - elements: if the reference_uri is an element, this will be added + without modifications + - dict: if the reference_uri is a dict, the reference element will be + created using the attributes in the dict + i.e: {"URI": URI, "Type": Type, "Id": ID} + - string: if refence_uri is a string, this will be interpreted as the + attribute `URI` + """ def _check_brothers(element1, element2): """helper method to determiante if two elements have the same tag :param element1: element @@ -515,7 +531,7 @@ def _check_brothers(element1, element2): :param element2: elemnt :type element2: etree._Element """ - return doc_root.tag == doc_el.tag + return element1.tag == element2.tag signed_info = SubElement(sig_root, ds_tag("SignedInfo"), nsmap=self.namespaces) c14n_method = SubElement(signed_info, ds_tag("CanonicalizationMethod"), Algorithm=self.c14n_alg) @@ -528,8 +544,12 @@ def _check_brothers(element1, element2): if etree.iselement(reference_uri): # adding built references signed_info.append(reference_uri) - else: - reference = SubElement(signed_info, ds_tag("Reference"), URI=reference_uri) + else: + if isinstance(reference_uri, dict): + reference = SubElement(signed_info, ds_tag("Reference"), **reference_uri) + else: + reference = SubElement(signed_info, ds_tag("Reference"), URI=reference_uri) + if self.method == methods.enveloped: transforms = SubElement(reference, ds_tag("Transforms")) if _check_brothers(doc_root, c14n_inputs[i]): @@ -544,6 +564,18 @@ def _check_brothers(element1, element2): signature_value = SubElement(sig_root, ds_tag("SignatureValue")) return signed_info, signature_value + def _sort_elements(self, doc_root, sig_root, c14n_inputs): + """ + This method is implemented to preserve the order in signature structure + case: If one or more elements are in the element "Signature" before + appending SignedInfo, SignatureValue and KeyInfo, the structure's order + will be corrupted once them have been added. + """ + ds_object = doc_root.xpath("//ds:Object", namespaces=namespaces)[0] + sig_root.insert(len(sig_root), ds_object) + + return doc_root, sig_root, c14n_inputs + def _serialize_key_value(self, key, key_info_element): key_value = SubElement(key_info_element, ds_tag("KeyValue")) if self.sign_alg.startswith("rsa-"): diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index 96552ec..ef7c149 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -240,7 +240,7 @@ def _clean_elements_from_black_list(self, elements): lambda x: isinstance(x, etree._Element) and x.tag not in self.black_list, elements )) - def _add_xades_reference(self, el, attrs=dict()): + def _add_xades_reference(self, el, **attrs): """ The refs depends the xmldsig scheme,(https://www.w3.org/TR/xmldsig-core2/#sec-Overview) not the XADES scheme, therefore, those refs have to wait for the general element to be append to @@ -259,25 +259,11 @@ def _add_xades_reference(self, el, attrs=dict()): Note: PLEASE!! take into account that the digest may differ in the validation process caused by the transforms and the namespaces applied """ - self.refs.append("#" + el.get("Id")) - # attrs["URI"] = "#" + el.get("Id") - # attrs["Type"] = re.sub("[{}]","",el.tag) - # self.refs.append( - # DS.Reference( - # DS.Transforms(DS.Transform(Algorithm=self.default_c14n_algorithm)), - # DS.DigestMethod( - # Algorithm=self.known_digest_tags[self.digest_alg] - # ), - # DS.DigestValue( - # self._get_digest( - # self._c14n(el, algorithm=self.c14n_alg), - # self._get_digest_method_by_tag(self.digest_alg) - # ) - # ), - # **attrs - # ) - # ) - + if attrs: + attrs["URI"] = "#" + el.get("Id") + self.refs.append(attrs) + else: + self.refs.append("#" + el.get("Id")) def _generate_xades_ssp_elements(self, options_struct): """ @@ -691,7 +677,7 @@ def _generate_xades(self, options_struct): This ds:Reference element shall include the Type attribute with its value set to: """ - self._add_xades_reference(sp) + self._add_xades_reference(sp, {"Type": "http://uri.etsi.org/01903#SignedProperties"}) """ A XAdES signature shall not incorporate empty UnsignedProperties elements. From 2676f793030bdd7ad16f5905cb239f09f5baa70b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Mon, 10 Feb 2020 09:27:55 -0500 Subject: [PATCH 18/19] Oversights --- signxml/xades/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index ef7c149..d1065b0 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -677,7 +677,7 @@ def _generate_xades(self, options_struct): This ds:Reference element shall include the Type attribute with its value set to: """ - self._add_xades_reference(sp, {"Type": "http://uri.etsi.org/01903#SignedProperties"}) + self._add_xades_reference(sp, **{"Type": "http://uri.etsi.org/01903#SignedProperties"}) """ A XAdES signature shall not incorporate empty UnsignedProperties elements. From 13d369689eca1ccfc457fc3545ff95a3c4233303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deiber=20Rinc=C3=B3n?= Date: Mon, 10 Feb 2020 09:39:57 -0500 Subject: [PATCH 19/19] Fix oversights --- signxml/xades/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/signxml/xades/__init__.py b/signxml/xades/__init__.py index d1065b0..89ea4c3 100644 --- a/signxml/xades/__init__.py +++ b/signxml/xades/__init__.py @@ -240,7 +240,7 @@ def _clean_elements_from_black_list(self, elements): lambda x: isinstance(x, etree._Element) and x.tag not in self.black_list, elements )) - def _add_xades_reference(self, el, **attrs): + def _add_xades_reference(self, el, attrs={}): """ The refs depends the xmldsig scheme,(https://www.w3.org/TR/xmldsig-core2/#sec-Overview) not the XADES scheme, therefore, those refs have to wait for the general element to be append to @@ -677,7 +677,7 @@ def _generate_xades(self, options_struct): This ds:Reference element shall include the Type attribute with its value set to: """ - self._add_xades_reference(sp, **{"Type": "http://uri.etsi.org/01903#SignedProperties"}) + self._add_xades_reference(sp, {"Type": "http://uri.etsi.org/01903#SignedProperties"}) """ A XAdES signature shall not incorporate empty UnsignedProperties elements.