Skip to content

Commit 6879c98

Browse files
committed
Documentation and consistency improvements
1 parent 2d8a54d commit 6879c98

File tree

6 files changed

+64
-50
lines changed

6 files changed

+64
-50
lines changed

docs/conf.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@
77
release = ""
88
language = "en"
99
master_doc = "index"
10-
extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"]
10+
extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.intersphinx"]
1111
source_suffix = [".rst", ".md"]
1212
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
1313
pygments_style = "sphinx"
1414
autodoc_member_order = "bysource"
1515
autodoc_typehints = "description"
1616
typehints_fully_qualified = True
1717
always_document_param_types = True
18+
intersphinx_mapping = {
19+
"https://docs.python.org/3": None,
20+
"https://lxml.de/apidoc": "https://lxml.de/apidoc/objects.inv",
21+
"https://cryptography.io/en/latest": "https://cryptography.io/en/latest/objects.inv",
22+
"https://www.pyopenssl.org/en/stable": "https://www.pyopenssl.org/en/stable/objects.inv",
23+
}
1824

1925
if "readthedocs.org" in os.getcwd().split("/"):
2026
with open("index.rst", "w") as fh:

signxml/algorithms.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from enum import Enum, auto
1+
from enum import Enum
22
from typing import Callable
33

44
from cryptography.hazmat.primitives import hashes
@@ -13,19 +13,19 @@ class SignatureConstructionMethod(Enum):
1313
<http://www.w3.org/TR/xmldsig-core2/#sec-Definitions>`_.
1414
"""
1515

16-
enveloped = auto()
16+
enveloped = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
1717
"""
1818
The signature is over the XML content that contains the signature as an element. The content provides the root
1919
XML document element. This is the most common XML signature type in modern applications.
2020
"""
2121

22-
enveloping = auto()
22+
enveloping = "enveloping-signature"
2323
"""
2424
The signature is over content found within an Object element of the signature itself. The Object (or its
2525
content) is identified via a Reference (via a URI fragment identifier or transform).
2626
"""
2727

28-
detached = auto()
28+
detached = "detached-signature"
2929
"""
3030
The signature is over content external to the Signature element, and can be identified via a URI or
3131
transform. Consequently, the signature is "detached" from the content it signs. This definition typically applies to
@@ -52,7 +52,9 @@ def _missing_(cls, value):
5252

