Skip to content

Commit

Permalink
Post-3.0 improvements
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
kislyuk committed Nov 27, 2022
1 parent 9794814 commit 82ae152
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 139 deletions.
19 changes: 13 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
SignXML: XML Signature in Python
================================
SignXML: XML Signature and XAdES in Python
==========================================

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

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

**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>`_
**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>`_

.. admonition:: Establish trust

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

* `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>`_
* `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>`_
* `ElementTree compatibility of lxml.etree <https://lxml.de/compatibility.html>`_
* `XML Signatures with Python ElementTree <https://technotes.shemyak.com/posts/xml-signatures-with-python-elementtree>`_

Expand Down Expand Up @@ -226,11 +230,14 @@ Links
* `List of W3C XML Signature standards and drafts <https://www.w3.org/TR/?title=xml%20signature>`_
* `W3C Recommendation: XML Signature Syntax and Processing Version 1.1 <http://www.w3.org/TR/xmldsig-core1>`_
* `W3C Working Group Note: XML Signature Syntax and Processing Version 2.0 <http://www.w3.org/TR/xmldsig-core2>`_
* `W3C Working Group Note: XML Security 2.0 Requirements and Design Considerations <https://www.w3.org/TR/2013/NOTE-xmlsec-reqs2-20130411/>`_
* `W3C Working Group Note: XML Security 2.0 Requirements and Design Considerations
<https://www.w3.org/TR/2013/NOTE-xmlsec-reqs2-20130411/>`_
* `W3C Working Group Note: XML Signature Best Practices <http://www.w3.org/TR/xmldsig-bestpractices/>`_
* `XML-Signature Interoperability <http://www.w3.org/Signature/2001/04/05-xmldsig-interop.html>`_
* `W3C Working Group Note: Test Cases for C14N 1.1 and XMLDSig Interoperability <http://www.w3.org/TR/xmldsig2ed-tests/>`_
* `RFC 9231: Additional XML Security Uniform Resource Identifiers (URIs) <https://www.rfc-editor.org/rfc/rfc9231.html>`_
* `Intelligence Community Technical Specification: Web Service Security Guidance for Use of XML Signature and XML
Encryption <https://github.com/XML-Security/signxml/blob/develop/docs/dni-guidance.pdf>`_
* `XMLSec: Related links <https://www.aleksey.com/xmlsec/related.html>`_
* `OWASP SAML Security Cheat Sheet <https://www.owasp.org/index.php/SAML_Security_Cheat_Sheet>`_
* `Okta Developer Docs: SAML <https://developer.okta.com/standards/SAML/>`_
Expand Down
3 changes: 1 addition & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
pygments_style = "sphinx"
autodoc_member_order = "bysource"
autodoc_typehints = "description"
typehints_fully_qualified = True
always_document_param_types = True
autodoc_typehints_description_target = "documented_params"
intersphinx_mapping = {
"https://docs.python.org/3": None,
"https://lxml.de/apidoc": "https://lxml.de/apidoc/objects.inv",
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
license="Apache Software License",
author="Andrey Kislyuk",
author_email="kislyuk@gmail.com",
description="Python XML Signature library",
description="Python XML Signature and XAdES library",
long_description=open("README.rst").read(),
python_requires=">=3.7",
install_requires=[
Expand Down
4 changes: 2 additions & 2 deletions signxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
See `SignXML documentation <#synopsis>`_ for examples.
"""

from .signer import XMLSigner, XMLSignatureReference
from .verifier import XMLVerifier, VerifyResult
from .signer import XMLSigner, SignatureReference
from .verifier import XMLVerifier, VerifyResult, SignatureConfiguration
from .algorithms import DigestAlgorithm, SignatureMethod, CanonicalizationMethod, SignatureConstructionMethod
from .exceptions import InvalidCertificate, InvalidDigest, InvalidInput, InvalidSignature
from .processor import XMLSignatureProcessor
Expand Down
59 changes: 49 additions & 10 deletions signxml/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class DigestAlgorithm(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
<http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of the XML Signature 1.1 standard for details.
"""

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

SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
"""
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
Support for their algorithm identifiers is deprecated and will be removed in a future release.
"""

@property
def implementation(self) -> Callable:
"""
Expand All @@ -83,10 +88,6 @@ class SignatureMethod(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
<http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of the XML Signature 1.1 standard for details.
"""

DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
HMAC_SHA1 = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
ECDSA_SHA1 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"
ECDSA_SHA224 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224"
ECDSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"
ECDSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384"
Expand All @@ -99,14 +100,45 @@ class SignatureMethod(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
RSA_PSS = "http://www.w3.org/2007/05/xmldsig-more#rsa-pss"
DSA_SHA256 = "http://www.w3.org/2009/xmldsig11#dsa-sha256"
ECDSA_SHA3_224 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-224"
ECDSA_SHA3_256 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-256"
ECDSA_SHA3_384 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-384"
ECDSA_SHA3_512 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-512"
EDDSA_ED25519 = "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519"
EDDSA_ED448 = "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448"
SHA3_224_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-224-rsa-MGF1"
SHA3_256_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-256-rsa-MGF1"
SHA3_384_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-384-rsa-MGF1"
SHA3_512_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-512-rsa-MGF1"
SHA224_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha224-rsa-MGF1"
SHA256_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1"
SHA384_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1"
SHA512_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1"

DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
"""
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
Support for their algorithm identifiers is deprecated and will be removed in a future release.
"""
HMAC_SHA1 = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
"""
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
Support for their algorithm identifiers is deprecated and will be removed in a future release.
"""
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
"""
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
Support for their algorithm identifiers is deprecated and will be removed in a future release.
"""
ECDSA_SHA1 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"
"""
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
Support for their algorithm identifiers is deprecated and will be removed in a future release.
"""
SHA1_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha1-rsa-MGF1"
"""
SHA1 based algorithms are not secure for use in digital signatures. They are included for legacy compatibility only.
Support for their algorithm identifiers is deprecated and will be removed in a future release.
"""


class CanonicalizationMethod(InvalidInputErrorMixin, Enum):
Expand Down Expand Up @@ -157,6 +189,13 @@ class CanonicalizationMethod(InvalidInputErrorMixin, Enum):
SignatureMethod.ECDSA_SHA3_256: hashes.SHA3_256,
SignatureMethod.ECDSA_SHA3_384: hashes.SHA3_384,
SignatureMethod.ECDSA_SHA3_512: hashes.SHA3_512,
SignatureMethod.EDDSA_ED25519: hashes.SHA512,
SignatureMethod.EDDSA_ED448: hashes.SHAKE256,
SignatureMethod.SHA3_224_RSA_MGF1: hashes.SHA3_224,
SignatureMethod.SHA3_256_RSA_MGF1: hashes.SHA3_256,
SignatureMethod.SHA3_384_RSA_MGF1: hashes.SHA3_384,
SignatureMethod.SHA3_512_RSA_MGF1: hashes.SHA3_512,
SignatureMethod.SHA224_RSA_MGF1: hashes.SHA224,
SignatureMethod.SHA256_RSA_MGF1: hashes.SHA256,
SignatureMethod.SHA384_RSA_MGF1: hashes.SHA384,
SignatureMethod.SHA512_RSA_MGF1: hashes.SHA512,
SignatureMethod.SHA1_RSA_MGF1: hashes.SHA1,
}
18 changes: 8 additions & 10 deletions signxml/processor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
import os
import warnings
from typing import Any, List, Tuple
Expand All @@ -11,6 +12,8 @@
from .exceptions import InvalidInput
from .util import namespaces

logger = logging.getLogger(__name__)


class XMLProcessor:
_schemas: List[Any] = []
Expand Down Expand Up @@ -86,27 +89,21 @@ def _get_digest(self, data, algorithm: DigestAlgorithm):
hasher.update(data)
return hasher.finalize()

def _find(self, element, query, require=True, anywhere=False):
def _find(self, element, query, require=True, xpath=""):
namespace = "ds"
if ":" in query:
namespace, _, query = query.partition(":")
if anywhere:
result = element.find(".//" + namespace + ":" + query, namespaces=namespaces)
else:
result = element.find(namespace + ":" + query, namespaces=namespaces)
result = element.find(f"{xpath}{namespace}:{query}", namespaces=namespaces)

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

def _findall(self, element, query, anywhere=False):
def _findall(self, element, query, xpath=""):
namespace = "ds"
if ":" in query:
namespace, _, query = query.partition(":")
if anywhere:
return element.findall(".//" + namespace + ":" + query, namespaces=namespaces)
else:
return element.findall(namespace + ":" + query, namespaces=namespaces)
return element.findall(f"{xpath}{namespace}:{query}", namespaces=namespaces)

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

def _resolve_reference(self, doc_root, reference, uri_resolver=None):
Expand Down
Loading

0 comments on commit 82ae152

Please sign in to comment.