Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-signxml for openSUSE:Factory checked in at 2026-02-17 16:47:07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-signxml (Old) and /work/SRC/openSUSE:Factory/.python-signxml.new.1977 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-signxml" Tue Feb 17 16:47:07 2026 rev:2 rq:1333380 version:4.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-signxml/python-signxml.changes 2025-09-03 21:08:30.983387587 +0200 +++ /work/SRC/openSUSE:Factory/.python-signxml.new.1977/python-signxml.changes 2026-02-17 16:47:35.864057753 +0100 @@ -1,0 +2,7 @@ +Mon Feb 16 15:06:38 UTC 2026 - Dirk Müller <[email protected]> + +- update to 4.3.0: + * Remove registration for ec.SECT* ECDSA curves + * Fix key info matching behavior + +------------------------------------------------------------------- Old: ---- signxml-4.2.0.tar.gz New: ---- signxml-4.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-signxml.spec ++++++ --- /var/tmp/diff_new_pack.lmsijK/_old 2026-02-17 16:47:40.356246240 +0100 +++ /var/tmp/diff_new_pack.lmsijK/_new 2026-02-17 16:47:40.360246408 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-signxml # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: python-signxml -Version: 4.2.0 +Version: 4.3.0 Release: 0 Summary: Python XML Signature and XAdES library License: Apache-2.0 ++++++ signxml-4.2.0.tar.gz -> signxml-4.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/signxml-4.2.0/.github/workflows/ci.yml new/signxml-4.3.0/.github/workflows/ci.yml --- old/signxml-4.2.0/.github/workflows/ci.yml 2020-02-02 01:00:00.000000000 +0100 +++ new/signxml-4.3.0/.github/workflows/ci.yml 2020-02-02 01:00:00.000000000 +0100 @@ -8,8 +8,8 @@ strategy: max-parallel: 8 matrix: - os: [ubuntu-22.04, ubuntu-latest, macos-13, macos-latest] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + os: [ubuntu-22.04, ubuntu-latest, macos-14, macos-latest] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/signxml-4.2.0/Changes.rst new/signxml-4.3.0/Changes.rst --- old/signxml-4.2.0/Changes.rst 2020-02-02 01:00:00.000000000 +0100 +++ new/signxml-4.3.0/Changes.rst 2020-02-02 01:00:00.000000000 +0100 @@ -1,3 +1,22 @@ +Changes for v4.3.0 (2026-02-14) +=============================== + +- Remove registration for ec.SECT\* ECDSA curves (#289) + +- Fix key info matching behavior (#287) + +Changes for v4.2.2 (2026-01-21) +=============================== + +- Support default namespace with no xmlns=“” undeclarations in + sign/verify round-trip (#286) + +Changes for v4.2.1 (2026-01-18) +=============================== + +- Add legacy SigningCertificate with IssuerSerial for XAdES + interoperability (#282) + Changes for v4.2.0 (2025-08-19) =============================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/signxml-4.2.0/PKG-INFO new/signxml-4.3.0/PKG-INFO --- old/signxml-4.2.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 +++ new/signxml-4.3.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: signxml -Version: 4.2.0 +Version: 4.3.0 Summary: Python XML Signature and XAdES library Project-URL: Homepage, https://github.com/XML-Security/signxml Project-URL: Documentation, https://xml-security.github.io/signxml/ @@ -21,17 +21,16 @@ Classifier: Operating System :: POSIX Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Libraries :: Python Modules -Requires-Python: >=3.8 +Requires-Python: >=3.9 Requires-Dist: certifi>=2023.11.17 Requires-Dist: cryptography>=43 Requires-Dist: lxml<7,>=5.2.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/signxml-4.2.0/pyproject.toml new/signxml-4.3.0/pyproject.toml --- old/signxml-4.2.0/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 +++ new/signxml-4.3.0/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 @@ -2,7 +2,7 @@ name = "signxml" description = "Python XML Signature and XAdES library" readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "Apache Software License" } authors = [{ name = "Andrey Kislyuk"}, {email = "[email protected]" }] maintainers = [{ name = "Andrey Kislyuk"}, {email = "[email protected]" }] @@ -14,12 +14,11 @@ "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Development Status :: 5 - Production/Stable", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/signxml-4.2.0/signxml/processor.py new/signxml-4.3.0/signxml/processor.py --- old/signxml-4.2.0/signxml/processor.py 2020-02-02 01:00:00.000000000 +0100 +++ new/signxml-4.3.0/signxml/processor.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,7 @@ +import importlib.resources import logging -import os +import threading +from functools import lru_cache from typing import Any, List, Tuple from xml.etree import ElementTree as stdlibElementTree @@ -14,18 +16,24 @@ logger = logging.getLogger(__name__) +@lru_cache +def get_schema(schema_file: str) -> etree.XMLSchema: + pkg_name = "signxml.xades.schemas" if schema_file.startswith("XAdES") else "signxml.schemas" + with importlib.resources.open_text(pkg_name, schema_file) as schema_fh: + return etree.XMLSchema(etree.parse(schema_fh)) + + class XMLProcessor: _schemas: List[Any] = [] schema_files: List[Any] = [] _default_parser, _parser = None, None - _schema_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), "schemas")) @classmethod def schemas(cls): - if len(cls._schemas) == 0: - for schema_file in cls.schema_files: - schema_path = os.path.join(cls._schema_dir, schema_file) - cls._schemas.append(etree.XMLSchema(etree.parse(schema_path))) + with threading.Lock(): + if len(cls._schemas) == 0: + for schema_file in cls.schema_files: + cls._schemas.append(get_schema(schema_file)) return cls._schemas @property @@ -62,19 +70,22 @@ schema_files = ["xmldsig1-schema.xsd"] # See https://tools.ietf.org/html/rfc5656 + # ec.SECT* curves have been de-registered due to CVE-2026-26007 + # (https://github.com/pyca/cryptography/security/advisories/GHSA-r6ph-v2qm-q3c2) + # List of disabled curves: + # "urn:oid:1.3.132.0.1": ec.SECT163K1, + # "urn:oid:1.3.132.0.26": ec.SECT233K1, + # "urn:oid:1.3.132.0.27": ec.SECT233R1, + # "urn:oid:1.3.132.0.16": ec.SECT283R1, + # "urn:oid:1.3.132.0.36": ec.SECT409K1, + # "urn:oid:1.3.132.0.37": ec.SECT409R1, + # "urn:oid:1.3.132.0.38": ec.SECT571K1 known_ecdsa_curves = { "urn:oid:1.2.840.10045.3.1.7": ec.SECP256R1, "urn:oid:1.3.132.0.34": ec.SECP384R1, "urn:oid:1.3.132.0.35": ec.SECP521R1, - "urn:oid:1.3.132.0.1": ec.SECT163K1, "urn:oid:1.2.840.10045.3.1.1": ec.SECP192R1, "urn:oid:1.3.132.0.33": ec.SECP224R1, - "urn:oid:1.3.132.0.26": ec.SECT233K1, - "urn:oid:1.3.132.0.27": ec.SECT233R1, - "urn:oid:1.3.132.0.16": ec.SECT283R1, - "urn:oid:1.3.132.0.36": ec.SECT409K1, - "urn:oid:1.3.132.0.37": ec.SECT409R1, - "urn:oid:1.3.132.0.38": ec.SECT571K1, } known_ecdsa_curve_oids = {ec().name: oid for oid, ec in known_ecdsa_curves.items()} # type: ignore[abstract] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/signxml-4.2.0/signxml/signer.py new/signxml-4.3.0/signxml/signer.py --- old/signxml-4.2.0/signxml/signer.py 2020-02-02 01:00:00.000000000 +0100 +++ new/signxml-4.3.0/signxml/signer.py 2020-02-02 01:00:00.000000000 +0100 @@ -7,7 +7,7 @@ from cryptography.hazmat.primitives.asymmetric.padding import MGF1, PSS, PKCS1v15 from cryptography.hazmat.primitives.hmac import HMAC from cryptography.hazmat.primitives.serialization import Encoding, load_pem_private_key -from lxml.etree import Element, SubElement, _Element +from lxml.etree import Element, QName, SubElement, _Element from .algorithms import ( CanonicalizationMethod, @@ -117,6 +117,20 @@ self._parser = None self.signature_annotators = [self._add_key_info] + def _ds_tag(self, tag): + """ + Create a QName for the ds namespace, respecting the configured namespace mapping. + + When the default namespace is set to the ds namespace ({None: namespaces.ds}), + elements should be created without an explicit namespace so they inherit from + the nsmap context. This avoids spurious xmlns="" undeclarations in C14N output. + + See https://github.com/XML-Security/signxml/issues/275 + """ + if None in self.namespaces and self.namespaces[None] == namespaces.ds: # type:ignore[index] + return QName(None, tag) + return ds_tag(tag) + def check_deprecated_methods(self): if "SHA1" in self.sign_alg.name or "SHA1" in self.digest_alg.name: msg = "SHA1-based algorithms are not supported in the default configuration because they are not secure" @@ -304,9 +318,9 @@ if self.sign_alg.name.startswith("HMAC_"): return if signing_settings.key_info is None: - key_info = SubElement(sig_root, ds_tag("KeyInfo")) + key_info = SubElement(sig_root, self._ds_tag("KeyInfo")) if signing_settings.key_name is not None: - keyname = SubElement(key_info, ds_tag("KeyName")) + keyname = SubElement(key_info, self._ds_tag("KeyName")) keyname.text = signing_settings.key_name if signing_settings.cert_chain is None or signing_settings.always_add_key_value: @@ -314,9 +328,9 @@ if signing_settings.cert_chain is not None: assert len(signing_settings.cert_chain) > 0 - x509_data = SubElement(key_info, ds_tag("X509Data")) + x509_data = SubElement(key_info, self._ds_tag("X509Data")) for cert in signing_settings.cert_chain: - x509_certificate = SubElement(x509_data, ds_tag("X509Certificate")) + x509_certificate = SubElement(x509_data, self._ds_tag("X509Certificate")) if isinstance(cert, (str, bytes)): x509_certificate.text = strip_pem_header(cert) else: @@ -333,7 +347,7 @@ return c14n_inputs, new_references def _unpack(self, data, references: List[SignatureReference]): - sig_root = Element(ds_tag("Signature"), nsmap=self.namespaces) + sig_root = Element(self._ds_tag("Signature"), nsmap=self.namespaces) if self.construction_method == SignatureConstructionMethod.enveloped: if isinstance(data, (str, bytes)): raise InvalidInput("When using enveloped signature, **data** must be an XML element") @@ -376,7 +390,7 @@ c14n_inputs = [self.get_root(data)] elif self.construction_method == SignatureConstructionMethod.enveloping: doc_root = sig_root - c14n_inputs = [Element(ds_tag("Object"), nsmap=self.namespaces, Id="object")] + c14n_inputs = [Element(self._ds_tag("Object"), nsmap=self.namespaces, Id="object")] if isinstance(data, (str, bytes)): c14n_inputs[0].text = data else: @@ -389,14 +403,16 @@ ): assert reference.c14n_method is not None if self.construction_method == SignatureConstructionMethod.enveloped: - SubElement(transforms_node, ds_tag("Transform"), Algorithm=SignatureConstructionMethod.enveloped.value) + SubElement( + transforms_node, self._ds_tag("Transform"), Algorithm=SignatureConstructionMethod.enveloped.value + ) if not exclude_c14n_transform_element: - SubElement(transforms_node, ds_tag("Transform"), Algorithm=reference.c14n_method.value) + SubElement(transforms_node, self._ds_tag("Transform"), Algorithm=reference.c14n_method.value) else: if not exclude_c14n_transform_element: c14n_xform = SubElement( transforms_node, - ds_tag("Transform"), + self._ds_tag("Transform"), Algorithm=reference.c14n_method.value, ) if reference.inclusive_ns_prefixes: @@ -407,41 +423,41 @@ def _build_sig( self, sig_root, references, c14n_inputs, inclusive_ns_prefixes, exclude_c14n_transform_element=False ): - signed_info = SubElement(sig_root, ds_tag("SignedInfo"), nsmap=self.namespaces) - sig_c14n_method = SubElement(signed_info, ds_tag("CanonicalizationMethod"), Algorithm=self.c14n_alg.value) + signed_info = SubElement(sig_root, self._ds_tag("SignedInfo"), nsmap=self.namespaces) + sig_c14n_method = SubElement(signed_info, self._ds_tag("CanonicalizationMethod"), Algorithm=self.c14n_alg.value) if inclusive_ns_prefixes: SubElement(sig_c14n_method, ec_tag("InclusiveNamespaces"), PrefixList=" ".join(inclusive_ns_prefixes)) - SubElement(signed_info, ds_tag("SignatureMethod"), Algorithm=self.sign_alg.value) + SubElement(signed_info, self._ds_tag("SignatureMethod"), Algorithm=self.sign_alg.value) for i, reference in enumerate(references): if reference.c14n_method is None: reference = replace(reference, c14n_method=self.c14n_alg) if reference.inclusive_ns_prefixes is None: reference = replace(reference, inclusive_ns_prefixes=inclusive_ns_prefixes) - reference_node = SubElement(signed_info, ds_tag("Reference"), URI=reference.URI) - transforms = SubElement(reference_node, ds_tag("Transforms")) + reference_node = SubElement(signed_info, self._ds_tag("Reference"), URI=reference.URI) + transforms = SubElement(reference_node, self._ds_tag("Transforms")) self._build_transforms_for_reference( transforms_node=transforms, reference=reference, exclude_c14n_transform_element=exclude_c14n_transform_element, ) - SubElement(reference_node, ds_tag("DigestMethod"), Algorithm=self.digest_alg.value) - digest_value = SubElement(reference_node, ds_tag("DigestValue")) + SubElement(reference_node, self._ds_tag("DigestMethod"), Algorithm=self.digest_alg.value) + digest_value = SubElement(reference_node, self._ds_tag("DigestValue")) payload_c14n = self._c14n( c14n_inputs[i], algorithm=reference.c14n_method, inclusive_ns_prefixes=reference.inclusive_ns_prefixes ) digest = self._get_digest(payload_c14n, algorithm=self.digest_alg) digest_value.text = b64encode(digest).decode() - signature_value = SubElement(sig_root, ds_tag("SignatureValue")) + signature_value = SubElement(sig_root, self._ds_tag("SignatureValue")) return signed_info, signature_value def _build_signature_properties(self, signature_properties): # FIXME: make this use the annotator API - obj = Element(ds_tag("Object"), attrib={"Id": "prop"}, nsmap=self.namespaces) - signature_properties_el = Element(ds_tag("SignatureProperties")) + obj = Element(self._ds_tag("Object"), attrib={"Id": "prop"}, nsmap=self.namespaces) + signature_properties_el = Element(self._ds_tag("SignatureProperties")) for i, el in enumerate(signature_properties): signature_property = Element( - ds_tag("SignatureProperty"), + self._ds_tag("SignatureProperty"), attrib={ "Id": el.attrib.pop("Id", f"sigprop{i}"), "Target": el.attrib.pop("Target", f"#sigproptarget{i}"), @@ -456,17 +472,17 @@ """ Add the public components of the key to the signature (see https://www.w3.org/TR/xmldsig-core2/#sec-KeyValue). """ - key_value = SubElement(key_info_node, ds_tag("KeyValue")) + key_value = SubElement(key_info_node, self._ds_tag("KeyValue")) if self.sign_alg.name.startswith("RSA_") or self.sign_alg.name.startswith("SHA"): - rsa_key_value = SubElement(key_value, ds_tag("RSAKeyValue")) - modulus = SubElement(rsa_key_value, ds_tag("Modulus")) + rsa_key_value = SubElement(key_value, self._ds_tag("RSAKeyValue")) + modulus = SubElement(rsa_key_value, self._ds_tag("Modulus")) modulus.text = b64encode(long_to_bytes(key.public_key().public_numbers().n)).decode() - exponent = SubElement(rsa_key_value, ds_tag("Exponent")) + exponent = SubElement(rsa_key_value, self._ds_tag("Exponent")) exponent.text = b64encode(long_to_bytes(key.public_key().public_numbers().e)).decode() elif self.sign_alg.name.startswith("DSA_"): - dsa_key_value = SubElement(key_value, ds_tag("DSAKeyValue")) + dsa_key_value = SubElement(key_value, self._ds_tag("DSAKeyValue")) for field in "p", "q", "g", "y": - e = SubElement(dsa_key_value, ds_tag(field.upper())) + e = SubElement(dsa_key_value, self._ds_tag(field.upper())) if field == "y": key_params = key.public_key().public_numbers() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/signxml-4.2.0/signxml/verifier.py new/signxml-4.3.0/signxml/verifier.py --- old/signxml-4.2.0/signxml/verifier.py 2020-02-02 01:00:00.000000000 +0100 +++ new/signxml-4.3.0/signxml/verifier.py 2020-02-02 01:00:00.000000000 +0100 @@ -82,9 +82,9 @@ Ignore the presence of a KeyValue element when X509Data is present in the signature and used for verifying. The presence of both elements is an ambiguity and a security hazard. The public key used to sign the document is already encoded in the certificate (which is in X509Data), so the verifier must either ignore - KeyValue or make sure it matches what's in the certificate. SignXML does not implement the functionality - necessary to match the keys, and throws an InvalidInput error instead. Set this to True to bypass the error - and validate the signature using X509Data only. + KeyValue or make sure it matches what's in the certificate. When set to ``False``, SignXML compares KeyValue + (and DEREncodedKeyValue) against the X.509 certificate and raises InvalidInput on mismatch. Set this to + ``True`` to bypass the check and validate the signature using X509Data only. """ default_reference_c14n_method: CanonicalizationMethod = CanonicalizationMethod.CANONICAL_XML_1_1 @@ -261,7 +261,7 @@ return X509CertChainVerifier(ca_pem_file=ca_pem_file) def _match_key_values(self, key_value, der_encoded_key_value, signing_cert, signature_alg): - if self.config.ignore_ambiguous_key_info is False: + if self.config.ignore_ambiguous_key_info is True: return cert_pub_key = signing_cert.public_key() # If both X509Data and KeyValue are present, match one against the other and raise an error on mismatch diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/signxml-4.2.0/signxml/xades/xades.py new/signxml-4.3.0/signxml/xades/xades.py --- old/signxml-4.2.0/signxml/xades/xades.py 2020-02-02 01:00:00.000000000 +0100 +++ new/signxml-4.3.0/signxml/xades/xades.py 2020-02-02 01:00:00.000000000 +0100 @@ -98,6 +98,8 @@ Parameters to pass to the :class:`signxml.XMLSigner` constructor. """ + use_deprecated_legacy_signing_certificate: bool = False + def __init__( self, signature_policy: Optional[XAdESSignaturePolicy] = None, @@ -190,7 +192,7 @@ signing_time.text = datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds") def add_signing_certificate(self, signed_signature_properties, sig_root, signing_settings: SigningSettings): - # TODO: check if we need to support SigningCertificate + # Emit both legacy SigningCertificate (SHA1 + IssuerSerial) and SigningCertificateV2 signing_cert_v2 = SubElement( signed_signature_properties, xades_tag("SigningCertificateV2"), nsmap=self.namespaces ) @@ -202,6 +204,33 @@ loaded_cert = x509.load_pem_x509_certificate(add_pem_header(cert)) der_encoded_cert = loaded_cert.public_bytes(Encoding.DER) cert_digest_bytes = self._get_digest(der_encoded_cert, algorithm=self.digest_alg) + + # Legacy SigningCertificate + if self.use_deprecated_legacy_signing_certificate: + cert_digest_sha1_bytes = self._get_digest(der_encoded_cert, algorithm=DigestAlgorithm.SHA1) + signing_cert = SubElement( + signed_signature_properties, xades_tag("SigningCertificate"), nsmap=self.namespaces + ) + + cert_node_legacy = SubElement(signing_cert, xades_tag("Cert"), nsmap=self.namespaces) + cert_digest = SubElement(cert_node_legacy, xades_tag("CertDigest"), nsmap=self.namespaces) + SubElement( + cert_digest, ds_tag("DigestMethod"), nsmap=self.namespaces, Algorithm=DigestAlgorithm.SHA1.value + ) + digest_value_node = SubElement(cert_digest, ds_tag("DigestValue"), nsmap=self.namespaces) + digest_value_node.text = b64encode(cert_digest_sha1_bytes).decode() + issuer_serial = SubElement(cert_node_legacy, xades_tag("IssuerSerial"), nsmap=self.namespaces) + issuer_name = SubElement(issuer_serial, ds_tag("X509IssuerName"), nsmap=self.namespaces) + issuer_name.text = "C={C},O={O},OU={OU},CN={CN}".format( # type:ignore[str-bytes-safe] + C=loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.COUNTRY_NAME)[0].value, + O=loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.ORGANIZATION_NAME)[0].value, + OU=loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.ORGANIZATIONAL_UNIT_NAME)[0].value, + CN=loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)[0].value, + ) + serial_number = SubElement(issuer_serial, ds_tag("X509SerialNumber"), nsmap=self.namespaces) + serial_number.text = str(loaded_cert.serial_number) + + # SigningCertificateV2 (current default) cert_node = SubElement(signing_cert_v2, xades_tag("Cert"), nsmap=self.namespaces) cert_digest = SubElement(cert_node, xades_tag("CertDigest"), nsmap=self.namespaces) SubElement(cert_digest, ds_tag("DigestMethod"), nsmap=self.namespaces, Algorithm=self.digest_alg.value) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/signxml-4.2.0/test/test.py new/signxml-4.3.0/test/test.py --- old/signxml-4.2.0/test/test.py 2020-02-02 01:00:00.000000000 +0100 +++ new/signxml-4.3.0/test/test.py 2020-02-02 01:00:00.000000000 +0100 @@ -479,6 +479,29 @@ expected_match = f'<Signature xmlns="{namespaces["ds"]}">' self.assertTrue(re.search(expected_match.encode("ascii"), signed_data)) + def test_sign_verify_default_ns_roundtrip(self): + """ + Test sign/verify round-trip when using default namespace. + + See https://github.com/XML-Security/signxml/issues/275 + """ + crt, key = self.load_example_keys() + data = etree.parse(self.example_xml_files[0]).getroot() + signer = XMLSigner() + signer.namespaces = {None: namespaces["ds"]} + signed = signer.sign(data, key=key, cert=crt) + + signed_info = signed.find(".//SignedInfo") + self.assertIsNotNone(signed_info) + c14n_output = etree.tostring(signed_info, method="c14n").decode() + self.assertNotIn('xmlns=""', c14n_output) + self.assertIn('xmlns="http://www.w3.org/2000/09/xmldsig#"', c14n_output) + + signed_data = etree.tostring(signed) + verifier = XMLVerifier() + verifier.excise_empty_xmlns_declarations = True + verifier.verify(signed_data, x509_cert=crt) + def test_elementtree_compat(self): data = stdlibElementTree.parse(self.example_xml_files[0]).getroot() signer = XMLSigner() @@ -625,6 +648,26 @@ def test_excision_of_untrusted_comments(self): pass # TODO: test comments excision + def test_mismatched_key_value_with_x509_data(self): + crt, key = self.load_example_keys() + data = etree.parse(os.path.join(os.path.dirname(__file__), "example.xml")).getroot() + signer = XMLSigner() + signed = signer.sign(data, key=key, cert=crt, always_add_key_value=True) + key_info = signed.find(".//ds:KeyInfo", namespaces=namespaces) + key_value = key_info.find("ds:KeyValue", namespaces=namespaces) + key_info.remove(key_value) + mismatched_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + signer._serialize_key_value(mismatched_key, key_info) + signed_xml = etree.tostring(signed) + + with self.assertRaisesRegex( + InvalidInput, + "Both X509Data and KeyValue found and they represent different public keys", + ): + XMLVerifier().verify(signed_xml, x509_cert=crt) + + XMLVerifier().verify(signed_xml, x509_cert=crt, ignore_ambiguous_key_info=True) + def test_ws_security(self): wsse_dir = os.path.join(interop_dir, "ws-security", "ws.js") with open(os.path.join(wsse_dir, "examples", "server_public.pem"), "rb") as fh:
