structured-xades 0.21.1

XAdES mechanisms over caller-owned XML and explicit resource accounting
Documentation

structured-xades

Provider-neutral XAdES mechanisms with explicit memory and work accounting. The crate supports no_std + alloc and performs no implicit I/O.

An XML adapter supplies the original document, stable node identities, authenticated reference instructions and transformed byte streams. Cryptographic providers supply digest and signature operations: digests through the shared structured_asn1::digest::Digest contract, with certificate::DigestProvider adding only XML DigestMethod resolution and classification. Application code supplies trust inputs and decides whether the resulting evidence meets its policy.

Creating and augmenting signatures

produce supplies construction and append planning for the EN 319 132-1 V1.3.1 (2024-07) baseline. An XML owner implements produce::Writer over an unpublished staging transaction. Typed expanded-name events describe elements, attributes, text and exact binary values. The owner selects prefixes, validates the requested XML Schema datatypes, escapes text, encodes base64, registers IDs and admits every allocation and scan through the supplied Work. No XML parser, serializer, signing backend, network client or clock is added here.

  • produce::Basic::write creates the mandatory directly incorporated QualifyingProperties/SignedProperties, claimed zoned SigningTime, SigningCertificateV2 and the ordinary objects' MIME descriptions. The returned reference instructions identify the signed-properties and optional countersignature reference roles. write_key_info includes the actual complete signing certificate. The XML signer installs the references and signs through its selected provider. Optional policy, role, commitment and object timestamps can be staged before signing and use the existing complete validation pipeline.
  • produce::Augmentation::input prepares a future signature or archive timestamp without inserting a placeholder token. It requires a positive assessment of the same preparation. Archive creation requires LT or LTA material facts. timestamp::Input::prepare_append is the lower-level input selector, without a grade claim. Creation's complete unsigned tail and validation's prefix before an existing archive share one normative input-order kernel.
  • produce::Materials::missing derives every missing certificate/CRL/OCSP from the shared complete incorporation report. write_materials appends the actual missing values as AnyValidationData, preserving earlier subtrees. OCSP construction requires the original whole response matching the actual required signed body; a bare BasicOCSPResponse is insufficient. Empty plans emit no empty property. Placement, credential-specific evidence and temporal validity remain explicit existing assessments.
  • renewed::create derives RenewedDigestsV2 from the complete authenticated Manifest catalogue. The caller explicitly selects expiring algorithms. Shared processing resolves all selected old/new identities before hashing, refuses unchanged identities, hashes the actual detached ReferenceOutput and the canonical original instruction separately, and retains every selected pair. Unavailable input or resource/provider refusal cannot return a success subset. The new method is a borrowed URI, not a node that must already exist in the document. renewed::MethodProvider::resolve_new resolves that exact value through the same selected provider and compiled policy. Only parameter-free methods are supported by this construction capability; methods requiring parameters must be explicitly refused. Renewal::write emits the same URI through typed writer events and copies only the original canonicalization method with its parameters. No intermediate document generation is needed.
  • produce::Augmentation::check_response calls the existing request-bound RFC3161 checker and XAdES profile hook. Checked::facts retains negative results. Only privately issued evidence permits Checked::timestamp to borrow an appendable same-response token. write_timestamp checks the exact original source, selected signature and transaction tail before and after emission, and copies the original canonicalization method with its parameters.

For migration, implement Writer first, then Transaction for append operations. Its immutable base snapshot, signature and unsigned parent must describe the actual source used by the plan. The evolving draft is separate. Discard the whole draft on failure; publishing a partially emitted property is not supported.

For renewal producers migrating from a node-based new_method, pass the selected algorithm URI to renewed::create and implement renewed::MethodProvider beside the existing certificate::DigestProvider. The new hook must preserve the exact URI, actual digest identity, current Work and deployment-policy refusal; do not delegate by manufacturing an XML node. Renewal and Created now carry a lifetime for the borrowed method URI. Existing-node digest validation is unchanged.

use structured_asn1::memory::Work;
use structured_xades::{produce, timestamp::TimestampXml};

// The owner has already checked the response against the live request and the
// proposed input. Negative facts stay available in checked.facts().
fn append_checked<X, B, W>(
    plan: &produce::Augmentation<'_, X>,
    checked: &produce::CheckedFor<'_, X>,
    draft: &mut B,
    work: &W,
) -> Result<bool, produce::Error<B::Error>>
where
    X: TimestampXml,
    B: produce::Transaction<
        Id = X::Id, Document = X::Document, Generation = X::Generation,
    >,
    W: Work,
{
    let Some(token) = checked.timestamp() else { return Ok(false); };
    plan.write_timestamp(&token, draft, work)?;
    Ok(true)
}

The full sequence is: create/sign B-B, assess that generation, compute the future B-T input and its selected-provider digest, encode a structured_cms::timestamp::NewRequest, acquire a response outside the library, check it with explicit request/time facts, and append its accepted token. For LT/LTA, stage missing validation material and any selected digest renewal first, then re-prepare/re-assess that staged generation before computing the next archive input. Repeat this sequence for every renewal, including the previous TSA's material. Any intervening source change invalidates the plan and request. Previously protected signed bytes, namespace context, unsigned subtrees and archive inputs must survive commit; do not add an Object or rewrite an earlier counter after archiving it.

