feat(wsse): pure-Python WS-Security signing (no xmlsec required)#1485
feat(wsse): pure-Python WS-Security signing (no xmlsec required)#1485martincollignon wants to merge 4 commits into
Conversation
Add `zeep.wsse.crypto` module as a drop-in alternative to the existing xmlsec-based `zeep.wsse.signature` module. Uses the `cryptography` library instead of the C-based `xmlsec`, making installation straightforward on all platforms. New capabilities beyond the xmlsec-based module: - No C library dependency (pure Python via `cryptography` + `lxml`) - PKCS#12 (.p12/.pfx) key loading support - Configurable signed parts (Body, Timestamp, UsernameToken, BinarySecurityToken, or any element with wsu:Id) - Per-reference inclusive namespace prefixes for exclusive C14N - Mixed digest/signature algorithms (e.g. SHA-256 digests + RSA-SHA1) Classes: CryptoSignature, CryptoBinarySignature, CryptoMemorySignature, CryptoBinaryMemorySignature, PKCS12Signature Install with: pip install zeep[crypto] Closes mvantellingen#1357, relates to mvantellingen#1419, mvantellingen#1428, mvantellingen#1363, mvantellingen#1318 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add features surfaced by review: - KeyIdentifier styles (ThumbprintSHA1, SubjectKeyIdentifier) - Configurable security header element ordering - Timestamp freshness validation (Created/Expires) - Certificate validity period validation - Internal _configure() method for cleaner initialization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
I've been using some of this code myself. It generally works, but I did have to make some modifications. Most importantly, the |
|
Thanks for the feedback. I pushed a follow-up commit (
Focused verification passed locally: PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python -m pytest tests/test_wsse_crypto.py
# 43 passed |
|
These changes seem to work, but I'm not sure how to set I also don't think passing in a timestamp at context construction time will work, as the timestamp will soon be out of date, and I believe we keep using the same security context. |
|
Thank you for your work on this. The BST change looks good. I think there is still an issue with the timestamp just being set once at wsse context creation time. Shouldn't the timestamp be created inside CryptoMemorySignature.apply()? something like: def _make_timestamp_token(self, envelope: etree._Element) -> None:
from datetime import timedelta
security = get_security_header(envelope) # type: ignore[no-untyped-call]
now = datetime.now(UTC)
timestamp = etree.SubElement(security, _wsu("Timestamp"))
ensure_id(timestamp) # type: ignore[no-untyped-call]
created = etree.SubElement(timestamp, _wsu("Created"))
created.text = now.strftime("%Y-%m-%dT%H:%M:%SZ")
expires = etree.SubElement(timestamp, _wsu("Expires"))
expires.text = (now + timedelta(seconds=300)).strftime("%Y-%m-%dT%H:%M:%SZ")
def apply(
self, envelope: etree._Element, headers: object
) -> tuple[etree._Element, object]:
if (self.sign_timestamp):
security = get_security_header(envelope) # type: ignore[no-untyped-call]
ts = security.find(_wsu("Timestamp"))
if (ts is None):
self._make_timestamp_token(envelope)
self._sign(envelope)
return envelope, headers |
Summary
Adds
zeep.wsse.crypto— a pure-Python alternative to the existing xmlsec-basedzeep.wsse.signaturemodule. Uses thecryptographylibrary instead of the C-basedxmlsec, making installation straightforward on all platforms.Motivation: We build Landbruget.dk, a Danish agricultural data transparency project. Integrating with Denmark's VetStat SOAP API for antibiotic usage data required WS-Security features that zeep's current xmlsec-based module doesn't support — and installing
xmlsecacross CI/CD and developer machines was a constant pain point. We ended up writing ~300 lines of manual XML signing. This PR extracts that into a clean, general-purpose module that benefits everyone.What's new
extra_references)Usage
Install:
pip install zeep[crypto]Related issues
cannot load crypto library for xmlsec.#1357 (xmlsec installation failures)Test plan
signature.py— fully additivecryptographynot installed (classes set toNonein__init__.py)cryptooptional dependency added tosetup.pyextras🤖 Generated with Claude Code