Signing, verification, and encryption¶
pygamlastan supports XML-DSig signing with either file-based private keys or a private key held on a PKCS#11 token (HSM), plus signature verification, XML encryption/decryption, and canonicalization. These live in pygamlastan.crypto.
Key management¶
pygamlastan.crypto.KeysManager holds keys and trusted certificates.
The convenience builders cover the common SP and IdP setups:
from pygamlastan import crypto
# SP: own signing key plus the trusted IdP certificate.
sp_keys = crypto.KeysManager.build_sp(sp_private_key_pem, idp_certificate_pem)
# IdP: own signing key.
idp_keys = crypto.KeysManager.build_idp(idp_private_key_pem)
# Or build one up by hand.
km = crypto.KeysManager()
km.add_key_pem(private_key_pem, usage="sign")
km.add_trusted_cert(peer_certificate) # PEM or DER bytes
Enveloped signatures¶
gamlastan signs a template: the document to be signed must already contain a
<ds:Signature> element with an empty DigestValue/SignatureValue and
the signing certificate in <ds:KeyInfo>. The signer fills in the digest and
signature:
signer = crypto.SamlSigner.from_pem(private_key_pem)
signed_xml = signer.sign_enveloped(xml_with_signature_template)
The IdP profiles produce documents ready to be signed this way; embed your certificate in the template and sign the serialized message.
Verifying signatures¶
Verify with a pygamlastan.crypto.SamlVerifier. Build one trusting a
single certificate, or from a fully populated KeysManager:
verifier = crypto.SamlVerifier.from_cert(idp_certificate_pem)
result = verifier.verify_enveloped(signed_xml)
if result.is_valid():
signed_ids = result.signed_reference_ids() # ids whose digest was checked
from_cert() registers the certificate both
as a verification key and as a trust anchor. By default the verifier runs in
trusted_keys_only mode, so a certificate embedded in the message’s
<KeyInfo> is not blindly trusted. signed_reference_ids() is exactly what
you feed to pygamlastan.profiles.process_response() as
verified_signed_ids.
PKCS#11 / HSM signing¶
To keep the private key on a hardware token, load the PKCS#11 module, open a
session, and create a token-backed signer. The private key never leaves the
token; the certificate comes from the signature template, so the
KeysManager may be empty.
from pygamlastan import crypto
provider = crypto.Pkcs11Provider("/usr/lib/softhsm/libsofthsm2.so")
session = provider.open_session("1234") # user PIN
hsm_signer = session.signer("saml-signing-key", "rsa-sha256")
signer = crypto.SamlSigner.with_pkcs11(hsm_signer)
signed_xml = signer.sign_enveloped(xml_with_signature_template)
Supported algorithm names include rsa-sha256, rsa-sha384,
rsa-sha512, rsa-pss-sha256, ecdsa-p256-sha256,
ecdsa-p384-sha384, ecdsa-p521-sha512, and ed25519.
Tip
Build the wheel in your target environment for HSM deployments
The published manylinux wheel targets a broad compatibility baseline. For
a PKCS#11/HSM deployment, prefer building the wheel in - or against - the
environment where it will run. The compiled extension links the host C and
crypto stack, and the PKCS#11 module (SoftHSM2, kryoptic, or a vendor HSM
driver) is dlopen-ed at runtime from that same host. Building where your
token tooling and system libraries live - for example maturin build
--release on the target host, or inside a container that matches production
- avoids glibc/loader and provider-ABI mismatches and lets you exercise the
signing path against the real module before shipping. The generic prebuilt
wheel is fine for development and for the file-key signing paths.
Redirect (detached) signatures¶
For the HTTP-Redirect binding the signature is detached and computed over the
query string. The bindings apply this for you when you pass a
signer to pygamlastan.bindings.redirect_encode(); the lower-level
sign_redirect_query() and
verify_redirect_query() are available when
you need them directly.
Encryption and decryption¶
pygamlastan.crypto.SamlEncryptor and
pygamlastan.crypto.SamlDecryptor handle XML encryption (for example
EncryptedAssertion). SamlEncryptor.for_certificate encrypts to a recipient
certificate (the per-request PEFIM flow):
encryptor = crypto.SamlEncryptor.for_certificate(recipient_cert_der)
encrypted = encryptor.encrypt(template_xml, plaintext_bytes)
decryptor = crypto.SamlDecryptor(km)
plaintext = decryptor.decrypt(encrypted)
Canonicalization¶
pygamlastan.crypto.exc_c14n() and pygamlastan.crypto.canonicalize()
expose Exclusive and Inclusive C14N, returning the canonical bytes:
canonical = crypto.exc_c14n('<a xmlns:b="urn:x"><b:c>hi</b:c></a>')