Skip to content

Commit

Permalink
Documentation and consistency improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
kislyuk committed Nov 14, 2022
1 parent 2d8a54d commit 6879c98
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 50 deletions.
8 changes: 7 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@
release = ""
language = "en"
master_doc = "index"
extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"]
extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.intersphinx"]
source_suffix = [".rst", ".md"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
pygments_style = "sphinx"
autodoc_member_order = "bysource"
autodoc_typehints = "description"
typehints_fully_qualified = True
always_document_param_types = True
intersphinx_mapping = {
"https://docs.python.org/3": None,
"https://lxml.de/apidoc": "https://lxml.de/apidoc/objects.inv",
"https://cryptography.io/en/latest": "https://cryptography.io/en/latest/objects.inv",
"https://www.pyopenssl.org/en/stable": "https://www.pyopenssl.org/en/stable/objects.inv",
}

if "readthedocs.org" in os.getcwd().split("/"):
with open("index.rst", "w") as fh:
Expand Down
21 changes: 13 additions & 8 deletions signxml/algorithms.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from enum import Enum, auto
from enum import Enum
from typing import Callable

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

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

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

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

class DigestAlgorithm(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
"""
An enumeration of digest algorithms supported by SignXML. See RFC 9231 for details.
An enumeration of digest algorithms supported by SignXML. See `RFC 9231
<https://www.rfc-editor.org/rfc/rfc9231.html>`_ and the `Algorithm Identifiers and Implementation Requirements
<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"
Expand All @@ -76,8 +78,9 @@ def implementation(self) -> Callable:
# TODO: check if padding errors are fixed by using padding=MGF1
class SignatureMethod(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
"""
An enumeration of signature methods (also referred to as signature algorithms) supported by SignXML. See RFC 9231
for details.
An enumeration of signature methods (also referred to as signature algorithms) supported by SignXML. See `RFC 9231
<https://www.rfc-editor.org/rfc/rfc9231.html>`_ and the `Algorithm Identifiers and Implementation Requirements
<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"
Expand Down Expand Up @@ -109,7 +112,9 @@ class SignatureMethod(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
class CanonicalizationMethod(InvalidInputErrorMixin, Enum):
"""
An enumeration of XML canonicalization methods (also referred to as canonicalization algorithms) supported by
SignXML. See RFC 9231 for details.
SignXML. See `RFC 9231 <https://www.rfc-editor.org/rfc/rfc9231.html>`_ and the `Algorithm Identifiers and
Implementation Requirements <http://www.w3.org/TR/xmldsig-core1/#sec-AlgID>`_ section of the XML Signature 1.1
standard for details.
"""

CANONICAL_XML_1_0 = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
Expand Down
51 changes: 23 additions & 28 deletions signxml/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
from dataclasses import dataclass
from typing import List, Optional, Union

from cryptography.hazmat.primitives.asymmetric import ec, utils
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa, utils
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.hmac import HMAC
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from lxml.etree import Element, SubElement, _Element
from OpenSSL.crypto import FILETYPE_PEM, dump_certificate
from OpenSSL.crypto import FILETYPE_PEM, X509, dump_certificate

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

signature_annotators: List
Expand All @@ -92,7 +93,7 @@ def __init__(
method: SignatureConstructionMethod = SignatureConstructionMethod.enveloped,
signature_algorithm: Union[SignatureMethod, str] = SignatureMethod.RSA_SHA256,
digest_algorithm: Union[DigestAlgorithm, str] = DigestAlgorithm.SHA256,
c14n_algorithm=CanonicalizationMethod.CANONICAL_XML_1_1,
c14n_algorithm: Union[CanonicalizationMethod, str] = CanonicalizationMethod.CANONICAL_XML_1_1,
):
if method is None or method not in SignatureConstructionMethod:
raise InvalidInput(f"Unknown signature construction method {method}")
Expand All @@ -113,16 +114,16 @@ def __init__(
def sign(
self,
data,
key=None,
key: Optional[Union[str, bytes, rsa.RSAPrivateKey, dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey]] = None,
passphrase: Optional[bytes] = None,
cert=None,
cert: Optional[Union[str, List[str], List[X509]]] = None,
reference_uri: Optional[Union[str, List[str], List[XMLSignatureReference]]] = None,
key_name: Optional[str] = None,
key_info: Optional[_Element] = None,
id_attribute: Optional[str] = None,
always_add_key_value: bool = False,
inclusive_ns_prefixes: Optional[List[str]] = None,
signature_properties=None,
signature_properties: Optional[Union[_Element, List[_Element]]] = None,
) -> _Element:
"""
Sign the data and return the root element of the resulting XML tree.
Expand All @@ -131,20 +132,15 @@ def sign(
:type data: String, file-like object, or XML ElementTree Element API compatible object
:param key:
Key to be used for signing. When signing with a certificate or RSA/DSA/ECDSA key, this can be a string/bytes
containing a PEM-formatted key, or a :py:class:`cryptography.hazmat.primitives.interfaces.RSAPrivateKey`,
:py:class:`cryptography.hazmat.primitives.interfaces.DSAPrivateKey`, or
:py:class:`cryptography.hazmat.primitives.interfaces.EllipticCurvePrivateKey` object. When signing with a
containing a PEM-formatted key, or a :class:`cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`,
:class:`cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, or
:class:`cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` object. When signing with a
HMAC, this should be a string containing the shared secret.
:type key:
string, bytes, :py:class:`cryptography.hazmat.primitives.interfaces.RSAPrivateKey`,
:py:class:`cryptography.hazmat.primitives.interfaces.DSAPrivateKey`, or
:py:class:`cryptography.hazmat.primitives.interfaces.EllipticCurvePrivateKey` object
:param passphrase: Passphrase to use to decrypt the key, if any.
:param cert:
X.509 certificate to use for signing. This should be a string containing a PEM-formatted certificate, or an
array of strings or OpenSSL.crypto.X509 objects containing the certificate and a chain of intermediate
certificates.
:type cert: string, array of strings, or array of OpenSSL.crypto.X509 objects
array of strings or :class:`OpenSSL.crypto.X509` objects containing the certificate and a chain of
intermediate certificates.
:param reference_uri:
Custom reference URI or list of reference URIs to incorporate into the signature. When ``method`` is set to
``detached`` or ``enveloped``, reference URIs are set to this value and only the referenced elements are
Expand Down Expand Up @@ -175,10 +171,9 @@ def sign(
:param signature_properties:
One or more Elements that are to be included in the SignatureProperies section when using the detached
method.
:type signature_properties: :py:class:`lxml.etree.Element` or list of :py:class:`lxml.etree.Element` s
:returns:
A :py:class:`lxml.etree.Element` object representing the root of the XML tree containing the signature and
A :class:`lxml.etree._Element` object representing the root of the XML tree containing the signature and
the payload data.
To specify the location of an enveloped signature within **data**, insert a
Expand All @@ -192,7 +187,7 @@ def sign(
if isinstance(cert, (str, bytes)):
cert_chain = list(iterate_pem(cert))
else:
cert_chain = cert
cert_chain = cert # type: ignore

input_references = self._preprocess_reference_uri(reference_uri)

Expand Down Expand Up @@ -235,7 +230,7 @@ def sign(
signed_info_node, algorithm=self.c14n_alg, inclusive_ns_prefixes=inclusive_ns_prefixes
)
if self.sign_alg.name.startswith("HMAC_"):
signer = HMAC(key=key, algorithm=digest_algorithm_implementations[self.sign_alg]())
signer = HMAC(key=key, algorithm=digest_algorithm_implementations[self.sign_alg]()) # type: ignore
signer.update(signed_info_c14n)
signature_value_node.text = b64encode(signer.finalize()).decode()
sig_root.append(signature_value_node)
Expand Down Expand Up @@ -378,7 +373,7 @@ def _build_sig(self, sig_root, references, c14n_inputs, inclusive_ns_prefixes):
reference_node = SubElement(signed_info, ds_tag("Reference"), URI=reference.URI)
transforms = SubElement(reference_node, ds_tag("Transforms"))
if self.construction_method == SignatureConstructionMethod.enveloped:
SubElement(transforms, ds_tag("Transform"), Algorithm=namespaces.ds + "enveloped-signature")
SubElement(transforms, ds_tag("Transform"), Algorithm=SignatureConstructionMethod.enveloped.value)
SubElement(transforms, ds_tag("Transform"), Algorithm=reference.c14n_method.value)
else:
c14n_xform = SubElement(transforms, ds_tag("Transform"), Algorithm=reference.c14n_method.value)
Expand Down
11 changes: 6 additions & 5 deletions signxml/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from typing import Any, List, Optional

from cryptography.hazmat.primitives import hashes, hmac
from lxml.etree import QName

from ..exceptions import InvalidCertificate, RedundantCert, SignXMLException

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


def ds_tag(tag):
return "{" + namespaces.ds + "}" + tag
return QName(namespaces.ds, tag)


def dsig11_tag(tag):
return "{" + namespaces.dsig11 + "}" + tag
return QName(namespaces.dsig11, tag)


def ec_tag(tag):
return "{" + namespaces.ec + "}" + tag
return QName(namespaces.ec, tag)


def xades_tag(tag):
return "{" + namespaces.xades + "}" + tag
return QName(namespaces.xades, tag)


def xades141_tag(tag):
return "{" + namespaces.xades141 + "}" + tag
return QName(namespaces.xades141, tag)


@dataclass
Expand Down
14 changes: 10 additions & 4 deletions signxml/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@
from OpenSSL.crypto import load_certificate
from OpenSSL.crypto import verify as openssl_verify

from .algorithms import CanonicalizationMethod, DigestAlgorithm, SignatureMethod, digest_algorithm_implementations
from .algorithms import (
CanonicalizationMethod,
DigestAlgorithm,
SignatureConstructionMethod,
SignatureMethod,
digest_algorithm_implementations,
)
from .exceptions import InvalidCertificate, InvalidDigest, InvalidInput, InvalidSignature
from .processor import XMLSignatureProcessor
from .util import (
Expand Down Expand Up @@ -141,7 +147,7 @@ def _apply_transforms(self, payload, transforms_node, signature, c14n_algorithm:
transforms = self._findall(transforms_node, "Transform")

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

for transform in transforms:
Expand Down Expand Up @@ -242,7 +248,7 @@ def verify(
:param parser:
Custom XML parser instance to use when parsing **data**. The default parser arguments used by SignXML are:
``resolve_entities=False``. See https://lxml.de/FAQ.html#how-do-i-use-lxml-safely-as-a-web-service-endpoint.
:type parser: :py:class:`lxml.etree.XMLParser` compatible parser
:type parser: :class:`lxml.etree.XMLParser` compatible parser
:param uri_resolver:
Function to use to resolve reference URIs that don't start with "#". The function is called with a single
string argument containing the URI to be resolved, and is expected to return a lxml.etree node or string.
Expand All @@ -259,7 +265,7 @@ def verify(
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.
:raises: :py:class:`cryptography.exceptions.InvalidSignature`
:raises: :class:`signxml.exceptions.InvalidSignature`
"""
self.hmac_key = hmac_key
self.require_x509 = require_x509
Expand Down
9 changes: 5 additions & 4 deletions signxml/xades/xades.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,10 @@ def verify( # type: ignore
**xml_verifier_args,
) -> List[XAdESVerifyResult]:
"""
Verify the XAdES signature supplied in the data and return a list of **VerifyResult** data structures
representing the data signed by the signature, or raise an exception if the signature is not valid. By default,
this requires the signature to be generated using a valid X.509 certificate. To enable other means of signature
validation, set the **require_x509** argument to `False`.
Verify the XAdES signature supplied in the data and return a list of :class:`XAdESVerifyResult` data structures
representing the data signed by the signature, or raise an exception if the signature is not valid. This method
is a wrapper around :meth:`signxml.XMLVerifier.verify`; see its documentation for more details and arguments it
supports.
:param expect_signature_policy:
If you need to assert that the verified XAdES signature carries specific data in the
Expand All @@ -340,6 +340,7 @@ def verify( # type: ignore
Parameters to pass to :meth:`signxml.XMLVerifier.verify`.
"""
self.expect_signature_policy = expect_signature_policy
xml_verifier_args["require_x509"] = True
verify_results = super().verify(data, expect_references=expect_references, **xml_verifier_args)
if not isinstance(verify_results, list):
raise InvalidInput("Expected to find multiple references in signature")
Expand Down

0 comments on commit 6879c98

Please sign in to comment.