Skip to content

Commit 82ae152

Browse files
committed
Post-3.0 improvements
- Mark SHA1 as deprecated - Aggregate verification settings in dataclass - Mark all dataclasses in API as frozen - Add ability to assert expected signature location - Add ability to assert expected signature algorithms - Add ability to assert expected digest algorithms - Add MGF1 ("RSASSA-PSS without parameters") algorithm identifiers - Remove PSS ("RSASSA-PSS with parameters") and EdDSA algorithm identifiers (given low usage and no interop examples, we will not be implementing PSS parameters for now; EdDSA key info additionally has no standardized way to serialize it) - Add debug logging of canonicalization outputs - Documentation and formatting improvements
1 parent 9794814 commit 82ae152

File tree

11 files changed

+286
-139
lines changed

11 files changed

+286
-139
lines changed

README.rst

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
SignXML: XML Signature in Python
2-
================================
1+
SignXML: XML Signature and XAdES in Python
2+
==========================================
33

44
*SignXML* is an implementation of the W3C `XML Signature <http://en.wikipedia.org/wiki/XML_Signature>`_ standard in
55
Python. This standard (also known as XMLDSig and `RFC 3275 <http://www.ietf.org/rfc/rfc3275.txt>`_) is used to provide
@@ -23,7 +23,7 @@ of the Version 1.1 standard, and most recommended ones. Its features are:
2323
`cryptography <https://github.com/pyca/cryptography>`_, `pyOpenSSL <https://github.com/pyca/pyopenssl>`_
2424
* Comprehensive testing (including the XMLDSig interoperability suite) and `continuous integration
2525
<https://github.com/XML-Security/signxml/actions>`_
26-
* Simple interface with useful defaults
26+
* Simple interface with useful, ergonomic, and secure defaults (no network calls, XSLT or XPath transforms)
2727
* Compactness, readability, and extensibility
2828

2929
Installation
@@ -108,7 +108,10 @@ Assuming ``metadata.xml`` contains SAML metadata for the assertion source:
108108
data returned by the ``verify()`` method. The ``signed_xml`` attribute of the return value is the XML node or string that
109109
was signed.
110110

111-
**Recommended reading:** `W3C XML Signature Best Practices for Applications <http://www.w3.org/TR/xmldsig-bestpractices/#practices-applications>`_, `On Breaking SAML: Be Whoever You Want to Be <https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final91.pdf>`_, `Duo Finds SAML Vulnerabilities Affecting Multiple Implementations <https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations>`_
111+
**Recommended reading:** `W3C XML Signature Best Practices for Applications
112+
<http://www.w3.org/TR/xmldsig-bestpractices/#practices-applications>`_, `On Breaking SAML: Be Whoever You Want to Be
113+
<https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final91.pdf>`_, `Duo Finds SAML Vulnerabilities
114+
Affecting Multiple Implementations <https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations>`_
112115

113116
.. admonition:: Establish trust
114117

@@ -167,7 +170,8 @@ parsing, and use lxml to work with untrusted XML input in general. If you do pas
167170
SignXML, you should be aware of differences in XML namespace handling between the two libraries. See the following
168171
references for more information:
169172

170-
* `How do I use lxml safely as a web-service endpoint? <https://lxml.de/FAQ.html#how-do-i-use-lxml-safely-as-a-web-service-endpoint>`_
173+
* `How do I use lxml safely as a web-service endpoint?
174+
<https://lxml.de/FAQ.html#how-do-i-use-lxml-safely-as-a-web-service-endpoint>`_
171175
* `ElementTree compatibility of lxml.etree <https://lxml.de/compatibility.html>`_
172176
* `XML Signatures with Python ElementTree <https://technotes.shemyak.com/posts/xml-signatures-with-python-elementtree>`_
173177

