Package: signxml
SignXMLis an implementation of the W3CXML Signaturestandard in
Python. This standard (also known as “XMLDSig”) is used to provide payload security inSAML 2.0,XAdES, andWS-Security, among other uses. The standard is defined in theW3C RecommendationXML Signature Syntax and Processing Version 1.1.SignXMLimplements all of the required components of the Version 1.1
standard, and most recommended ones. Its features are:Use of a libxml2-based XML parser configured to defend againstcommon XML attackswhen verifying signaturesExtensions to allow signing with and verifying X.509 certificate chains, including hostname/CN validationExtensions to sign and verifyXAdESsignaturesSupport for exclusive XML canonicalization with inclusive prefixes (InclusiveNamespaces PrefixList, required to verify signatures generated by
some SAML implementations)Modern Python compatibility (3.7-3.11+ and PyPy)Well-supported, portable, reliable dependencies:lxml,cryptography,pyOpenSSLComprehensive testing (including the XMLDSig interoperability suite) andcontinuous integrationSimple interface with useful, ergonomic, and secure defaults (no network calls, XSLT or XPath transforms)Compactness, readability, and extensibilityInstallationpip install signxmlNote: SignXML depends onlxmlandcryptography, which in turn depend onOpenSSL,LibXML, and Python tools to interface with them. You can install those as follows:OSCommandUbuntuapt-getinstall--no-install-recommendspython3-pippython3-wheelpython3-setuptoolspython3-opensslpython3-lxmlRed Hat,
Amazon Linux,
CentOSyum installpython3-pippython3-pyOpenSSLpython3-lxmlMac OSInstallHomebrew, then runbrew install python.SynopsisSignXML uses thelxml ElementTree APIto work with XML data.fromlxmlimportetreefromsignxmlimportXMLSigner,XMLVerifierdata_to_sign="<Test/>"cert=open("cert.pem").read()key=open("privkey.pem").read()root=etree.fromstring(data_to_sign)signed_root=XMLSigner().sign(root,key=key,cert=cert)verified_data=XMLVerifier().verify(signed_root).signed_xmlTo make this example self-sufficient for test purposes:Generate a test certificate and key usingopenssl req-x509-nodes-subj"/CN=test"-days1-newkeyrsa-keyoutprivkey.pem-outcert.pem(runyum install opensslon Red Hat).Pass thex509_cert=certkeyword argument toXMLVerifier.verify(). (In production, ensure this is replaced with
the correct configuration for the trusted CA or certificate - this determines which signatures your application trusts.)Verifying SAML assertionsAssumingmetadata.xmlcontains SAML metadata for the assertion source:fromlxmlimportetreefrombase64importb64decodefromsignxmlimportXMLVerifierwithopen("metadata.xml","rb")asfh:cert=etree.parse(fh).find("//ds:X509Certificate").textassertion_data=XMLVerifier().verify(b64decode(assertion_body),x509_cert=cert).signed_xmlSigning SAML assertionsThe SAML assertion schema specifies a location for the enveloped XML signature (between<Issuer>and<Subject>). To sign a SAML assertion in a schema-compliant way, insert a signature placeholder tag at that location
before calling XMLSigner:<ds:SignatureId="placeholder"></ds:Signature>.See what is signedIt is important to understand and follow the best practice rule of “See what is signed” when verifying XML
signatures. The gist of this rule is: if your application neglects to verify that the information it trusts is
what was actually signed, the attacker can supply a valid signature but point you to malicious data that wasn’t signed
by that signature. Failure to follow this rule can lead to vulnerability against attacks likeSAML signature wrapping.In SignXML, you can ensure that the information signed is what you expect to be signed by only trusting the
data returned by theverify()method. Thesigned_xmlattribute of the return value is the XML node or string that
was signed.Recommended reading:W3C XML Signature Best Practices for Applications,On Breaking SAML: Be Whoever You Want to Be,Duo Finds SAML Vulnerabilities
Affecting Multiple ImplementationsEstablish trustIf you do not supply any keyword arguments toverify(), the default behavior is to trustanyvalid XML
signature generated using a valid X.509 certificate trusted by your system’s CA store. This means anyone can
get an SSL certificate and generate a signature that you will trust. To establish trust in the signer, use thex509_certargument to specify a certificate that was pre-shared out-of-band (e.g. via SAML metadata, as
shown inVerifying SAML assertions), orcert_subject_nameto specify a
subject name that must be in the signing X.509 certificate given by the signature (verified as if it were a
domain name), orca_pem_file/ca_pathto give a custom CA.XML signature construction methods: enveloped, detached, envelopingThe XML Signature specification defines three ways to compose a signature with the data being signed: enveloped,
detached, and enveloping signature. Enveloped is the default method. To specify the type of signature that you want to
generate, pass themethodargument tosign():signed_root=XMLSigner(method=signxml.methods.detached).sign(root,key=key,cert=cert)verified_data=XMLVerifier().verify(signed_root).signed_xmlFor detached signatures, the code above will use theIdorIDattribute ofrootto generate a relative URI
(<ReferenceURI="#value"). You can also override the value ofURIby passing areference_uriargument tosign(). To verify a detached signature that refers to an external entity, pass a callable resolver inXMLVerifier().verify(data,uri_resolver=...).See theAPI documentationfor more details.XML representation details: Configuring namespace prefixes and whitespaceSome applications require a particular namespace prefix configuration - for example, a number of applications assume
that thehttp://www.w3.org/2000/09/xmldsig#namespace is set as the default, unprefixed namespace instead of using
the customaryds:prefix. While in normal use namespace prefix naming is an insignificant representation detail,
it can be significant in some XML canonicalization and signature configurations. To configure the namespace prefix map
when generating a signature, set theXMLSigner.namespacesattribute:signer=signxml.XMLSigner(...)signer.namespaces={None:signxml.namespaces.ds}signed_root=signer.sign(...)Similarly, whitespace in the signed document is significant for XML canonicalization and signature purposes. Do not
pretty-print the XML after generating the signature, since this can unfortunately render the signature invalid.XML parsing security and compatibility withxml.etree.ElementTreeSignXML uses thelxmlElementTree library, not theElementTree from Python’s standard library,
to work with XML. lxml is used due to its superior resistance to XML attacks, as well as XML canonicalization and
namespace organization features. It is recommended that you pass XML string input directly to signxml before further
parsing, and use lxml to work with untrusted XML input in general. If you do passxml.etree.ElementTreeobjects to
SignXML, you should be aware of differences in XML namespace handling between the two libraries. See the following
references for more information:How do I use lxml safely as a web-service endpoint?ElementTree compatibility of lxml.etreeXML Signatures with Python ElementTreeXAdES signaturesXAdES (“XML Advanced Electronic Signatures”)is a standard for attaching
metadata to XML Signature objects. This standard is endorsed by the European Union as the implementation for itseSignatureregulations.SignXML supports signing and verifying documents usingXAdESsignatures:fromsignxmlimportDigestAlgorithmfromsignxml.xadesimport(XAdESSigner,XAdESVerifier,XAdESVerifyResult,XAdESSignaturePolicy,XAdESDataObjectFormat)signature_policy=XAdESSignaturePolicy(Identifier="MyPolicyIdentifier",Description="Hello XAdES",DigestMethod=DigestAlgorithm.SHA256,DigestValue="Ohixl6upD6av8N7pEvDABhEL6hM=",)data_object_format=XAdESDataObjectFormat(Description="My XAdES signature",MimeType="text/xml",)signer=XAdESSigner(signature_policy=signature_policy,claimed_roles=["signer"],data_object_format=data_object_format,c14n_algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315",)signed_doc=signer.sign(doc,key=private_key,cert=certificate)verifier=XAdESVerifier()verify_results=verifier.verify(signed_doc,x509_cert=certificate,expect_references=3,expect_signature_policy=signature_policy)forverify_resultinverify_results:ifisinstance(verify_result,XAdESVerifyResult):verify_result.signed_properties# use this to access parsed XAdES propertiesAuthorsAndrey Kislyukand SignXML contributors.LinksProject home page (GitHub)DocumentationPackage distribution (PyPI)Change logList of W3C XML Signature standards and draftsW3C Recommendation: XML Signature Syntax and Processing Version 1.1W3C Working Group Note: XML Signature Best PracticesXML-Signature InteroperabilityW3C Working Group Note: Test Cases for C14N 1.1 and XMLDSig InteroperabilityW3C Working Group Note: XML Signature Syntax and Processing Version 2.0(This draft standard proposal was never finalized and is not in general use.)Intelligence Community Technical Specification: Web Service Security Guidance for Use of XML Signature and XML
EncryptionXMLSec: Related linksOWASP SAML Security Cheat SheetOkta Developer Docs: SAMLBugsPlease report bugs, issues, feature requests, etc. onGitHub.VersioningThis package follows theSemantic Versioning 2.0.0standard. To control changes, it is
recommended that application developers pin the package version and manage it usingpip-toolsor similar. For library developers, pinning the major version is
recommended.LicenseCopyright 2014-2023, Andrey Kislyuk and SignXML contributors. Licensed under the terms of theApache License, Version 2.0. Distribution of the LICENSE and NOTICE
files with source copies of this package and derivative works isREQUIREDas specified by the Apache License.