Construction alone asserts neither an attained level nor trust. Run the existing field, certificate, complete participant/material and baseline assessments on the resulting generation before publishing it. Native XML signing/C14N integration and downstream interoperability remain separate from this package's mechanisms.

Validating an existing signature

  1. prepare::prepare binds the selected signature to its complete protected SignedProperties and inventories its original properties and references.

  2. prepare::check_fields checks declared attributes, URI/OID fields, optional validation-reference scalar/binary syntax and the claimed signing time before certificate or provider effects. Unsigned certificate references remain a distinct model and cannot substitute for protected signing-certificate binding. validation separately compares original whole-artifact digests, RFC 4514 issuer/responder names and exact identifier time labels, and reads DER issuer/serial and OCSP byKey hints through the shared PKIX/CMS kernels. validation::compare_reference_catalogues visits every prepared reference and every compatible parsed XML material source, including certificates embedded in OCSP. It retains all comparison outcomes and original source ordinals while sharing finished hashes for identical artifacts and complete resolved algorithm identities. Responder-key matching uses the same OCSP identity kernel as response authentication; it does not repeat signatures. compare_reference_catalogues_with_tokens includes the separate original timestamp pool, including carried CRLs, whole OCSP envelopes and embedded certificates. Pool identities remain explicit and identical copies reuse one finished digest. reference_presence checks conditional incorporation against the complete original token/counter census; unavailable methods and identifier-only references remain unproven rather than becoming missing values. External parsed certificates, CRLs and whole OCSP responses can be compared through compare_reference_target without manufacturing XML copies. check_root_certificate_references covers the actual root CA/anchor path and excludes its signing certificate with the same digest memo. Its result does not assert the separate credential/TSA role rules. check_certificate_reference_roles checks ordinary references against the complete actual credential CA census, preserving shared TSA/signature uses and indeterminate older-certificate CA facts. It reuses the same lazy CA and digest kernels; it does not establish attribute-reference completeness. check_credential_certificate_references derives the required credential signer, ancestor and existing-anchor set from actual participant paths. Original protected and ordinary references can satisfy that set; redundant attribute copies remain a separate advisory finding. All sources share one certificate/provider memo, and absent optional properties need no catalogue. check_revocation_reference_roles applies the corresponding rule to actual status targets. It associates known basic OCSP bodies with their original whole XML/carried envelopes; missing envelopes remain unproven, with no payload substitution. It also retains every actual signer-path status contributor, including both lists of a delta source, and reports whether written ordinary references cover that required set. All comparisons reuse one exact-artifact/provider memo. Credential paths retain every non-anchor complete/delta or OCSP contributor too: ordinary and attribute references provide the A.1.4 alternatives, with redundant attribute copies reported as advisory. Attribute rows do not satisfy the signer's ordinary-reference set. Missing original whole OCSP envelopes remain unproven in either role. Shared status uses retain all participant purposes; source presence alone does not validate identifier hints or grade. check_crl_reference and check_ocsp_reference compose original issuer/responder and issue/production time facts; OCSP uses its authenticated signer and preflights mandatory time syntax before provider effects. check_ocsp_envelope_reference also binds that authenticated body to the exact whole response whose digest is checked; an identifier from another signed response cannot satisfy that relation. Its receipt retains the original property/row, whole envelope and signed response/signer. identifies_response requires both identifier fields and the digest when written. A legitimate identifier-only reference remains distinguishable through digest(); it does not protect the whole contents against substitution or establish status or trust. check_crl_value_reference likewise binds optional issuer/time fields and the mandatory whole-list digest to one actual parsed CRL. Its receipt retains the original property, row and list; independently successful facts from two lists cannot be combined. Missing optional identifiers remain valid when the digest identifies the list, while unreadable time prevents provider dispatch. None of these facts alone establishes a grade.

  3. certificate, counter, hashes, credential, timestamp and pki establish their respective source-bound facts. Format-neutral path, status, participant, timestamp-interval and material facts live in structured-ltv; XAdES receipts implement its traits, and xml::Access is its source query. The participant census (pki::participants, derived from these receipts only) includes plain countersignatures and timestamps nested in countersignatures. The timestamp census also retains the optional Annex A signature/reference and reference-only timestamps; their implicit inputs preserve original preceding-property order rather than reusing archive coverage.

    timestamp::with_tokens and with_counter_tokens require an explicit TokenProfile. Cms admits the RFC 5652 BER envelope independently of the unsigned XML encoding declaration. Etsi preserves declaration-driven admission, with DER required when the attribute is absent. Each TokenSource retains EncodingDeclaration::{Absent,Explicit} and separate EncodingConformance::{Matches,Mismatch,Unchecked}. A mismatch retains the strict reader's located cause. CER is readable as BER in Cms, but its canonical conformance is Unchecked(Cer); Etsi refuses CER. PER/XER and unknown URIs refuse before decoding in either profile.

    Matches reports the existing declared-encoding CMS envelope reader's admission, not signature validity, trust, a baseline level or full validation of opaque ANY values. CMS mode probes DER declarations with that same strict kernel under original/current budgets, then retains the BER model. It owns decoded bytes once and drops probe catalogues before retaining the model. Terminal resource failures stop the pool rather than becoming mismatches. DER requirements for signed attributes and TSTInfo, validation-material encoding rules and original XML/archive input bytes remain unchanged. Use Check::retain_actor_from for root timestamps and Check::retain_counter_from for countersignatures when checking a TokenEntry from either pool. These checks reuse the parsed token and its selected encoding. Standalone checks read the XML declaration directly.

  4. baseline::basic joins the checked fields, exact signing certificate, effective key, counter paths and actual hash uses. Every written protected, ordinary and attribute certificate-reference issuer hint uses the same DER reader. Located readable encoding faults prevent a positive basic predicate; original catalogue ordinals remain available through HintSource. A hint never replaces certificate digest binding. As specified in §5.2.2 Note 2, optional IssuerSerialV2 contents are retrieval hints: their agreement is not an additional binding requirement. The hint reports describe DER syntax, rather than asserting that their issuer/serial matches a selected certificate.

  5. structured_ltv::baseline::timestamped requires the complete token census and records each root signature timestamp's static time association.

  6. material::check_participants and material::credentials::check examine the complete participant/status closure. structured_ltv::baseline::long_term joins those original reports with the preceding receipts.

  7. For each archive, derive the preceding-participant closure and original element coverage. structured_ltv::baseline::archive_material joins the X.509 and credential material reports for that archive; structured_ltv::baseline::archives requires every root archive once, in original order. renewed::check_prepared separately processes every written root renewal's original Manifest instructions and digest pairs, retaining failed comparisons alongside successful ones.

  8. baseline::assess takes the stage reports of exactly the level that the preparation requested: Evidence::basic for B-B, Evidence::timestamped for B-T, Evidence::LongTerm for B-LT, and Evidence::Archival (the long_term and archives reports) for B-LTA. It refuses a different level, or an archive census built from another preparation or another authenticated credential census than the whole signature. The result states whether every condition of that level holds, including the earlier levels.