@@ -226,11 +230,14 @@ Links
226230
* `List of W3C XML Signature standards and drafts <https://www.w3.org/TR/?title=xml%20signature>`_
227231
* `W3C Recommendation: XML Signature Syntax and Processing Version 1.1 <http://www.w3.org/TR/xmldsig-core1>`_
228232
* `W3C Working Group Note: XML Signature Syntax and Processing Version 2.0 <http://www.w3.org/TR/xmldsig-core2>`_
229-
* `W3C Working Group Note: XML Security 2.0 Requirements and Design Considerations <https://www.w3.org/TR/2013/NOTE-xmlsec-reqs2-20130411/>`_
233+
* `W3C Working Group Note: XML Security 2.0 Requirements and Design Considerations
234+
<https://www.w3.org/TR/2013/NOTE-xmlsec-reqs2-20130411/>`_
230235
* `W3C Working Group Note: XML Signature Best Practices <http://www.w3.org/TR/xmldsig-bestpractices/>`_
231236
* `XML-Signature Interoperability <http://www.w3.org/Signature/2001/04/05-xmldsig-interop.html>`_
232237
* `W3C Working Group Note: Test Cases for C14N 1.1 and XMLDSig Interoperability <http://www.w3.org/TR/xmldsig2ed-tests/>`_
233238
* `RFC 9231: Additional XML Security Uniform Resource Identifiers (URIs) <https://www.rfc-editor.org/rfc/rfc9231.html>`_
239+
* `Intelligence Community Technical Specification: Web Service Security Guidance for Use of XML Signature and XML
240+
Encryption <https://github.com/XML-Security/signxml/blob/develop/docs/dni-guidance.pdf>`_
234241
* `XMLSec: Related links <https://www.aleksey.com/xmlsec/related.html>`_
235242
* `OWASP SAML Security Cheat Sheet <https://www.owasp.org/index.php/SAML_Security_Cheat_Sheet>`_
236243
* `Okta Developer Docs: SAML <https://developer.okta.com/standards/SAML/>`_

