signxml-references-modified 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
signxml/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """
2
+ Use :class:`signxml.XMLSigner` and :class:`signxml.XMLVerifier` to sign and verify XML Signatures, respectively.
3
+ See `SignXML documentation <#synopsis>`_ for examples.
4
+ """
5
+
6
+ from .signer import XMLSigner, SignatureReference
7
+ from .verifier import XMLVerifier, VerifyResult, SignatureConfiguration
8
+ from .algorithms import DigestAlgorithm, SignatureMethod, CanonicalizationMethod, SignatureConstructionMethod
9
+ from .exceptions import InvalidCertificate, InvalidDigest, InvalidInput, InvalidSignature
10
+ from .processor import XMLSignatureProcessor
11
+ from .util import namespaces
12
+
13
+ methods = SignatureConstructionMethod
@@ -0,0 +1,10 @@
1
+ """
2
+ This file indicates that this directory contains the hooks to include the schema files on the final version
3
+ if it's compiled with pyinstaller.
4
+ """
5
+
6
+ import os
7
+
8
+
9
+ def get_hook_dirs():
10
+ return [os.path.dirname(__file__)]
@@ -0,0 +1,5 @@
1
+ """Hook for pyinstaller to include the files are signxml/schemas/* into the final build."""
2
+
3
+ from PyInstaller.utils.hooks import collect_data_files # type: ignore
4
+
5
+ datas = collect_data_files("signxml", excludes=["__pyinstaller"])
signxml/algorithms.py ADDED
@@ -0,0 +1,201 @@
1
+ from enum import Enum
2
+ from typing import Callable, Dict, Type, Union
3
+
4
+ from cryptography.hazmat.primitives import hashes
5
+
6
+ from .exceptions import InvalidInput
7
+
8
+
9
+ class SignatureConstructionMethod(Enum):
10
+ """
11
+ An enumeration of signature construction methods supported by SignXML, used to specify the method when signing.
12
+ See the list of signature types under `XML Signature Syntax and Processing Version 2.0, Definitions
13
+ <http://www.w3.org/TR/xmldsig-core2/#sec-Definitions>`_.
14
+ """
15
+
16
+ enveloped = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
17
+ """
18
+ The signature is over the XML content that contains the signature as an element. The content provides the root
19
+ XML document element. This is the most common XML signature type in modern applications.
20
+ """
21
+
22
+ enveloping = "enveloping-signature"
23
+ """
24
+ The signature is over content found within an Object element of the signature itself. The Object (or its
25
+ content) is identified via a Reference (via a URI fragment identifier or transform).
26
+ """
27
+
28
+ detached = "detached-signature"
29
+ """
30
+ The signature is over content external to the Signature element, and can be identified via a URI or
31
+ transform. Consequently, the signature is "detached" from the content it signs. This definition typically applies to
32
+ separate data objects, but it also includes the instance where the Signature and data object reside within the same
33
+ XML document but are sibling elements.
34
+ """
35
+
36
+
37
+ class FragmentLookupMixin:
38
+ @classmethod
39
+ def from_fragment(cls, fragment):
40
+ for i in cls: # type: ignore[attr-defined]
41
+ if i.value.endswith("#" + fragment):
42
+ return i
43
+ else:
44
+ raise InvalidInput(f"Unrecognized {cls.__name__} identifier fragment: {fragment}")
45
+
46
+
47
+ class InvalidInputErrorMixin:
48
+ @classmethod
49
+ def _missing_(cls, value):
50
+ raise InvalidInput(f"Unrecognized {cls.__name__}: {value}")
51
+
52
+ def __repr__(self):
53
+ return f"{self.__class__.__name__}.{self.name}" # type: ignore[attr-defined]
54
+
55
+
56
+ class DigestAlgorithm(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
57
+ """
58
+ An enumeration of digest algorithms supported by SignXML. See the
59
+ `Algorithm Identifiers and Implementation Requirements <http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of
60
+ the XML Signature 1.1 standard for details.
61
+ """
62
+
63
+ SHA224 = "http://www.w3.org/2001/04/xmldsig-more#sha224"
64
+ SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
65
+ SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
66
+ SHA512 = "http://www.w3.org/2001/04/xmlenc#sha512"
67
+ SHA3_224 = "http://www.w3.org/2007/05/xmldsig-more#sha3-224"
68
+ SHA3_256 = "http://www.w3.org/2007/05/xmldsig-more#sha3-256"
69
+ SHA3_384 = "http://www.w3.org/2007/05/xmldsig-more#sha3-384"
70
+ SHA3_512 = "http://www.w3.org/2007/05/xmldsig-more#sha3-512"
71
+
72
+ SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
73
+ "See `SHA1 deprecation`_."
74
+
75
+ @property
76
+ def implementation(self) -> Callable:
77
+ """
78
+ The cryptography callable that implements the specified algorithm.
79
+ """
80
+ return digest_algorithm_implementations[self]
81
+
82
+
83
+ # TODO: check if padding errors are fixed by using padding=MGF1
84
+ class SignatureMethod(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
85
+ """
86
+ An enumeration of signature methods (also referred to as signature algorithms) supported by SignXML. See the
87
+ `Algorithm Identifiers and Implementation Requirements <http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of
88
+ the XML Signature 1.1 standard for details.
89
+ """
90
+
91
+ RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
92
+ """
93
+ The RSASSA-PKCS1-v1_5 algorithm described in RFC 3447. This is the default, most widely supported signature method.
94
+ """
95
+
96
+ RSA_SHA224 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224"
97
+ RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
98
+ RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
99
+ ECDSA_SHA224 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224"
100
+ ECDSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"
101
+ ECDSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384"
102
+ ECDSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512"
103
+ ECDSA_SHA3_224 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-224"
104
+ ECDSA_SHA3_256 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-256"
105
+ ECDSA_SHA3_384 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-384"
106
+ ECDSA_SHA3_512 = "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-512"
107
+ DSA_SHA256 = "http://www.w3.org/2009/xmldsig11#dsa-sha256"
108
+ HMAC_SHA224 = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha224"
109
+ HMAC_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"
110
+ HMAC_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384"
111
+ HMAC_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512"
112
+ SHA3_224_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-224-rsa-MGF1"
113
+ SHA3_256_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-256-rsa-MGF1"
114
+ SHA3_384_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-384-rsa-MGF1"
115
+ SHA3_512_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha3-512-rsa-MGF1"
116
+ SHA224_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha224-rsa-MGF1"
117
+ SHA256_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1"
118
+ SHA384_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1"
119
+ SHA512_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1"
120
+
121
+ DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
122
+ """
123
+ _`SHA1 deprecation`: SHA1 based algorithms are not secure for use in digital signatures. They are included for
124
+ legacy compatibility only and disabled by default. To verify SHA1 based signatures, use::
125
+
126
+ XMLVerifier().verify(
127
+ expect_config=SignatureConfiguration(
128
+ signature_methods=...,
129
+ digest_algorithms=...
130
+ )
131
+ )
132
+ """
133
+ HMAC_SHA1 = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
134
+ "See `SHA1 deprecation`_."
135
+ RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
136
+ "See `SHA1 deprecation`_."
137
+ ECDSA_SHA1 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"
138
+ "See `SHA1 deprecation`_."
139
+ SHA1_RSA_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha1-rsa-MGF1"
140
+ "See `SHA1 deprecation`_."
141
+
142
+
143
+ class CanonicalizationMethod(InvalidInputErrorMixin, Enum):
144
+ """
145
+ An enumeration of XML canonicalization methods (also referred to as canonicalization algorithms) supported by
146
+ SignXML. See the `Algorithm Identifiers and Implementation Requirements
147
+ <http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of the XML Signature 1.1 standard for details.
148
+ """
149
+
150
+ CANONICAL_XML_1_0 = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
151
+ CANONICAL_XML_1_0_WITH_COMMENTS = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
152
+ CANONICAL_XML_1_1 = "http://www.w3.org/2006/12/xml-c14n11"
153
+ CANONICAL_XML_1_1_WITH_COMMENTS = "http://www.w3.org/2006/12/xml-c14n11#WithComments"
154
+ EXCLUSIVE_XML_CANONICALIZATION_1_0 = "http://www.w3.org/2001/10/xml-exc-c14n#"
155
+ EXCLUSIVE_XML_CANONICALIZATION_1_0_WITH_COMMENTS = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
156
+
157
+ # The identifier for Canonical XML 2.0 is "http://www.w3.org/2010/xml-c14n2", but it is not a W3C standard.
158
+ # While it is supported by lxml, it's not in general use and not supported by SignXML
159
+
160
+
161
+ digest_algorithm_implementations: Dict[Union[DigestAlgorithm, SignatureMethod], Type[hashes.HashAlgorithm]] = {
162
+ DigestAlgorithm.SHA1: hashes.SHA1,
163
+ DigestAlgorithm.SHA224: hashes.SHA224,
164
+ DigestAlgorithm.SHA384: hashes.SHA384,
165
+ DigestAlgorithm.SHA256: hashes.SHA256,
166
+ DigestAlgorithm.SHA512: hashes.SHA512,
167
+ DigestAlgorithm.SHA3_224: hashes.SHA3_224,
168
+ DigestAlgorithm.SHA3_256: hashes.SHA3_256,
169
+ DigestAlgorithm.SHA3_384: hashes.SHA3_384,
170
+ DigestAlgorithm.SHA3_512: hashes.SHA3_512,
171
+ SignatureMethod.DSA_SHA1: hashes.SHA1,
172
+ SignatureMethod.HMAC_SHA1: hashes.SHA1,
173
+ SignatureMethod.RSA_SHA1: hashes.SHA1,
174
+ SignatureMethod.ECDSA_SHA1: hashes.SHA1,
175
+ SignatureMethod.ECDSA_SHA224: hashes.SHA224,
176
+ SignatureMethod.ECDSA_SHA256: hashes.SHA256,
177
+ SignatureMethod.ECDSA_SHA384: hashes.SHA384,
178
+ SignatureMethod.ECDSA_SHA512: hashes.SHA512,
179
+ SignatureMethod.HMAC_SHA224: hashes.SHA224,
180
+ SignatureMethod.HMAC_SHA256: hashes.SHA256,
181
+ SignatureMethod.HMAC_SHA384: hashes.SHA384,
182
+ SignatureMethod.HMAC_SHA512: hashes.SHA512,
183
+ SignatureMethod.RSA_SHA224: hashes.SHA224,
184
+ SignatureMethod.RSA_SHA256: hashes.SHA256,
185
+ SignatureMethod.RSA_SHA384: hashes.SHA384,
186
+ SignatureMethod.RSA_SHA512: hashes.SHA512,
187
+ SignatureMethod.DSA_SHA256: hashes.SHA256,
188
+ SignatureMethod.ECDSA_SHA3_224: hashes.SHA3_224,
189
+ SignatureMethod.ECDSA_SHA3_256: hashes.SHA3_256,
190
+ SignatureMethod.ECDSA_SHA3_384: hashes.SHA3_384,
191
+ SignatureMethod.ECDSA_SHA3_512: hashes.SHA3_512,
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,
201
+ }
signxml/exceptions.py ADDED
@@ -0,0 +1,35 @@
1
+ """
2
+ SignXML exception types.
3
+ """
4
+
5
+ import cryptography.exceptions
6
+
7
+
8
+ class SignXMLException(Exception):
9
+ pass
10
+
11
+
12
+ class InvalidSignature(cryptography.exceptions.InvalidSignature, SignXMLException):
13
+ """
14
+ Raised when signature validation fails.
15
+ """
16
+
17
+
18
+ class InvalidDigest(InvalidSignature):
19
+ """
20
+ Raised when digest validation fails (causing the signature to be untrusted).
21
+ """
22
+
23
+
24
+ class InvalidCertificate(InvalidSignature):
25
+ """
26
+ Raised when certificate validation fails.
27
+ """
28
+
29
+
30
+ class InvalidInput(ValueError, SignXMLException):
31
+ pass
32
+
33
+
34
+ class RedundantCert(SignXMLException):
35
+ pass
signxml/processor.py ADDED
@@ -0,0 +1,160 @@
1
+ import logging
2
+ import os
3
+ from typing import Any, List, Tuple
4
+ from xml.etree import ElementTree as stdlibElementTree
5
+
6
+ from cryptography.hazmat.primitives.asymmetric import ec
7
+ from cryptography.hazmat.primitives.hashes import Hash
8
+ from lxml import etree
9
+
10
+ from .algorithms import CanonicalizationMethod, DigestAlgorithm, digest_algorithm_implementations
11
+ from .exceptions import InvalidInput
12
+ from .util import namespaces
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class XMLProcessor:
18
+ _schemas: List[Any] = []
19
+ schema_files: List[Any] = []
20
+ _default_parser, _parser = None, None
21
+ _schema_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), "schemas"))
22
+
23
+ @classmethod
24
+ def schemas(cls):
25
+ if len(cls._schemas) == 0:
26
+ for schema_file in cls.schema_files:
27
+ schema_path = os.path.join(cls._schema_dir, schema_file)
28
+ cls._schemas.append(etree.XMLSchema(etree.parse(schema_path)))
29
+ return cls._schemas
30
+
31
+ @property
32
+ def parser(self):
33
+ if self._parser is None:
34
+ if self._default_parser is None:
35
+ self._default_parser = etree.XMLParser(resolve_entities=False)
36
+ return self._default_parser
37
+ return self._parser
38
+
39
+ def _fromstring(self, xml_string, **kwargs):
40
+ xml_node = etree.fromstring(xml_string, parser=self.parser, **kwargs)
41
+ for entity in xml_node.iter(etree.Entity):
42
+ raise InvalidInput("Entities are not supported in XML input")
43
+ return xml_node
44
+
45
+ def _tostring(self, xml_node, **kwargs):
46
+ return etree.tostring(xml_node, **kwargs)
47
+
48
+ def get_root(self, data):
49
+ if isinstance(data, (str, bytes)):
50
+ return self._fromstring(data)
51
+ elif isinstance(data, stdlibElementTree.Element):
52
+ # TODO: add debug level logging statement re: performance impact here
53
+ return self._fromstring(stdlibElementTree.tostring(data, encoding="utf-8"))
54
+ else:
55
+ # Create a separate copy of the node so we can modify the tree and avoid any c14n inconsistencies from
56
+ # namespaces propagating from parent nodes. The lxml docs recommend using copy.deepcopy for this, but it
57
+ # doesn't seem to preserve namespaces. It would be nice to find a less heavy-handed way of doing this.
58
+ return self._fromstring(self._tostring(data))
59
+
60
+
61
+ class XMLSignatureProcessor(XMLProcessor):
62
+ schema_files = ["xmldsig1-schema.xsd"]
63
+
64
+ # See https://tools.ietf.org/html/rfc5656
65
+ known_ecdsa_curves = {
66
+ "urn:oid:1.2.840.10045.3.1.7": ec.SECP256R1,
67
+ "urn:oid:1.3.132.0.34": ec.SECP384R1,
68
+ "urn:oid:1.3.132.0.35": ec.SECP521R1,
69
+ "urn:oid:1.3.132.0.1": ec.SECT163K1,
70
+ "urn:oid:1.2.840.10045.3.1.1": ec.SECP192R1,
71
+ "urn:oid:1.3.132.0.33": ec.SECP224R1,
72
+ "urn:oid:1.3.132.0.26": ec.SECT233K1,
73
+ "urn:oid:1.3.132.0.27": ec.SECT233R1,
74
+ "urn:oid:1.3.132.0.16": ec.SECT283R1,
75
+ "urn:oid:1.3.132.0.36": ec.SECT409K1,
76
+ "urn:oid:1.3.132.0.37": ec.SECT409R1,
77
+ "urn:oid:1.3.132.0.38": ec.SECT571K1,
78
+ }
79
+ known_ecdsa_curve_oids = {ec().name: oid for oid, ec in known_ecdsa_curves.items()} # type: ignore[abstract]
80
+
81
+ excise_empty_xmlns_declarations = False
82
+
83
+ id_attributes: Tuple[str, ...] = ("Id", "ID", "id", "xml:id")
84
+
85
+ def _get_digest(self, data, algorithm: DigestAlgorithm):
86
+ algorithm_implementation = digest_algorithm_implementations[algorithm]()
87
+ hasher = Hash(algorithm=algorithm_implementation)
88
+ hasher.update(data)
89
+ return hasher.finalize()
90
+
91
+ def _find(self, element, query, require=True, xpath=""):
92
+ namespace = "ds"
93
+ if ":" in query:
94
+ namespace, _, query = query.partition(":")
95
+ result = element.find(f"{xpath}{namespace}:{query}", namespaces=namespaces)
96
+
97
+ if require and result is None:
98
+ raise InvalidInput(f"Expected to find XML element {query} in {element.tag}")
99
+ return result
100
+
101
+ def _findall(self, element, query, xpath=""):
102
+ namespace = "ds"
103
+ if ":" in query:
104
+ namespace, _, query = query.partition(":")
105
+ return element.findall(f"{xpath}{namespace}:{query}", namespaces=namespaces)
106
+
107
+ def _c14n(self, nodes, algorithm: CanonicalizationMethod, inclusive_ns_prefixes=None):
108
+ exclusive, with_comments = False, False
109
+
110
+ if algorithm.value.startswith("http://www.w3.org/2001/10/xml-exc-c14n#"):
111
+ exclusive = True
112
+ if algorithm.value.endswith("#WithComments"):
113
+ with_comments = True
114
+
115
+ if not isinstance(nodes, list):
116
+ nodes = [nodes]
117
+
118
+ c14n = b""
119
+ for node in nodes:
120
+ c14n += etree.tostring(
121
+ node,
122
+ method="c14n",
123
+ exclusive=exclusive,
124
+ with_comments=with_comments,
125
+ inclusive_ns_prefixes=inclusive_ns_prefixes,
126
+ )
127
+ if exclusive is False and self.excise_empty_xmlns_declarations is True:
128
+ # Incorrect legacy behavior. See also:
129
+ # - https://github.com/XML-Security/signxml/issues/193
130
+ # - http://www.w3.org/TR/xml-c14n, "namespace axis"
131
+ # - http://www.w3.org/TR/xml-c14n2/#sec-Namespace-Processing
132
+ c14n = c14n.replace(b' xmlns=""', b"")
133
+ logger.debug("Canonicalized string (exclusive=%s, with_comments=%s): %s", exclusive, with_comments, c14n)
134
+ return c14n
135
+
136
+ def _resolve_reference(self, doc_root, reference, uri_resolver=None):
137
+ uri = reference.get("URI")
138
+ if uri is None:
139
+ raise InvalidInput("References without URIs are not supported")
140
+ elif uri == "":
141
+ return doc_root
142
+ elif uri.startswith("#xpointer("):
143
+ raise InvalidInput("XPointer references are not supported")
144
+ # doc_root.xpath(uri.lstrip("#"))[0]
145
+ elif uri.startswith("#"):
146
+ for id_attribute in self.id_attributes:
147
+ xpath_query = f"//*[@*[local-name() = '{id_attribute}']=$uri]"
148
+ results = doc_root.xpath(xpath_query, uri=uri.lstrip("#"))
149
+ if len(results) > 1:
150
+ raise InvalidInput(f"Ambiguous reference URI {uri} resolved to {len(results)} nodes")
151
+ elif len(results) == 1:
152
+ return results[0]
153
+ raise InvalidInput(f"Unable to resolve reference URI: {uri}")
154
+ else:
155
+ if uri_resolver is None:
156
+ raise InvalidInput(f"External URI dereferencing is not configured: {uri}")
157
+ result = uri_resolver(uri)
158
+ if result is None:
159
+ raise InvalidInput(f"Unable to resolve reference URI: {uri}")
160
+ return result
signxml/py.typed ADDED
File without changes
File without changes