Receipts keep detailed negative findings. A material-presence predicate does not establish usable revocation status, historical trust, timestamp chronology or algorithm longevity. The requested structural B-B/B-T/B-LT/B-LTA requirements remain distinct from verified evidence and application acceptance. Extra containers do not promote a result to a higher level.

Current-operation accounting

Every query receives its current Work, including queries over previously retained document data. For example, base-datatype validation delegates to the XML owner's original scalar kernel:

use structured_asn1::memory::Work;
use structured_xades::{
    reference::LexicalSource,
    scalar::{self, Datatype, ScalarResult, ScalarXml},
    xml::Access,
};

fn check_original_uri<X: ScalarXml, W: Work>(
    access: &Access<'_, X, W>,
    node: X::Id,
) -> ScalarResult<X> {
    scalar::check(access, node, LexicalSource::SimpleContent, Datatype::AnyUri)
}

Allocation capacity remains charged for its lifetime. New traversal, normalization, copying and provider work require admission before execution; retained facts provide no credit for later work. Scoped normalized values cannot escape their visitor. Source/generation changes, resource failures and callback errors remain processing failures, separate from malformed, unsupported, missing or mismatching evidence.

Adapter boundary

An adapter must preserve original signature selection, reference identity, namespace interpretation, canonicalization choices and transformed bytes. QName syntax, original namespace resolution and scalar normalization belong to the XML owner. QNameXml retains the original lexical reading and separates an unbound prefix from malformed text and processing refusal. The attribute kernel accepts an explicit type equal to the actual pinned declaration, including contextual Cert/CertifiedRole/RecomputedDigestValue declarations. Overriding types needing other derivation/content validation remain explicitly unassessed; a resolved name alone never certifies them. Schema hints do not trigger fetches. The generic core contains no second XML parser, canonicalizer, URI resolver or crypto backend. Synthetic XML fixtures exercise the generic contracts; actual XML adapter and interoperability qualification are separate requirements.

The repository's scripts/qualify-xades-corpus.py checks SHA256-pinned public DSS fixtures (B-B, B-LT and B-LTA, negative and signature-wrapping cases, and a document with two signatures) using test-only xmlsec1/OpenSSL and libxml2 C14N through lxml. It compares the common signature/archive input streams byte-for-byte with independently canonicalized/transformed inputs and their signed token imprints, verifies XML/CMS and OCSP mathematics, including isolated signature and data mutations, and checks each negative fixture's stated defect. Upstream payloads and their licence notice remain outside packages in target/. These external tools are test oracles; the Rust runtime gains no FFI dependency. Historical trust, revocation acceptance and the bounded native XML adapter remain separate checks.