docs/conf.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
pygments_style = "sphinx"
1414
autodoc_member_order = "bysource"
1515
autodoc_typehints = "description"
16-
typehints_fully_qualified = True
17-
always_document_param_types = True
16+
autodoc_typehints_description_target = "documented_params"
1817
intersphinx_mapping = {
1918
"https://docs.python.org/3": None,
2019
"https://lxml.de/apidoc": "https://lxml.de/apidoc/objects.inv",

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
license="Apache Software License",
1010
author="Andrey Kislyuk",
1111
author_email="[email protected]",
12-
description="Python XML Signature library",
12+
description="Python XML Signature and XAdES library",
1313
long_description=open("README.rst").read(),
1414
python_requires=">=3.7",
1515
install_requires=[

signxml/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
See `SignXML documentation <#synopsis>`_ for examples.
44
"""
55

6-
from .signer import XMLSigner, XMLSignatureReference
7-
from .verifier import XMLVerifier, VerifyResult
6+
from .signer import XMLSigner, SignatureReference
7+
from .verifier import XMLVerifier, VerifyResult, SignatureConfiguration
88
from .algorithms import DigestAlgorithm, SignatureMethod, CanonicalizationMethod, SignatureConstructionMethod
99
from .exceptions import InvalidCertificate, InvalidDigest, InvalidInput, InvalidSignature
1010
from .processor import XMLSignatureProcessor

signxml/algorithms.py

+49-10
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ class DigestAlgorithm(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
5757
<http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of the XML Signature 1.1 standard for details.
5858
"""
5959

60-
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
6160
SHA224 = "http://www.w3.org/2001/04/xmldsig-more#sha224"
6261
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
6362
SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
@@ -67,6 +66,12 @@ class DigestAlgorithm(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
6766
SHA3_384 = "http://www.w3.org/2007/05/xmldsig-more#sha3-384"
6867
SHA3_512 = "http://www.w3.org/2007/05/xmldsig-more#sha3-512"
6968

69+
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
70+
"""
71+
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
72+
Support for their algorithm identifiers is deprecated and will be removed in a future release.
73+
"""
74+
7075
@property
7176
def implementation(self) -> Callable:
7277
"""
@@ -83,10 +88,6 @@ class SignatureMethod(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
8388
<http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of the XML Signature 1.1 standard for details.
8489
"""
8590

86-
DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
87-
HMAC_SHA1 = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
88-
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
89-
ECDSA_SHA1 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"
9091
ECDSA_SHA224 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224"
9192
ECDSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"
9293
ECDSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384"
@@ -99,14 +100,45 @@ class SignatureMethod(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
99100
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
100101
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
101102
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
102-
RSA_PSS = "http://www.w3.org/2007/05/xmldsig-more#rsa-pss"
103103
DSA_SHA256 = "http://www.w3.org/2009/xmldsig11#dsa-sha256"
104104
ECDSA_SHA3_224 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-224"
105105
ECDSA_SHA3_256 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-256"
106106
ECDSA_SHA3_384 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-384"
107107
ECDSA_SHA3_512 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-512"
108-
EDDSA_ED25519 = "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519"
109-
EDDSA_ED448 = "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448"
108+
SHA3_224_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-224-rsa-MGF1"
109+
SHA3_256_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-256-rsa-MGF1"
110+
SHA3_384_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-384-rsa-MGF1"
111+
SHA3_512_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-512-rsa-MGF1"
112+
SHA224_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha224-rsa-MGF1"
113+
SHA256_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1"
114+
SHA384_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1"
115+
SHA512_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1"
116+
117+
DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
118+
"""
119+
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
120+
Support for their algorithm identifiers is deprecated and will be removed in a future release.
121+
"""
122+
HMAC_SHA1 = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
123+
"""
124+
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
125+
Support for their algorithm identifiers is deprecated and will be removed in a future release.
126+
"""
127+
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
128+
"""
129+
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
130+
Support for their algorithm identifiers is deprecated and will be removed in a future release.
131+
"""
132+
ECDSA_SHA1 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"
133+
"""
134+
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
135+
Support for their algorithm identifiers is deprecated and will be removed in a future release.
136+
"""
137+
SHA1_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha1-rsa-MGF1"
138+
"""
139+
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
140+
Support for their algorithm identifiers is deprecated and will be removed in a future release.
141+
"""
110142

111143

112144
class CanonicalizationMethod(InvalidInputErrorMixin, Enum):
@@ -157,6 +189,13 @@ class CanonicalizationMethod(InvalidInputErrorMixin, Enum):
157189
SignatureMethod.ECDSA_SHA3_256: hashes.SHA3_256,
158190
SignatureMethod.ECDSA_SHA3_384: hashes.SHA3_384,
159191
SignatureMethod.ECDSA_SHA3_512: hashes.SHA3_512,
160-
SignatureMethod.EDDSA_ED25519: hashes.SHA512,
161-
SignatureMethod.EDDSA_ED448: hashes.SHAKE256,
192+
SignatureMethod.SHA3_224_RSA_MGF1: hashes.SHA3_224,
193+
SignatureMethod.SHA3_256_RSA_MGF1: hashes.SHA3_256,
194+
SignatureMethod.SHA3_384_RSA_MGF1: hashes.SHA3_384,
195+
SignatureMethod.SHA3_512_RSA_MGF1: hashes.SHA3_512,
196+
SignatureMethod.SHA224_RSA_MGF1: hashes.SHA224,
197+
SignatureMethod.SHA256_RSA_MGF1: hashes.SHA256,
198+
SignatureMethod.SHA384_RSA_MGF1: hashes.SHA384,
199+
SignatureMethod.SHA512_RSA_MGF1: hashes.SHA512,
200+
SignatureMethod.SHA1_RSA_MGF1: hashes.SHA1,
162201
}

signxml/processor.py

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import os
23
import warnings
34
from typing import Any, List, Tuple
@@ -11,6 +12,8 @@
1112
from .exceptions import InvalidInput
1213
from .util import namespaces
1314

15+
logger = logging.getLogger(__name__)
16+
1417

1518
class XMLProcessor:
1619
_schemas: List[Any] = []
@@ -86,27 +89,21 @@ def _get_digest(self, data, algorithm: DigestAlgorithm):
8689
hasher.update(data)
8790
return hasher.finalize()
8891

89-
def _find(self, element, query, require=True, anywhere=False):
92+
def _find(self, element, query, require=True, xpath=""):
9093
namespace = "ds"
9194
if ":" in query:
9295
namespace, _, query = query.partition(":")
93-
if anywhere:
94-
result = element.find(".//" + namespace + ":" + query, namespaces=namespaces)
95-
else:
96-
result = element.find(namespace + ":" + query, namespaces=namespaces)
96+
result = element.find(f"{xpath}{namespace}:{query}", namespaces=namespaces)
9797

9898
if require and result is None:
9999
raise InvalidInput(f"Expected to find XML element {query} in {element.tag}")
100100
return result
101101

102-
def _findall(self, element, query, anywhere=False):
102+
def _findall(self, element, query, xpath=""):
103103
namespace = "ds"
104104
if ":" in query:
105105
namespace, _, query = query.partition(":")
106-
if anywhere:
107-
return element.findall(".//" + namespace + ":" + query, namespaces=namespaces)
108-
else:
109-
return element.findall(namespace + ":" + query, namespaces=namespaces)
106+
return element.findall(f"{xpath}{namespace}:{query}", namespaces=namespaces)
110107

111108
def _c14n(self, nodes, algorithm: CanonicalizationMethod, inclusive_ns_prefixes=None):
112109
exclusive, with_comments = False, False
@@ -138,6 +135,7 @@ def _c14n(self, nodes, algorithm: CanonicalizationMethod, inclusive_ns_prefixes=
138135
# - http://www.w3.org/TR/xml-c14n, "namespace axis"
139136
# - http://www.w3.org/TR/xml-c14n2/#sec-Namespace-Processing
140137
c14n = c14n.replace(b' xmlns=""', b"")
138+
logger.debug("Canonicalized string (exclusive=%s, with_comments=%s): %s", exclusive, with_comments, c14n)
141139
return c14n
142140

143141
def _resolve_reference(self, doc_root, reference, uri_resolver=None):

0 commit comments

Comments
 (0)