Expand description
XML Digital Signatures (XMLDSig).
Implements XML Signature Syntax and Processing.
§Current Status
- URI dereference: same-document references (
"",#id,#xpointer(/),#xpointer(id('...'))) - ID attribute resolution with configurable attribute names
- Exact element, attribute, and namespace node sets for transform processing
- Enveloped, canonicalization, Base64, XPath 1.0, and XPath Filter 2.0 transforms
§Signing and verification
Build an enveloped signature with a modern algorithm, then verify it with the embedded certificate. Secure defaults require SHA-224 or stronger; legacy SHA-1 methods require an explicit operation policy opt-in.
use xml_sec::c14n::{C14nAlgorithm, C14nMode};
use xml_sec::xmldsig::{
DefaultKeyResolver, DigestAlgorithm, ReferenceBuilder, RsaSigningKey,
SignContext, SignatureAlgorithm, SignatureBuilder, Transform, VerifyContext,
X509CertificateKeyInfoWriter,
};
let c14n = C14nAlgorithm::new(C14nMode::Exclusive1_0, false);
let template = SignatureBuilder::new(c14n.clone(), SignatureAlgorithm::RsaSha256)
.add_reference(
ReferenceBuilder::new(DigestAlgorithm::Sha256)
.uri("#message")
.transform(Transform::Enveloped)
.transform(Transform::C14n(c14n)),
)
.key_info(true)
.build_template()?;
let xml = xml_sec::xmldsig::mutation::append_signature_to_root(
"<Message ID=\"message\">hello</Message>",
&template,
)?;
let signing_key = RsaSigningKey::from_pkcs8_pem(private_key_pem)?;
let key_info = X509CertificateKeyInfoWriter::from_pem(certificate_pem)?;
let signed = SignContext::new(&signing_key)
.key_info_writer(&key_info)
.sign_template(&xml)?;
let resolver = DefaultKeyResolver::default();
let result = VerifyContext::new().key_resolver(&resolver).verify(&signed)?;
assert!(matches!(result.status, xml_sec::xmldsig::DsigStatus::Valid));§XML Digital Signatures
§Migrating from 0.1.10
SignatureAlgorithm::EcdsaP256Sha256 and EcdsaP384Sha384 are now
EcdsaSha256 and EcdsaSha384, because the signature URI selects a digest while
the key selects P-256, P-384, or P-521. The enum also covers DSA, HMAC, RSA,
and ECDSA SHA-1/SHA-224/SHA-256/SHA-384/SHA-512 methods implemented by the
XMLDSig 1.1 interop pipeline. SignedInfo reports optional HMAC truncation through
hmac_output_length_bits. Both public types are non-exhaustive; consumers should
use wildcard match arms and obtain SignedInfo through the parser.
The xmldsig feature provides signing and verification pipelines for same-document XML
signatures and detached references whose payloads the caller supplies. It supports inclusive and
exclusive canonicalization, enveloped signatures,
Base64, XPath 1.0, and XPath Filter 2.0 transforms, RSA PKCS#1 v1.5, RSA-PSS, DSA,
ECDSA with P-256/P-384/P-521 keys, HMAC truncation, embedded X.509 certificates,
and configured key resolution.
The complete Phaos XMLDSig 3, 2012 XMLDSig 1.1, and XMLDSig Second Edition corpora are checked in for deterministic offline interoperability testing. Every signature document is executed through the public verification API and must be classified exactly once. Supported vectors must verify, invalid vectors must return their exact failure class, and vectors that depend on an unavailable capability such as XSLT or HMAC-MD5 must fail at an explicit typed boundary rather than being skipped.
§Experimental Recipient Encapsulation
experimental-pq supports libxmlsec1 1.3.13’s experimental
EncapsulationMechanism inside signature KeyInfo. It is an extension, not a
W3C signature algorithm. Use SignContext::new_encapsulation(&recipient_public)
with an HMAC builder/template and an explicitly permitting SigningPolicy.
The context fills the KEM ciphertext before canonicalization and reference
digests, then consumes the resulting 256-bit secret as the HMAC key.
Verification uses VerifyContext::decapsulation_key(&recipient_private) and
the same explicit algorithm permission. Supplying an ordinary HMAC key cannot
bypass the mechanism, and conflicting recipient parameter sets are rejected.
Signing and CLI preflight resolve same-document mechanisms reached through
KeyInfoReference, retaining the original ciphertext element for atomic mutation.
External KEM signing targets are not mutable caller documents and are rejected.
This preflight also inspects policy-permitted external KeyInfo references when
an ordinary signing key is supplied, so it cannot silently preserve a KEM
instruction. External XML parsing shares the operation budget; unrelated key
hints do not require decoding their key material.
Verification also resolves mechanisms reached through KeyInfoReference, using
the same source/URI policy, depth, cycle and shared parsing limits as other key
metadata. Multiple mechanisms in the resolved graph are rejected as ambiguous.
Ordinary explicit signing keys (including HMAC) and pinned verification keys ignore
unused key lookup hints, including malformed or unresolved hints. Bounded mechanism
discovery follows local and policy-permitted external references without decoding
unrelated key material.
The public inspect_pinned_verification_key_info preflight follows these references
too. Its original-document metadata return type cannot retain external DOM handles:
an external mechanism produces an explicit context error, never an absent-mechanism
result. Core verification with a decapsulation key supports external mechanisms.
An explicitly supplied KEM decapsulation key likewise ignores unrelated key candidates:
verification collects and validates the mechanism graph without importing lookup hints.
This does not relax mechanism syntax, algorithm permission, reference policy, cycle,
depth, ambiguity, or aggregate-work checks.
An actual mechanism, direct or referenced, is an operation instruction and cannot
be ignored by an ordinary signer or pinned verifier. Signing checks the projected XML size and
reserves raw ciphertext, encoded ciphertext, and the HMAC secret copy before
calling the encapsulation primitive.
KEM/HMAC does not authenticate the sender: anyone holding the public key
can create a new valid message. This path requires explicit
VerificationTrustMode::CryptographicOnly and returns
KeyTrustEvidence::NotEstablished; a trusted-key requirement fails before
decapsulation. Use a genuine digital signature when sender identity matters.
§Examples
examples/sign.rs builds an enveloped RSA-SHA256 signature with an embedded X.509
certificate. examples/verify.rs verifies that document through DefaultKeyResolver:
cargo run --example sign > signed.xml
cargo run --example verify -- signed.xmlThe signing and verification contexts share the same reference-transform implementation.
Both pipelines compile a deterministic operation graph before provider success or
document mutation can be reported. The graph orders parsing, key and URI resolution,
mutable-reference dependencies, transforms, digests, signature crypto, evidence, and
the final mutation gate. One operation context owns the immutable policy snapshot,
stable document generation, resolver and transform-cache state, cumulative budgets,
authenticated node identities, and first-failure evidence. Nested helpers and reparses
therefore cannot reset accounting or use identities captured from another generation.
Every executable graph node checks its dependencies before entering the closure that
performs parsing, resolution, canonicalization, or provider work. Nodes bound to a
document or caller-owned external resource additionally require a freshly observed
node identity or content fingerprint; the generic execution entry point rejects them.
XPath document-identity caching is operation-owned but chain-scoped, so cache entries
are reused within one live transform chain and invalidated before another chain or
after an internal reparse. Controlled mutations verify the expected document
generation before commit and advance that expectation by exactly one generation
inside the operation context. That context is also the sole owner of the authenticated
node set used to admit Manifest discovery.
Authenticated Manifest structure is deliberately compiled as a second phase of the
same graph only after SignatureValue succeeds; malformed or expensive unauthenticated
Manifest content is never parsed speculatively. Manifest dependency cycles are rejected
before their digest execution, while signing analyzes concrete mutable DigestValue
and SignatureValue targets and rejects cycles before changing the caller’s document.
SigningPolicy::manifest_processing controls direct <Object>/<Manifest>
reference generation. Processing fills Manifest digests before SignedInfo so a
SignedInfo reference to the Manifest authenticates the final values; Manifest
and SignedInfo references share transform work and the aggregate reference
ceiling. Nested Manifest dependencies are filled from leaves to dependants so
every digest observes finalized content; cycles are rejected before mutation.
Dependency analysis uses the effective XPath/XPath Filter 2.0 node-set, so a
self-reference that excludes its mutable DigestValue is not misclassified as
a cycle.
The default core policy leaves application-defined Manifest values
untouched, while the xmlsec1 compatibility CLI enables processing unless
--ignore-manifests is present.
XPathHereSemantics::Specification follows the XMLDSig <XPath> contract; callers
interoperating with legacy libxmlsec1 here() behavior can explicitly select
XPathHereSemantics::XmlSecLegacy on both contexts.
SigningPolicy::transforms applies to every canonicalization algorithm the signing pipeline
executes. Verification follows XMLDSig’s implicit C14N 1.0 coercion for a Reference chain ending
as a node set. Signature generation follows the Second Edition recommendation instead: before
digest planning it materializes C14N 1.1 as an explicit Transform wherever the generated chain
still produces a node set. The mutation is operation-gated, policy-checked, bounded, and included
in the canonicalized <SignedInfo> rather than being an invisible signing option. The declared
canonicalization method for <SignedInfo> is governed by the same policy. Reference output and
<SignedInfo> serialization consume one bounded canonicalization budget, so policy rejection
occurs during rendering rather than after an oversized buffer has already been allocated.
The same immutable policy controls the retained owned document and every committed signing
generation, including source validation in sign_with_builder, digest filling, SignedInfo
parsing, signature filling, and optional KeyInfo filling. An internal-DTD opt-in and XML node
ceiling therefore cannot be lost between stages. SignContext::sign_document and
sign_document_with_builder reuse an XmlDocument; string APIs are adapters over that boundary.
External callers that need non-default XML input rules construct that retained document with
XmlDocument::parse_with_policy(xml, &signing_or_verification_policy), so parsing and the later
operation share one immutable policy snapshot rather than independent DTD or resource settings.
Owned entry points revalidate the document’s parse provenance, so a document that required the
internal-DTD opt-in cannot cross into a stricter signing or verification context. Builder signing
stages template append, optional KeyInfo, every DigestValue, and SignatureValue in a private
document. A successful operation commits the complete result as one generation; any policy,
provider, key, transform, or serialization failure leaves the caller’s XML and generation
unchanged. Every staged mutation checks the active projected node count before commit.
Custom KeyInfoWriter output is treated as a separate untrusted XML input: its
byte ceiling is enforced before namespace wrapping or parsing, and its merged nodes remain under
the same operation ceiling as the rest of the signature.
Built-in writers emit RSAKeyValue/DSAKeyValue/ECKeyValue, DEREncodedKeyValue, embedded
X.509 certificate chains, or X509Digest selectors. Cryptographic writer work,
including the certificate digest used by X509Digest, runs through the same
operation provider as reference digests and signature generation. RSA, DSA, P-256, P-384, and
P-521 signing keys accept plain or password-encrypted PKCS#8 PEM and DER. The ECDSA signing-key
types also accept traditional SEC1 PEM and DER, which the CLI exposes through its generic
private-key options. DSA signatures emit
XMLDSig’s fixed-width r || s value;
DSA-SHA1 requires a 160-bit q, while DSA-SHA256 requires a 256-bit q, so a
key whose component width cannot be represented by the selected wire format is
rejected before signing.
DSA imports inspect borrowed PKCS#8 integers before allocating large-number
storage, including the optional RFC 5958 public component. When that component
is present it must agree with the public key derived from the private exponent;
otherwise import fails. Plain and password-encrypted PEM/DER use the same decoder,
also used for key-manager identity matching. Reciprocal xmlsec1 1.3.13 tests cover
1024/160, 2048/256 and 3072/256 keys, raw signature widths, embedded DSAKeyValue,
tampering, and explicit legacy-policy refusal.
Unencrypted DSA PEM imports distinguish malformed PEM (InvalidKeyPem), an
unexpected block label (InvalidKeyFormat), and invalid decoded PKCS#8 DER
(InvalidKeyDer). Non-whitespace trailing data is rejected at the PEM layer;
the private-key buffer passed to the DER decoder is zeroized on drop.
P-521 signing and verification use the same key-selected ECDSA framing contract
as P-256 and P-384.
Recursive KeyInfoReference materialization shares one candidate-work budget
across the complete reference graph. Each dereference and terminal source is
charged before expansion, so a shallow fan-out DAG cannot allocate an
exponential flattened source list while remaining below the depth limit.
Cycle identities include the owning document or stable external-resource URI:
equal fragment names in distinct documents remain independent, while reparsing
an external resource cannot hide a cycle that returns to that resource.
IdAttributeRegistration supplies immutable request context for non-standard ID attributes.
SignContext::id_attributes and VerifyContext::id_attributes apply the same global or
element-scoped registrations to operation start-node selection, every same-document Reference,
and XPath id() in ordinary and Filter 2.0 transforms.
scoped_any_namespace matches one element local name across namespaces, while scoped matches
one exact expanded name and uses None for no namespace. The registration is not stored in policy
and never comes from document content.
with_attribute_namespace(Some("urn:trusted")) restricts the registered attribute to an exact
namespace URI; None restricts it to unqualified attributes. Without this restriction, attribute
local names match across namespaces, preserving the CLI --id-attr contract. Registrations add
to the built-in ID/Id/id spellings, xml:id, and internal-DTD ID declarations; they do not
remove these defaults. xml:id and DTD ID values are normalized in the shared semantic DOM,
so borrowed and retained-document resolution, XPath, and canonicalization see the same value.
Attribute::xml_id_error() exposes non-fatal invalid-NCName and non-ID-declaration diagnostics.
As required by xml:id 1.0 sections 4 and 6,
ID assignment is preserved even when a diagnostic is present; callers can inspect it before
relying on such an attribute. Duplicate values on different elements remain ambiguous.
SignContext::sign_template selects the last descendant Signature template by default, preserving
append-then-sign workflows when a document already contains signatures. Process compatibility
boundaries that use donor document-order lookup can explicitly select
SignatureTemplateSelection::FirstDescendant; start_node_id scopes that selection to one subtree.
VerifyContext instead requires one unique document-level Signature by default and rejects
documents containing multiple candidates. Callers that intentionally consume multi-signature
documents can use verify_all() / verify_request() for complete bounded outcomes, or explicitly
choose first_document_signature() / scope lookup with start_node_id() for single-signature work.
SigningPolicy::rsa_keys validates normalized modulus width and public exponent before provider
dispatch. The default accepts 2048-8192-bit RSA keys for new signatures; compatibility callers can
raise or lower the minimum explicitly, while the 8192-bit implementation ceiling cannot be relaxed.
§Verification Requests and Evidence
VerifyContext::verify_all(&document) processes every Signature in document order and returns
VerifyEvidence. Ok means that the operation produced a report, not that the document passed:
inspect accepted(&document)? for generation-bound request acceptance. all_valid() reports only
the historical mathematical outcome, not authorization of the current document.
Malformed signatures and pipeline failures remain individual
SignatureEvidence results; an empty document never passes. ResourcePolicy::max_signatures
bounds discovery, report allocation, and signature work (default and absolute ceiling: 64).
For application authorization, pass VerificationRequest to verify_request. Request data is
borrowed: expected element identities, explicitly authorized keys for selected signatures,
caller-owned external bytes, signature cardinality, and an opaque correlation value. It cannot
relax the compiled security policy. The default requires all signatures to pass; AtLeast(n)
explicitly tolerates other failures, while Exactly(n) requires exactly n signatures, all valid.
Zero cardinality, duplicate targets, ambiguous key mappings, and foreign/stale identities are
rejected before key or digest work. External bytes cannot be supplied simultaneously in the
context and the request.
use xml_sec::XmlDocument;
use xml_sec::xmldsig::{
CallerTrustedSignatureKey, VerificationRequest, VerifyContext, VerifyingKey,
};
fn verify_expected(xml: &str, key: &dyn VerifyingKey) -> Result<bool, Box<dyn std::error::Error>> {
let document = XmlDocument::parse(xml.to_owned())?;
let context = VerifyContext::new();
let signatures = context.signature_identities(&document)?;
if signatures.len() != 1 {
return Ok(false);
}
let target = document.with_view(|view| view.node_for_id("payload", &[]))
.ok_or("missing or ambiguous payload ID")?;
let keys = [CallerTrustedSignatureKey { signature: signatures[0], key }];
let targets = [target];
let request = VerificationRequest {
expected_targets: &targets,
trusted_keys: &keys,
..VerificationRequest::default()
};
Ok(context.verify_request(&document, &request)?.accepted(&document)?)
}signature_identities is bounded discovery, not authentication; it does no cryptographic work.
Reports bind the processed Signature and Reference elements to the retained document generation.
accepted and covers_element reject foreign documents and every later document mutation.
Same-document references record the resolved target identity; external references record a SHA-256
fingerprint of resolved bytes before transforms, including XML Base resolution. The lexical URI
is diagnostic only. Reference digest status and typed key-trust evidence remain separate from
signature validity.
covers_element proves expanded element-content coverage only under a valid, authorized signature
(CallerTrusted or ValidatedX509). A matching digest under an invalid signature or an untrusted
embedded key is insufficient. Authenticated Manifest references contribute only when processing is
enabled and their own digests pass. Coverage excludes comments and the owning enveloped Signature;
it is not original-byte equality. Arbitrary XPath projections and binary transforms remain
indeterminate rather than claiming complete original-subtree coverage, as required by
XMLDSig 1.1 §8.1.1.
Any document mutation invalidates earlier coverage proofs. Same-name siblings cannot satisfy an
identity-based expected-target requirement.
Batch verification retains one parsed document and shares transform, XPath, external-resource, key-materialization, and canonicalization accounting across signatures. Authentication graphs remain signature-local, so one signature cannot authorize another signature’s Manifest. X.509 validation captures one operation time for every candidate path and signature.
§Provider Dispatch
SignContext::provider selects both digest primitives and operation randomness. Built-in ECDSA
signing obtains its prehash from that provider, while built-in RSA signing routes its blinding
randomness through the provider rather than acquiring operating-system randomness behind the
boundary. Custom SigningKey implementations that hash or use randomness inside their primitive
must implement SigningKey::sign_with_provider; externally managed keys can use the default
implementation only when the provider has no primitive work to observe.
VerifyContext::provider covers every verification-time cryptographic operation, including
reference digests, document signatures, X509Digest selector evaluation, X.509 candidate-path
edges, complete certificate paths, and CRL authentication performed by DefaultKeyResolver.
Certificate authentication uses a separate typed algorithm contract because certificate encodings
and algorithm permissions are independent of XMLDSig SignatureMethod. Unsupported certificate
OIDs remain typed path errors rather than ordinary signature mismatches. Every certificate OID
represented by that contract reaches the selected provider; the built-in provider may reject a
capability such as ECDSA-SHA512 while a custom provider can implement it. For an
id-RSASSA-PSS issuer key, the built-in provider requires typed SPKI parameters and enforces their
hash, MGF, minimum salt, and trailer-field restrictions before verifying a certificate signature.
Custom resolvers that evaluate cryptographic key metadata should override
KeyResolver::resolve_with_policy_and_provider; source-only resolvers can retain the default hook.
Before document-signature provider dispatch, the facade calls
VerifyingKey::validate_signature_value. Built-in resolved keys enforce the exact RSA modulus or
EC curve width there, so a permissive custom provider cannot reinterpret malformed XMLDSig wire
framing. Custom opaque keys must override that hook when their accepted framing depends on key
metadata unavailable through the generic algorithm URI. Providers that implement policy-selected
framing must also override validate_signature_value_with_policy and verify_with_policy; their
defaults intentionally preserve the standard-only hooks.
§Verification Policy
ECDSA SignatureValue uses the XMLDSig fixed-width r || s representation by
default for both signing and verification. Set
SigningPolicy::ecdsa_signature_value_encoding or
VerificationPolicy::ecdsa_signature_value_encoding to
EcdsaSignatureValueEncoding::XmlSecAsn1Der only at a trusted compatibility
boundary. DER is never auto-detected. Same-document bare fragments use
TransformPolicy::same_document_id_semantics; the library default requires the
standards form. SameDocumentIdSemantics::XmlSecBarename models libxmlsec1’s
default barename grammar backed by its single-quoted xpointer(id(...)) wrapper,
while preserving the barename rule that excludes comments. In contrast,
SameDocumentIdSemantics::XmlSecVisa3d performs direct registered-ID lookup.
The native CLI selects the donor barename mode by default and the direct mode for
--enable-visa3d-hack. ID registrations remain request context and duplicate
IDs fail in every mode.
The standards mode reverses UTF-8 URI percent escaping before resolving a fragment. Explicit
xpointer(/) and single-literal xpointer(id('...')) expressions retain comments, while empty
URIs and bare fragments remove them. XPointer caret escaping (^(, ^), ^^) is processed
before XPath literal matching. Compound XPath arguments, malformed escaping, and unbalanced
scheme parentheses are rejected. General XPointer expressions are not evaluated.
Verification separates signature correctness from key authorization.
VerificationPolicy::key_trust.mode defaults to VerificationTrustMode::RequireTrustedKey:
a .key(...) supplied by the caller, an explicitly authorized named inventory key,
an exact configured certificate pin, or a validated X.509 path is required before
signature-provider dispatch. Embedded keys and lookup certificates alone are candidates,
not authorization. Custom resolvers declare authorization through
KeyResolver::resolve_for_verification; the older discovery methods return candidates.
For mathematical interoperability checks only, explicitly select
VerificationTrustMode::CryptographicOnly in the immutable policy. A valid result in
that mode does not establish signer trust. VerifyResult::key_trust reports
NotEstablished, CallerTrusted, or ValidatedX509 separately from signature status;
authorization evidence does not turn an invalid signature into a valid one.
Validated path evidence contains bounded SHA-256 certificate identities, the exact anchor,
the operation’s single validation time, and whether CRL processing was enforced.
TrustedPublicKey cannot be constructed by merely parsing a certificate and is bound
to the trust policy that validated it. Resolvers re-evaluate trust for each operation;
updating an inventory does not reuse a previous operation’s authorization.
For production verification, configure KeyResolverConfig::lookup_certs with untrusted
certificates that selector-only X509Data may address or use as path intermediates, and configure
KeyResolverConfig::trusted_certs only with explicit trust anchors. With chain validation
enabled, a selected lookup certificate may chain through other lookup certificates but must end at
a trusted anchor. A trusted certificate selected directly remains an anchor, while embedded
certificates provide key material and do not become trusted merely because they appear in
<KeyInfo>. Exact DER duplicates across configured pools are evaluated once; when the same
certificate is present in both pools, its explicit trusted classification is retained.
ResourcePolicy::max_xml_document_bytes rejects verification and signing inputs before DOM
parsing; the same immutable ceiling is rechecked after signing mutations that enlarge the XML.
The selected node and depth ceilings are enforced by a streaming preflight before either parser
backend allocates a DOM, then rechecked on every staged copy, adapter parse, and committed generation.
ResourcePolicy::max_xml_parse_work_bytes separately bounds cumulative parser work across the
whole operation. Initial parsing, generated-template validation, binary-to-node-set adapters,
staged copies, digest dependency levels, Manifest recursion, and committed generations all charge
the same monotonic allowance. Recursive internal-entity replacement traversal is charged before a
DOM parser runs; failed parses do not restore the allowance. The policy-aware
XmlDocument byte APIs charge source bytes before decoding, then charge normalized
XML parsing to the same allowance, independently of backend selection.
Configured chain depth and candidate-path limits are validated after resolver defaults compose with
the operation policy. Candidate-path accounting includes every generated partial path, and
self-issued rollover certificates continue toward a distinct same-name issuer when its signature
validates; neither condition can bypass the configured work bounds or trust anchor requirement.
An explicit verification time may come from either boundary; if both boundaries provide one, the
timestamps must be identical because silently preferring either clock would discard caller policy.
Path validation excludes self-issued rollover CAs from pathLenConstraint, applies supported RFC
5280 NameConstraints to every subordinate certificate, and rejects critical extensions whose
semantics are not implemented. Repeated extension OIDs are rejected certificate-wide before any
extension-specific interpretation. Issuing certificates must assert BasicConstraints cA=true
and, when KeyUsage is present, keyCertSign. RFC 5280 requires conforming issuers to encode CA
BasicConstraints as critical, but path validation retains OpenSSL/xmlsec1 compatibility with
historical non-critical encodings. Empty certificate subjects require exactly one critical, non-empty
SubjectAlternativeName, and malformed GeneralName entries fail path validation regardless of whether
the subject is empty. Invalid DNS-based constraints, including email-domain and URI-host forms,
malformed IPv4/IPv6 encodings, non-contiguous CIDR masks, nonzero minimum, and any maximum
distance fail the path rather than behaving as ordinary name mismatches. Processed critical certificate
extensions are KeyUsage
(2.5.29.15), SubjectAlternativeName (2.5.29.17), BasicConstraints (2.5.29.19), and
NameConstraints (2.5.29.30). ExtendedKeyUsage (2.5.29.37) is processed whether critical or
non-critical on every certificate in the path: absent EKU and anyExtendedKeyUsage remain
unrestricted, while every other EKU must intersect
KeyTrustPolicy::allowed_extended_key_usages. The resolver-local and operation policy sets are
independent approvals, so a purpose must appear in both when VerifyContext composes them. Their
default empty sets therefore reject TLS-, code-signing-, and other purpose-restricted paths unless
the deployment explicitly approves that typed purpose at both boundaries. Critical
CertificatePolicies (2.5.29.32) still fails closed because policy-tree processing is not
implemented.
When X509Data supplies multiple selector categories, every
category must match certificates on the same selected, policy-valid path rather than unrelated
certificates from the lookup pool.
VerifyResult::status reports core validation: Valid means the cryptographic signature and
every <SignedInfo> reference succeeded. Invalid(reason) means core validation completed but
failed, such as a <SignedInfo> digest mismatch or invalid signature value.
When VerifyContext::process_manifests(true) is enabled, Manifest parsing starts only after the
<SignedInfo> references and SignatureValue both validate. Each authenticated Manifest reference
then has an independent status in VerifyResult::manifest_references. A failed Manifest reference
does not change the core VerifyResult::status; callers must inspect every Manifest result before
accepting data whose integrity depends on that Manifest. An empty Manifest result list is not proof
that the input contained no Manifest. VerifyContext::process_manifests(false) leaves the list empty
because Manifests were not processed; core validation failures and unsigned, unreferenced, or
structurally excluded Manifest blocks can also produce an empty list. Callers must distinguish the
disabled state from an enabled pass with no authenticated Manifest references.
Successful Manifest references whose transforms preserve the referenced XML structure extend the
authenticated set recursively. A sibling Manifest reached through such a reference is processed
once; failed digests and node-set transforms that discard structure never extend that trust frontier.
Manifest references obey the same per-reference transform-count ceiling and transform allowlist as
<SignedInfo> references; a violation is recorded in that Manifest reference’s independent status.
They also share ResourcePolicy::max_references with <SignedInfo>: every parsed Manifest
reference consumes that operation-wide budget, including entries whose transforms are unsupported
and retained only as independent failure results.
Malformed XMLDSig structure, unsupported algorithms in core signature processing, disallowed URIs
in <SignedInfo> references, and inconsistent KeyInfo metadata are processing errors rather
than validity statuses. Manifest policy violations and unsupported transforms remain independent
per-reference statuses as described above. Treat both Invalid(reason) and an API error as a
rejected document; never continue an authentication flow after either outcome.
External references are disabled by default. Verification and signing callers must both allow
their URI class with UriTypeSet and provide every payload through
VerifyContext::external_resources or SignContext::external_resources; neither operation
performs network or filesystem I/O. Individual resources are limited to 8 MiB and the
complete map to 32 MiB. The same aggregate ceiling is charged again per successful dereference,
so repeatedly referencing one map entry cannot multiply transform work or retained diagnostics
without bound. Signing dependency analysis and actual digest execution share that aggregate
budget across reparsed document generations. External key retrieval has an independent policy boundary: callers must
also opt in with VerifyContext::allowed_retrieval_method_uri_types. Allowing external signed
payloads never implicitly allows external key material. RetrievalMethod currently accepts
untransformed external rawX509Certificate data, untransformed direct same-document X509Data,
and the Merlin same-document X509Data XPath selection. Relative external Reference and
RetrievalMethod URIs are resolved against the owning element’s effective xml:base using RFC
3986 before lookup, so resource-map keys must use that resolved URI. The compiled resource policy
bounds both inherited xml:base components and cumulative URI-resolution bytes across external
References, RetrievalMethod, Reference transforms, and SignedInfo C14N 1.1 fixup; implementation
ceilings are 64 components and 1 MiB per operation. Other retrieval transform chains fail closed
instead of being ignored.
XMLDSig 1.1 KeyInfoReference has separate source and URI policy gates plus a
bounded recursion depth. Same-document and caller-owned external targets must
resolve to exactly one KeyInfo; missing targets, non-KeyInfo targets, and
cycles fail closed. Fragment lookup and nested references inside an external XML
document use that document’s ID namespace. Its fetched resource identity is the
RFC 3986 base for relative nested references, with inherited xml:base applied
on top before lookup. The mandatory URI attribute may be empty: under
Reference URI semantics it selects the current document, and element-valued
key lookup requires that document’s root element to be KeyInfo. Empty
self-references still fail through the ordinary cycle detector. All nested
external dereferences
consume the operation’s shared aggregate external-resource budget and never
trigger implicit I/O.
Callers that inspect template or signature metadata before running a complete
operation can use materialize_signing_key_info_references or
materialize_verification_key_info_references. Both entry points recursively
expand KeyInfoReference under the same candidate-work, depth, cycle, target,
parser, and external-resource bounds as the complete operation. Retrieval
methods nested in an external referenced document are processed while that
document’s URI context is active. Same-document RetrievalMethod sources remain
unmaterialized for the complete signing or verification pipeline to process.
The entry points differ only in the typed policy applied at the boundary and
consume their caller-configured UriReferenceResolver to bind a fresh
operation-scoped external-resource budget before any dereference.
Direct policy-free calls on HmacVerificationKey enforce the secure default
HMAC key and output minima. Compatibility truncation is available only through
the policy-aware hooks used by VerifyContext with an explicit HmacPolicy.
Both HmacVerificationKey and HmacSigningKey own their secret bytes in
zeroizing storage, so dropping either key overwrites the retained secret before
releasing its allocation.
VerifyContext::allowed_transforms applies to Reference transforms and implicit C14N,
the declared SignedInfo canonicalization method, and supported RetrievalMethod transforms.
Allowing XPath for signed payload processing therefore also explicitly permits the bounded
Merlin X509Data retrieval selector; omitting XPath rejects that key-retrieval path.
CRL checking is meaningful only inside authenticated X.509 path validation. A policy that enables
CRLs without enabling certificate-chain validation is rejected during context construction rather
than silently accepting a control the resolver cannot enforce.
CRL structure is validated before authority-key applicability is selected: duplicate CRL or
CRL-entry extension OIDs fail closed, and deltaCRLIndicator is rejected regardless of criticality.
Applicable CRLs must include the RFC 5280 nextUpdate field and the verification time must remain
inside the bounded thisUpdate through nextUpdate validity window.
The removeFromCRL reason is rejected in complete CRLs because it is meaningful only in a delta
CRL. URI subject alternative names
likewise require one RFC 3986 authority, including syntactically valid userinfo, before their host
can participate in NameConstraints matching.
Critical NameConstraints also retain their DER structure during validation: the extension must
contain at least one permitted or excluded subtree, every present subtree collection must be
non-empty, and unsupported minimum/maximum distance fields fail closed.
Internal DTD declarations are disabled by default. Verification requires the operation’s
VerificationPolicy::xml.allow_internal_dtd decision; the
VerifyContext::allow_internal_dtd(true) convenience method updates that same policy snapshot
rather than bypassing a separate policy gate. The decision applies consistently to the signed
document and caller-supplied detached XML parsed by node-set transforms. Direct transform callers
use execute_transforms_with_policy with the same VerificationPolicy::xml allowance. Signing uses
SigningPolicy::xml.allow_internal_dtd across its complete pipeline. External entity resolution
remains disabled. Internal-DTD ID declarations are carried into the shared semantic DOM and ID
index without implicit external DTD loading. Other attribute names require an explicit
request-scoped ID registration. XSLT is intentionally not executed because transforms operate on
attacker-controlled documents; an authenticated Manifest reference using unsupported XSLT is
reported as an invalid per-reference result without changing core SignedInfo validity.
§Current Scope
The optional legacy-algorithms feature adds MD5 and RIPEMD-160 reference digests,
RSA-MD5/RSA-RIPEMD160, HMAC-MD5/HMAC-RIPEMD160, and ECDSA-RIPEMD160. Secure library
defaults reject these methods even when compiled. Explicitly allow signature methods through
VerificationPolicy::signature_algorithms / SigningPolicy::signature_algorithms and reference
digests independently through digest_algorithms. Permitting a signature never permits its
reference digest implicitly. The same digest policy gates X509Digest certificate selectors,
including embedded certificates, configured lookup certificates, KeyInfoReference, and
RetrievalMethod, before selector hashing. ECDSA-RIPEMD160 retains independent curve selection.
These are
historical interoperability mechanisms, not recommended algorithms for new documents.
Implemented signature methods include DSA-SHA1/SHA256; HMAC-SHA1/SHA224/SHA256/SHA384/SHA512;
RSA PKCS#1 v1.5 with SHA-1/SHA224/SHA256/SHA384/SHA512; RSA-PSS with SHA-1,
SHA224/SHA256/SHA384/SHA512 and SHA3-224/256/384/512; and ECDSA
SHA-1/SHA224/SHA256/SHA384/SHA512 and SHA3-224/256/384/512. ECDSA selects P-256, P-384, or P-521
from the key independently of the hash identifier. Digest methods include
SHA-1/SHA224/SHA256/SHA384/SHA512 and SHA3-224/256/384/512. EdDSA supports pure Ed25519,
Ed25519ctx, Ed25519ph, Ed448, and Ed448ph with their distinct RFC 8032 domain separation.
HMAC output can be omitted for full width or explicitly truncated on an octet boundary, subject to
HmacPolicy key and output minima.
The selected length is checked before reference resolution or transforms in both signing and
verification, and before signing-key callbacks. Unsupported signature methods retain a typed
SigningError::ParseSignedInfo(ParseError::UnsupportedAlgorithm) error during signing preflight.
This early parameter check does not query key metadata; template signing queries it once in
the key preflight before producing the signature.
SignatureBuilder::signature_context(SignatureContext) places context bytes inside the
authenticated SignedInfo/SignatureMethod. Contexts are limited to 255 octets by the
cryptographic wire contract; a nonempty context on a method without context support is rejected
rather than ignored. Context-capable methods default to an empty context. The XML context
elements use libxmlsec1’s explicit experimental namespace
http://www.aleksey.com/xmlsec/2025/12/xmldsig-more#; these parameter elements are not
standardized by RFC 9231. Parsing enforces text and decoded-width bounds before allocating
context storage and rejects malformed Base64, nested elements, and incompatible parameters.
The pinned libxmlsec1 1.3.13 oracle does not read the XML context element for pure Ed448;
it does for Ed448ph. This library supports pure Ed448’s RFC 8032 context through the explicit
extension and validates it against RFC 8032 section 7.4’s known-answer vector. For reciprocal
pure-Ed448 interoperability with that donor, use an empty context.
The optional experimental-pq feature compiles ML-DSA-44/65/87 and
SLH-DSA-SHA2-128f/128s/192f/192s/256f/256s. Their XML signature identifiers are experimental
libxmlsec1 extensions, not W3C-standardized XML methods. Compilation alone does not grant
permission: signing and verification require an explicit algorithm allowlist in the immutable
operation policy. PKCS#8 and SPKI use RFC 9881/RFC 9909 encodings. ML-DSA import accepts
seed-only, expanded-only, and consistent seed-plus-expanded keys; expanded imports are checked
before signing. See the maintained RustCrypto patch for provenance,
validation, and reproduction. These key primitives are dispatched through CryptoProvider.
HMACOutputLength uses XML Schema integer syntax (including XML whitespace and
an optional leading plus), not Unicode whitespace. XMLDSig 1.1 requires at least 80 bits and
half the hash width; the default policy tightens that floor to at least 128 bits. Arbitrary
non-octet truncation accepted by some legacy implementations is not XMLDSig 1.1 conformance.
RSA-SHA1, DSA-SHA1, HMAC-SHA1, and ECDSA-SHA1 are legacy algorithms disabled by default.
Verification requires an independent opt-in: None in the general
VerificationPolicy::signature_algorithms allowlist does not bypass
VerificationPolicy::key_trust.allowed_legacy_signature_algorithms, where callers must explicitly
opt in to each legacy method needed by that operation. Signing requires the method in an explicit
SigningPolicy::signature_algorithms allowlist; selecting a SHA-1 method in document content never
enables it. KeyTrustPolicy::rsa_keys and
KeyTrustPolicy::dsa_keys separately enforce key-strength minima on resolved verification keys and
on issuer keys used to authenticate certificate paths and applicable CRLs; their secure defaults
are 2048 bits. Compatibility operations may lower the DSA minimum to 1024 bits, but the
non-configurable DSA implementation ceiling remains 3072 bits.
Built-in keys supplied directly through VerifyContext::key enforce the same immutable operation
policy as keys produced by DefaultKeyResolver; selecting a key source cannot tighten or weaken
the configured minimum. Custom opaque VerifyingKey implementations remain responsible for key
metadata that the core cannot inspect.
Selecting the algorithm in untrusted XML does not opt the operation into legacy cryptography, and
this gate runs before key resolution.
X.509 path and CRL authentication additionally supports standard RSA-PSS with SHA-256/SHA-384/
SHA-512 parameters, including RFC 4055 issuer-key restrictions, and Ed25519/Ed448.
With experimental-pq, the provider also authenticates pure ML-DSA and supported
SLH-DSA-SHA2 certificate/CRL signatures using the empty PKIX context, independently
of the XML context parameter. Certificate/CRL
permissions come from KeyTrustPolicy::certificate_signature_algorithms, independently
of the XML signature allowlist. None permits supported classical methods but rejects PQ;
Some(CertificateSignatureAlgorithms::Allowlist(...)) permits only exact listed methods
(including RSA-PSS digest, MGF digest and salt length). The compatibility CLI explicitly
selects AllSupported, leaving provider capability and strict AlgorithmIdentifier validation
in force without artificially restricting PSS salt lengths. This borrowed permission reaches
candidate path selection, complete path and CRL
validation before provider dispatch. Compiling experimental-pq alone never grants trust.
Permission rejection removes only the affected candidate path; another permitted path to an
explicit trust anchor remains usable. If no permitted path exists, the policy rejection is returned.
Legal XML Base64 whitespace in context and SignatureValue text consumes the operation’s
document resource budget; normalized payloads retain their protocol and implementation width limits.
Signature
AlgorithmIdentifier parameters are validated before provider dispatch: DSA, ECDSA, EdDSA,
and these post-quantum methods
require absent parameters; RSA PKCS#1 accepts NULL or absent; RSA-PSS requires valid typed
parameters. An id-RSASSA-PSS issuer key with absent parameters imposes no parameter restrictions,
as required by RFC 4055 section 3.3; present key parameters constrain the signature hash, MGF,
minimum salt length, and trailer field. Implicit external resource loading is not supported.
§RSA-PSS Parameters
The nine fixed RSA-PSS methods use MGF1 with the signature digest and a salt of that digest’s
width. SignatureAlgorithm::RsaPss(RsaPssParameters) represents the parameterized method from
RFC 9231 section 2.3.9, with independent
digest, mgf_digest, and exact salt_len. The builder emits RSAPSSParams; template signing
and verification parse the same typed parameters.
An omitted digest defaults to SHA-256. An omitted MGF uses the signature digest; an explicit
MGF1 element without its digest uses SHA-256. An omitted salt length uses the signature digest’s
width. Only trailer field 1 is supported. Parsing rejects duplicate, reordered, unknown, or
malformed parameters, and salt lengths outside the nonnegative xs:int range. Key preflight
rejects salts that cannot fit the RSA encoded message before signing work.
SHA-1 in either the message digest or MGF requires explicit legacy permission for the exact
signature method, or an explicit KeyTrustPolicy::parameterized_rsa_pss family permission.
RsaPssPermission::AllSupported in the signing or verification policy grants the parameterized
family without enumerating salt lengths; verification retains the independent legacy trust gate.
All family permissions default to ExactOnly. The compatibility CLI sets both permissions before
reading input, never deriving policy from a document’s parameters.
An id-RSASSA-PSS public key also enforces its hash, MGF, minimum salt length,
and trailer restrictions; an unrestricted RSA key does not add those restrictions.
RustCrypto supports independent hashes and exact salts. Other providers expose only their native
capabilities, with no fallback; see provider capabilities. The narrow
local RSA-PSS adaptation retains the existing RSA arithmetic, blinding, and
fault checks. Tests cover every pinned xmlsec1 RSA-PSS fixture, reciprocal signing, restricted
keys, malformed encodings, mixed hashes, and salt/modulus boundaries.
Saved OpenSSL vectors run without external programs; set OPENSSL_BIN to additionally require
live reciprocal OpenSSL checks. A configured but unavailable oracle fails the test. CI enables it.
Re-exports§
pub use builder::ReferenceBuilder;pub use builder::SignatureBuilder;pub use builder::SignatureBuilderError;pub use digest::DigestAlgorithm;pub use digest::compute_digest;pub use digest::compute_digest_with_provider;pub use digest::constant_time_eq;pub use keys::DefaultKeyResolver;pub use keys::HmacSha1VerificationKey;pub use keys::HmacVerificationKey;pub use keys::InspectedKeyCandidateBudget;pub use keys::KeyResolutionError;pub use keys::KeyResolverConfig;pub use keys::VerificationKey;pub use parse::KeyInfo;pub use parse::KeyInfoSource;pub use parse::KeyValueInfo;pub use parse::ParseError;pub use parse::Reference;pub use parse::RetrievalMethodTransforms;pub use parse::RsaPssParameters;pub use parse::SignatureAlgorithm;pub use parse::SignatureContext;pub use parse::SignedInfo;pub use parse::X509DataInfo;pub use parse::find_signature_node;pub use parse::parse_key_info;pub use parse::parse_reference;pub use parse::parse_signed_info;pub use parse::x509_certificate_matches_selectors;pub use sign::ComputedReferenceDigest;pub use sign::DerEncodedKeyValueInfoWriter;pub use sign::DsaSigningKey;pub use sign::EcdsaP256SigningKey;pub use sign::EcdsaP384SigningKey;pub use sign::EcdsaP521SigningKey;pub use sign::HmacSigningKey;pub use sign::KeyInfoWriteError;pub use sign::KeyInfoWriter;pub use sign::KeyValueInfoWriter;pub use sign::RsaSigningKey;pub use sign::SignContext;pub use sign::SignatureTemplateSelection;pub use sign::SigningDigestError;pub use sign::SigningError;pub use sign::SigningKey;pub use sign::SigningKeyError;pub use sign::SigningPublicKeyInfo;pub use sign::X509CertificateKeyInfoWriter;pub use sign::X509DigestKeyInfoWriter;pub use sign::compute_reference_digest_values;pub use sign::fill_reference_digest_values;pub use sign::validate_signing_key;pub use signature::SignatureVerificationError;pub use signature::verify_dsa_signature_spki;pub use signature::verify_ecdsa_signature_pem;pub use signature::verify_ecdsa_signature_pem_with_encoding;pub use signature::verify_ecdsa_signature_spki;pub use signature::verify_ecdsa_signature_spki_with_encoding;pub use signature::verify_rsa_signature_pem;pub use signature::verify_rsa_signature_spki;pub use transforms::BASE64_TRANSFORM_URI;pub use transforms::DEFAULT_IMPLICIT_C14N_URI;pub use transforms::ENVELOPED_SIGNATURE_URI;pub use transforms::RELATIONSHIP_TRANSFORM_URI;pub use transforms::RelationshipSelector;pub use transforms::Transform;pub use transforms::XPATH_FILTER2_TRANSFORM_URI;pub use transforms::XPATH_TRANSFORM_URI;pub use transforms::XPathExpression;pub use transforms::XPathFilter;pub use transforms::XPathFilterOperation;pub use transforms::XPathHereSemantics;pub use transforms::execute_transforms;pub use transforms::execute_transforms_with_policy;pub use transforms::parse_transforms;pub use trust::KeyTrustEvidence;pub use trust::ResolvedVerificationKey;pub use trust::TrustedPublicKey;pub use trust::X509TrustEvidence;pub use types::NodeSet;pub use types::TransformData;pub use types::TransformError;pub use verify::CallerTrustedSignatureKey;pub use verify::DsigError;pub use verify::DsigStatus;pub use verify::FailureReason;pub use verify::KeyResolver;pub use verify::ReferenceCoverage;pub use verify::ReferenceProcessingError;pub use verify::ReferenceResult;pub use verify::ReferenceSet;pub use verify::ReferencesResult;pub use verify::SignatureEvidence;pub use verify::SignatureKeyInfoMetadata;pub use verify::SignatureRequirement;pub use verify::SignatureSelection;pub use verify::UriTypeSet;pub use verify::VerificationRequest;pub use verify::VerifyContext;pub use verify::VerifyEvidence;pub use verify::VerifyResult;pub use verify::VerifyingKey;pub use verify::has_same_document_encapsulation;pub use verify::inspect_pinned_verification_key_info;pub use verify::inspect_signing_key_info;pub use verify::inspect_verification_key_info;pub use verify::materialize_signing_key_info_references;pub use verify::materialize_verification_key_info_references;pub use verify::process_all_references;pub use verify::process_reference;pub use verify::verify_signature_with_pem_key;pub use x509::X509ChainError;pub use x509::X509ChainOptions;pub use x509::verify_x509_certificate_chain;
Modules§
- builder
- Builders for deterministic XMLDSig signature templates.
- digest
- Digest computation for XMLDSig
<Reference>processing. - keys
- Configuration and key material for XMLDSig key resolution.
- mutation
- Lexical XML mutation helpers for the XMLDSig signing pipeline.
- parse
- Parsing of XMLDSig
<Signature>and<SignedInfo>elements. - sign
- Signing-side XMLDSig digest computation.
- signature
- Signature verification helpers for XMLDSig.
- transforms
- Transform pipeline for XMLDSig
<Reference>processing. - trust
- Key discovery and authorization are distinct from signature validation.
- types
- Core types for the XMLDSig transform pipeline.
- uri
- URI dereference for XMLDSig
<Reference>elements. - verify
- XMLDSig reference processing and end-to-end signature verification pipeline.
- x509
- X.509 certificate path and revocation validation.
Structs§
- EdDsa
Signing Key - Opaque EdDSA private key. Private bytes are zeroized by the primitive on drop.
Enums§
- PqAlgorithm
- FIPS 204/205 parameter sets exposed by libxmlsec1’s experimental XML methods.