Pure Rust XML Security for XMLDSig, XML Encryption, C14N, SAML 2.0, and X.509, built to replace libxmlsec1 workflows without a C toolchain or system libraries.
No C dependencies. No CMake. No system XML or crypto packages. Just cargo add xml-sec.
Warning
Early-stage pre-release. The API is unstable, XMLDSig/XMLEnc coverage is still incomplete, and this crate should not yet be used in production.
- End-to-end XML security: sign, verify, encrypt, and decrypt through public Rust APIs.
- SAML-ready primitives: enveloped signatures, encrypted assertions, X.509 keys, XPath, canonicalization, and strict same-document ID handling.
- Pure Rust deployment: RustCrypto,
x509-parser, and selectable Rust XML backends replace the libxml2/OpenSSL-style native dependency stack. - Fail-closed security policy: typed immutable policy controls algorithms, trust, XML parsing, transforms, external resources, and cumulative operation budgets.
- xmlsec1 interoperability: a native
xmlsec1CLI surface plus deterministic offline compatibility corpora and generated parity ledgers. - Encoding-aware XML input: strict bounded decoding for UTF-8, UTF-16, UTF-32, and supported legacy XML encodings before backend-independent semantic parsing.
- Reusable safe-Rust XSLT 1.0 engine: an isolated workspace crate with explicit resolver, clock, extension, and resource capabilities.
cargo add xml-secDefault features provide C14N, XMLDSig, XMLEnc, and the xmloxide XML backend. Adding the crate as
a dependency builds the library; install the command-line tool from the same package:
cargo install xml-sec
xmlsec1 versionApplications can build only the capabilities they need:
xml-sec = { version = "0.1", default-features = false, features = ["xmldsig", "c14n", "xml-backend-xmloxide"] }| Area | Available today |
|---|---|
| Canonicalization | Canonical XML 1.0/1.1, Exclusive C14N, comments, and document subsets |
| XML signatures | XMLDSig signing and verification, RSA/DSA/ECDSA/HMAC, XPath transforms, Manifest, KeyInfo, and caller-provided references |
| XML encryption | AES-CBC/GCM, RSA-OAEP, AES Key Wrap, multiple recipients, and Element/Content replacement |
| X.509 | Certificate key extraction, chain validation, CRLs, and policy-controlled trust |
| SAML 2.0 | Signed assertions and encrypted-assertion workflows covered by integration tests |
| XML input | Strict bounded byte decoding, entity/depth/node limits, stable node identities, and generation-safe mutation |
| Crypto | Provider-neutral contracts and opaque key handles with pure-Rust RustCrypto as the default implementation |
| CLI | Native xmlsec1 process interface for sign, verify, encrypt, decrypt, keys, and capability discovery |
Unsupported algorithms, unavailable provider capabilities, untrusted key sources, implicit external I/O, malformed encodings, and exhausted budgets return explicit errors. Initial parsing, recursive transforms, resolver work, staged mutations, and decryption retries share one operation-wide budget instead of resetting limits inside helpers.
examples/sign.rs creates an enveloped RSA-SHA256 signature and examples/verify.rs verifies it
through the embedded X.509 certificate:
cargo run --example sign > signed.xml
cargo run --example verify -- signed.xmlThe signing and verification pipelines support same-document and caller-provided references,
XPath 1.0 and XPath Filter 2 transforms, Manifest, structured KeyInfo, and policy-controlled
X.509 validation. See XML Digital Signatures for algorithms, transform semantics,
key resolution, failure handling, and current interoperability boundaries.
EncryptedDataBuilder supports direct symmetric keys, RSA-OAEP recipients, AES Key Wrap
recipients, and Element/Content document replacement:
use xml_sec::xmlenc::{DataEncryptionAlgorithm, EncryptedDataBuilder};
fn example() -> Result<(), Box<dyn std::error::Error>> {
let key = [0x42_u8; 16];
let encrypted_data = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
.direct_key(key)
.direct_key_name("application-content-key")
.encrypt_xml("<secret>value</secret>")?;
assert!(encrypted_data.encrypted_data_xml.contains("EncryptedData"));
Ok(())
}See XML Encryption for reciprocal decryption, key transport, recipient selection, document replacement, and parser policy.
xmloxide and roxmltree are independent parsers behind one source-preserving semantic DOM.
C14N, XPath, XMLDSig, XMLEnc, and mutation code do not branch on parser-specific tree types.
Use xml-backend-roxmltree for a thin alternative build, or compile xml-backends-all to select
Xmloxide, Roxmltree, or fail-closed Differential parsing per operation:
xml-sec = { version = "0.1", default-features = false, features = ["xmldsig", "xmlenc", "c14n", "xml-backends-all"] }use xml_sec::XmlBackend;
use xml_sec::xmldsig::VerifyContext;
# let xml = "<root/>";
let result = VerifyContext::new()
.xml_backend(XmlBackend::Roxmltree)
.verify(xml);
# let _ = result;Runtime selection never falls back to an implementation absent from the build. Differential mode requires both complete semantic arenas to agree on topology, expanded names, attributes, namespaces, text, processing instructions, source order, and ranges. Each backend receives the full configured allowance; the mode does not split one parser budget in half.
Raw-byte APIs select XML encoding from the BOM, byte signature, declaration, or trusted resolver metadata. UTF-8 remains borrowed when possible; other supported encodings are strictly transcoded under the same materialization ceiling. Conflicting declarations, malformed byte sequences, ambiguous BOM-less UTF-16/UTF-32, and unsupported EBCDIC variants fail explicitly.
API migration for this pre-release change: encoding::decode_xml_octets now requires a
maximum decoded-byte count as its second argument. encoding::XmlEncodingError is now the
shared, non-exhaustive xml_sec::encoding::XmlEncodingError; update matches to handle the new error
variants and include a fallback arm. Code constructing ResourcePolicy with every field must
also set max_xml_namespace_bindings (or start from ResourcePolicy::default() and override
selected fields). These changes keep decoding and namespace-scope allocation under explicit
resource limits. For trusted resolver-provided encoding metadata, use
xml_input::decode_xml_bounded(bytes, Some(encoding), maximum_decoded_bytes); the unbounded
decoder remains internal.
xmlsec1 list-transforms
xmlsec1 list-key-data
xmlsec1 verify --xml-backend xmloxide signed.xmlThe binary covers sign/verify, template-preserving encrypt/decrypt, AES key generation, capability
queries, donor option syntax, and deterministic process statuses through the same policy and
provider pipelines as the library. A fat build accepts
--xml-backend xmloxide|roxmltree|differential; a thin build rejects unavailable backends.
See the CLI compatibility guide for commands, formats, key lookup, diagnostics, and interoperability boundaries.
Interoperability evidence is deterministic and offline. The complete Phaos XMLDSig 3, XMLDSig 1.1,
and XMLDSig Second Edition corpora execute through public sign/verify APIs with exact valid,
invalid, and fail-closed classifications. Selected unmodified upstream XMLDSig, XML Encryption,
and key-management scenarios execute against the Rust xmlsec1 binary without network access or
a system libxmlsec1 installation.
The generated compatibility ledger tracks libxmlsec1 1.3.13 public surface and operation behavior with source and test evidence.
xml-sec-xslt is an XML-security-neutral XSLT 1.0 compiler and runtime. It
owns stylesheet compilation, XPath/XSLT semantics, template execution, result-tree construction,
deterministic budgets, and XML/HTML/text serialization. Its ExecutionEnvironment makes resolver
access, XInclude processing, and operation time explicit. The default grants no XInclude or ambient
clock access; callers may supply a fixed clock for reproducible EXSLT date functions or explicitly
request host-clock compatibility.
The engine currently remains an in-repository workspace crate rather than a separately published
package; xml-sec is the only crate published to crates.io. The main crate continues to reject XMLDSig
XSLT transforms until the policy, resource identity, and node-set adapter contracts are connected.
The shared strict byte-decoding and lexical source is compiled into xml-sec and reused by the
internal XSLT workspace crate.
| Specification | Status |
|---|---|
| Canonical XML 1.0 | Implemented; full-document and document-subset vectors |
| Canonical XML 1.1 | Implemented; xml:id and xml:base subset rules |
| Exclusive C14N | Implemented; InclusiveNamespaces PrefixList support |
| XMLDSig 1.0/1.1 | Core sign/verify pipelines; complete Merlin, Phaos 3, XMLDSig 1.1, and Second Edition corpora classified and executed |
| XML Encryption | Core AES-CBC/GCM, RSA-OAEP, and AES-KW pipelines implemented; broader conformance work continues |
| XSLT 1.0 | Separate safe-Rust engine implemented; XMLDSig transform adapter not yet connected |
The project tracks stable Rust and supports Rust 1.92 or newer. Detailed contracts and limitations live in the XMLDSig guide, XML Encryption guide, and CLI guide.
Apache-2.0
If xml-sec is useful in your stack, you can help fund continued implementation and maintenance.
USDT (TRC-20): TFDsezHa1cBkoeZT5q2T49Wp66K8t2DmdA