Skip to content

feat(wsse): pure-Python WS-Security signing (no xmlsec required)#1484

Closed
martincollignon wants to merge 51 commits intomvantellingen:masterfrom
martincollignon:feat/pure-python-wsse-signature
Closed

feat(wsse): pure-Python WS-Security signing (no xmlsec required)#1484
martincollignon wants to merge 51 commits intomvantellingen:masterfrom
martincollignon:feat/pure-python-wsse-signature

Conversation

@martincollignon
Copy link

Summary

Adds zeep.wsse.crypto — a pure-Python 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.

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 xmlsec across 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

Feature Current (xmlsec) New (crypto)
Sign Body + Timestamp
Sign UsernameToken
Sign BinarySecurityToken
Sign arbitrary elements ✅ (extra_references)
Inclusive namespace prefixes ✅ (per-reference)
PKCS#12 key loading
PEM key loading
Configurable digest algorithm Partial ✅ (SHA1/SHA256/SHA384/SHA512)
Configurable signature algorithm Partial ✅ (RSA-SHA1/SHA256/SHA384/SHA512)
No C library dependency
Signature verification

Usage

from zeep.wsse.crypto import CryptoSignature, CryptoBinarySignature

# Drop-in replacement for wsse.Signature
sig = CryptoSignature("key.pem", "cert.pem")

# BinarySecurityToken variant
sig = CryptoBinarySignature("key.pem", "cert.pem")

# PKCS#12 support
sig = CryptoBinarySignature.from_pkcs12("cert.p12", b"password")

# Sign extra elements + custom C14N prefixes (e.g. for government SOAP APIs)
sig = CryptoBinarySignature(
    "key.pem", "cert.pem",
    sign_username_token=True,
    sign_binary_security_token=True,
    digest_method="http://www.w3.org/2001/04/xmlenc#sha256",
    inclusive_ns_prefixes={"Body": ["wsse", "ds"]},
)

# Works with Compose
from zeep.wsse import Compose
from zeep.wsse.username import UsernameToken
wsse = Compose([UsernameToken("user", "pass"), sig])

Install: pip install zeep[crypto]

Related issues

Test plan

  • 33 new tests covering all features (signing, verification, algorithm combos, PKCS12, inclusive prefixes, Compose integration, edge cases)
  • Full existing test suite passes (504 passed, 0 failures)
  • No changes to existing signature.py — fully additive
  • Graceful fallback if cryptography not installed (classes set to None in __init__.py)

🤖 Generated with Claude Code

mvantellingen and others added 30 commits November 3, 2022 08:15
It's 6 years old already, how long can it stay modern ;-)
An exception will be thrown if a bool is returned from a SOAP service call.
`deserialize` soap.py will ask for the length of the result body, but it may not be allowed to take len on some result body types.
Added check if length is valid and returns the body directly if it is not.
I haven't tested with any other types, such as integers.
According to https://endoflife.date/python python 3.7 has been
EOSed 27 Jun 2023.
Filter all code over `pyupgracde --py38-plus`.

Signed-off-by: Tomasz Kłoczko <kloczek@github.com>
Signed-off-by: Tomasz Kłoczko <kloczek@github.com>
Signed-off-by: Tomasz Kłoczko <kloczek@github.com>
The latest release of the isodate package (0.7.2) doesn’t handle
timezone information for dates. While this is indeed not valid according
to the ISO specs we want to handle it anway.

At a workaround by stripping the timezone data ourselves
I bumped into this when opening several wsdl which in turn opened lots
of xsd, with 'file://' scheme.

The issue was that the `resp.raw.close` nor `resp.raw.release_conn` set
in the `FileAdapter` were ever called.

It's unclear to me whether this should be fixed in requests. It doesn't
do that great a job at resource management for the naive user aka
Human™. It makes sense to me that exhaustively reading `Response.raw`
should close it unless the caller explicitly set `stream` on the
request. Probably by using this `closing` pattern in the generator in
`Response.iter_content`.

Workarounds without this fix:
 - using a scheme-less url as zeep will assume it's a local path and
   open the file as a context manager.
 - use one of the caches from `zeep.cache` to hide duplicate open resources.
This seems to result in a conflict with xmlsec/libxml
mvantellingen and others added 21 commits October 13, 2024 10:20
This also moves the README from rST to markdown
Thanks again to Kraken Tech for making Zeep part of their OSS funding program by issuing a one-time grant in 2023
pytz dependency is removed. isodate is still needed for ISO durations
and missing features in Python's ISO datetime parser.
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>
@martincollignon martincollignon deleted the feat/pure-python-wsse-signature branch March 20, 2026 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.