5353
class DigestAlgorithm(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
5454
"""
55-
An enumeration of digest algorithms supported by SignXML. See RFC 9231 for details.
55+
An enumeration of digest algorithms supported by SignXML. See `RFC 9231
56+
<https://www.rfc-editor.org/rfc/rfc9231.html>`_ and the `Algorithm Identifiers and Implementation Requirements
57+
<http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of the XML Signature 1.1 standard for details.
5658
"""
5759

5860
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
@@ -76,8 +78,9 @@ def implementation(self) -> Callable:
7678
# TODO: check if padding errors are fixed by using padding=MGF1
7779
class SignatureMethod(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
7880
"""
79-
An enumeration of signature methods (also referred to as signature algorithms) supported by SignXML. See RFC 9231
80-
for details.
81+
An enumeration of signature methods (also referred to as signature algorithms) supported by SignXML. See `RFC 9231
82+
<https://www.rfc-editor.org/rfc/rfc9231.html>`_ and the `Algorithm Identifiers and Implementation Requirements
83+
<http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of the XML Signature 1.1 standard for details.
8184
"""
8285

8386
DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
@@ -109,7 +112,9 @@ class SignatureMethod(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
109112
class CanonicalizationMethod(InvalidInputErrorMixin, Enum):
110113
"""
111114
An enumeration of XML canonicalization methods (also referred to as canonicalization algorithms) supported by
112-
SignXML. See RFC 9231 for details.
115+
SignXML. See `RFC 9231 <https://www.rfc-editor.org/rfc/rfc9231.html>`_ and the `Algorithm Identifiers and
116+
Implementation Requirements <http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of the XML Signature 1.1
117+
standard for details.
113118
"""
114119

115120
CANONICAL_XML_1_0 = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"

signxml/signer.py

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
from dataclasses import dataclass
33
from typing import List, Optional, Union
44

5-
from cryptography.hazmat.primitives.asymmetric import ec, utils
5+
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa, utils
66
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
77
from cryptography.hazmat.primitives.hmac import HMAC
88
from cryptography.hazmat.primitives.serialization import load_pem_private_key
99
from lxml.etree import Element, SubElement, _Element
10-
from OpenSSL.crypto import FILETYPE_PEM, dump_certificate
10+
from OpenSSL.crypto import FILETYPE_PEM, X509, dump_certificate
1111

1212
from .algorithms import (
1313
CanonicalizationMethod,
@@ -62,13 +62,14 @@ class XMLSigner(XMLSignatureProcessor):
6262
``signxml.methods.enveloped``, ``signxml.methods.enveloping``, or ``signxml.methods.detached``. See
6363
:class:`SignatureConstructionMethod` for details.
6464
:param signature_algorithm:
65-
Algorithm that will be used to generate the signature, composed of the signature algorithm and the digest
66-
algorithm, separated by a hyphen. All algorithm IDs listed under the `Algorithm Identifiers and
67-
Implementation Requirements <http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of the XML Signature
68-
1.1 standard are supported.
69-
:param digest_algorithm: Algorithm that will be used to hash the data during signature generation. All algorithm IDs
70-
listed under the `Algorithm Identifiers and Implementation Requirements
71-
<http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of the XML Signature 1.1 standard are supported.
65+
Algorithm that will be used to generate the signature. See :class:`SignatureMethod` for the list of algorithm
66+
IDs supported.
67+
:param digest_algorithm:
68+
Algorithm that will be used to hash the data during signature generation. See :class:`DigestAlgorithm` for the
69+
list of algorithm IDs supported.
70+
:param c14n_algorithm:
71+
Algorithm that will be used to canonicalize (serialize in a reproducible way) the XML that is signed. See
72+
:class:`CanonicalizationMethod` for the list of algorithm IDs supported.
7273
"""
7374

7475
signature_annotators: List
@@ -92,7 +93,7 @@ def __init__(
9293
method: SignatureConstructionMethod = SignatureConstructionMethod.enveloped,
9394
signature_algorithm: Union[SignatureMethod, str] = SignatureMethod.RSA_SHA256,
9495
digest_algorithm: Union[DigestAlgorithm, str] = DigestAlgorithm.SHA256,
95-
c14n_algorithm=CanonicalizationMethod.CANONICAL_XML_1_1,
96+
c14n_algorithm: Union[CanonicalizationMethod, str] = CanonicalizationMethod.CANONICAL_XML_1_1,
9697
):
9798
if method is None or method not in SignatureConstructionMethod:
9899
raise InvalidInput(f"Unknown signature construction method {method}")
@@ -113,16 +114,16 @@ def __init__(
113114
def sign(
114115
self,
115116
data,
116-
key=None,
117+
key: Optional[Union[str, bytes, rsa.RSAPrivateKey, dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey]] = None,
117118
passphrase: Optional[bytes] = None,
118-
cert=None,
119+
cert: Optional[Union[str, List[str], List[X509]]] = None,
119120
reference_uri: Optional[Union[str, List[str], List[XMLSignatureReference]]] = None,
120121
key_name: Optional[str] = None,
121122
key_info: Optional[_Element] = None,
122123
id_attribute: Optional[str] = None,
123124
always_add_key_value: bool = False,
124125
inclusive_ns_prefixes: Optional[List[str]] = None,
125-
signature_properties=None,
126+
signature_properties: Optional[Union[_Element, List[_Element]]] = None,
126127
) -> _Element:
127128
"""
128129
Sign the data and return the root element of the resulting XML tree.
@@ -131,20 +132,15 @@ def sign(
131132
:type data: String, file-like object, or XML ElementTree Element API compatible object
132133
:param key:
133134
Key to be used for signing. When signing with a certificate or RSA/DSA/ECDSA key, this can be a string/bytes
134-
containing a PEM-formatted key, or a :py:class:`cryptography.hazmat.primitives.interfaces.RSAPrivateKey`,
135-
:py:class:`cryptography.hazmat.primitives.interfaces.DSAPrivateKey`, or
136-
:py:class:`cryptography.hazmat.primitives.interfaces.EllipticCurvePrivateKey` object. When signing with a
135+
containing a PEM-formatted key, or a :class:`cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`,
136+
:class:`cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, or
137+
:class:`cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` object. When signing with a
137138
HMAC, this should be a string containing the shared secret.
138-
:type key:
139-
string, bytes, :py:class:`cryptography.hazmat.primitives.interfaces.RSAPrivateKey`,
140-
:py:class:`cryptography.hazmat.primitives.interfaces.DSAPrivateKey`, or
141-
:py:class:`cryptography.hazmat.primitives.interfaces.EllipticCurvePrivateKey` object
142139
:param passphrase: Passphrase to use to decrypt the key, if any.
143140
:param cert:
144141
X.509 certificate to use for signing. This should be a string containing a PEM-formatted certificate, or an
145-
array of strings or OpenSSL.crypto.X509 objects containing the certificate and a chain of intermediate
146-
certificates.
147-
:type cert: string, array of strings, or array of OpenSSL.crypto.X509 objects
142+
array of strings or :class:`OpenSSL.crypto.X509` objects containing the certificate and a chain of
143+
intermediate certificates.
148144
:param reference_uri:
149145
Custom reference URI or list of reference URIs to incorporate into the signature. When ``method`` is set to
150146
``detached`` or ``enveloped``, reference URIs are set to this value and only the referenced elements are
@@ -175,10 +171,9 @@ def sign(
175171
:param signature_properties:
176172
One or more Elements that are to be included in the SignatureProperies section when using the detached
177173
method.
178-
:type signature_properties: :py:class:`lxml.etree.Element` or list of :py:class:`lxml.etree.Element` s
179174
180175
:returns:
181-
A :py:class:`lxml.etree.Element` object representing the root of the XML tree containing the signature and
176+
A :class:`lxml.etree._Element` object representing the root of the XML tree containing the signature and
182177
the payload data.
183178
184179
To specify the location of an enveloped signature within **data**, insert a
@@ -192,7 +187,7 @@ def sign(
192187
if isinstance(cert, (str, bytes)):
193188
cert_chain = list(iterate_pem(cert))
194189
else:
195-
cert_chain = cert
190+
cert_chain = cert # type: ignore
196191

197192
input_references = self._preprocess_reference_uri(reference_uri)
198193

@@ -235,7 +230,7 @@ def sign(
235230
signed_info_node, algorithm=self.c14n_alg, inclusive_ns_prefixes=inclusive_ns_prefixes
236231
)
237232
if self.sign_alg.name.startswith("HMAC_"):
238-
signer = HMAC(key=key, algorithm=digest_algorithm_implementations[self.sign_alg]())
233+
signer = HMAC(key=key, algorithm=digest_algorithm_implementations[self.sign_alg]()) # type: ignore
239234
signer.update(signed_info_c14n)
240235
signature_value_node.text = b64encode(signer.finalize()).decode()
241236
sig_root.append(signature_value_node)
@@ -378,7 +373,7 @@ def _build_sig(self, sig_root, references, c14n_inputs, inclusive_ns_prefixes):
378373
reference_node = SubElement(signed_info, ds_tag("Reference"), URI=reference.URI)
379374
transforms = SubElement(reference_node, ds_tag("Transforms"))
380375
if self.construction_method == SignatureConstructionMethod.enveloped:
381-
SubElement(transforms, ds_tag("Transform"), Algorithm=namespaces.ds + "enveloped-signature")
376+
SubElement(transforms, ds_tag("Transform"), Algorithm=SignatureConstructionMethod.enveloped.value)
382377
SubElement(transforms, ds_tag("Transform"), Algorithm=reference.c14n_method.value)
383378
else:
384379
c14n_xform = SubElement(transforms, ds_tag("Transform"), Algorithm=reference.c14n_method.value)

signxml/util/__init__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from typing import Any, List, Optional
1414

1515
from cryptography.hazmat.primitives import hashes, hmac
16+
from lxml.etree import QName
1617

1718
from ..exceptions import InvalidCertificate, RedundantCert, SignXMLException
1819

@@ -39,23 +40,23 @@ def __getattr__(self, a):
3940

4041

4142
def ds_tag(tag):
42-
return "{" + namespaces.ds + "}" + tag
43+
return QName(namespaces.ds, tag)
4344

4445

4546
def dsig11_tag(tag):
46-
return "{" + namespaces.dsig11 + "}" + tag
47+
return QName(namespaces.dsig11, tag)
4748

4849

4950
def ec_tag(tag):
50-
return "{" + namespaces.ec + "}" + tag
51+
return QName(namespaces.ec, tag)
5152

5253

5354
def xades_tag(tag):
54-
return "{" + namespaces.xades + "}" + tag
55+
return QName(namespaces.xades, tag)
5556

5657

5758
def xades141_tag(tag):
58-
return "{" + namespaces.xades141 + "}" + tag
59+
return QName(namespaces.xades141, tag)
5960

6061

6162
@dataclass

signxml/verifier.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@
1212
from OpenSSL.crypto import load_certificate
1313
from OpenSSL.crypto import verify as openssl_verify
1414

15-
from .algorithms import CanonicalizationMethod, DigestAlgorithm, SignatureMethod, digest_algorithm_implementations
15+
from .algorithms import (
16+
CanonicalizationMethod,
17+
DigestAlgorithm,
18+
SignatureConstructionMethod,
19+
SignatureMethod,
20+
digest_algorithm_implementations,
21+
)
1622
from .exceptions import InvalidCertificate, InvalidDigest, InvalidInput, InvalidSignature
1723
from .processor import XMLSignatureProcessor
1824
from .util import (
@@ -141,7 +147,7 @@ def _apply_transforms(self, payload, transforms_node, signature, c14n_algorithm:
141147
transforms = self._findall(transforms_node, "Transform")
142148

143149
for transform in transforms:
144-
if transform.get("Algorithm") == "http://www.w3.org/2000/09/xmldsig#enveloped-signature":
150+
if transform.get("Algorithm") == SignatureConstructionMethod.enveloped.value:
145151
_remove_sig(signature, idempotent=True)
146152

147153
for transform in transforms:
@@ -242,7 +248,7 @@ def verify(
242248
:param parser:
243249
Custom XML parser instance to use when parsing **data**. The default parser arguments used by SignXML are:
244250
``resolve_entities=False``. See https://lxml.de/FAQ.html#how-do-i-use-lxml-safely-as-a-web-service-endpoint.
245-
:type parser: :py:class:`lxml.etree.XMLParser` compatible parser
251+
:type parser: :class:`lxml.etree.XMLParser` compatible parser
246252
:param uri_resolver:
247253
Function to use to resolve reference URIs that don't start with "#". The function is called with a single
248254
string argument containing the URI to be resolved, and is expected to return a lxml.etree node or string.
@@ -259,7 +265,7 @@ def verify(
259265
necessary to match the keys, and throws an InvalidInput error instead. Set this to True to bypass the error
260266
and validate the signature using X509Data only.
261267
262-
:raises: :py:class:`cryptography.exceptions.InvalidSignature`
268+
:raises: :class:`signxml.exceptions.InvalidSignature`
263269
"""
264270
self.hmac_key = hmac_key
265271
self.require_x509 = require_x509

signxml/xades/xades.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,10 +323,10 @@ def verify( # type: ignore
323323
**xml_verifier_args,
324324
) -> List[XAdESVerifyResult]:
325325
"""
326-
Verify the XAdES signature supplied in the data and return a list of **VerifyResult** data structures
327-
representing the data signed by the signature, or raise an exception if the signature is not valid. By default,
328-
this requires the signature to be generated using a valid X.509 certificate. To enable other means of signature
329-
validation, set the **require_x509** argument to `False`.
326+
Verify the XAdES signature supplied in the data and return a list of :class:`XAdESVerifyResult` data structures
327+
representing the data signed by the signature, or raise an exception if the signature is not valid. This method
328+
is a wrapper around :meth:`signxml.XMLVerifier.verify`; see its documentation for more details and arguments it
329+
supports.
330330
331331
:param expect_signature_policy:
332332
If you need to assert that the verified XAdES signature carries specific data in the
@@ -340,6 +340,7 @@ def verify( # type: ignore
340340
Parameters to pass to :meth:`signxml.XMLVerifier.verify`.
341341
"""
342342
self.expect_signature_policy = expect_signature_policy
343+
xml_verifier_args["require_x509"] = True
343344
verify_results = super().verify(data, expect_references=expect_references, **xml_verifier_args)
344345
if not isinstance(verify_results, list):
345346
raise InvalidInput("Expected to find multiple references in signature")

0 commit comments

Comments
 (0)