๐ฆ asx-rs
Async-native, memory-safe AS2 + AS4 EDI transport library for Rust.
asx-rs implements the AS2 (RFC 4130) and
AS4 (OASIS ebMS3 + eDelivery)
protocols โ the wire formats used by PEPPOL, CEF eDelivery, BDEW, and tens of thousands of
EDI trading partner connections worldwide.
โจ Features
๐จ AS2 (RFC 4130 / RFC 5751)
- Send and receive signed payloads (CMS/S/MIME, RSA-SHA256)
- Synchronous and asynchronous MDN (Message Disposition Notification)
- MIC computation for end-to-end integrity verification
CmsSmimeTrustVerifier::requiring_signature()โ reject encrypted-only (unsigned) inbound messages so sender authentication can be enforced- Signed-receipt enforcement: an unsigned MDN is rejected when a signed receipt was
requested (
As2ReceiveMdnRequest::require_signed_mdn) โ no silent non-repudiation downgrade - Payload compression (RFC 5402 / zlib,
compressionfeature) - Configurable interop mode: strict vs. relaxed for legacy partners
- Retry classification (
SuccessConfirmed/Indeterminate/AcceptedPendingVerification)
๐ฌ AS4 (ebMS3 + OASIS eDelivery AS4 v1.15)
- One-Way/Push send and receive (SOAP 1.1 and 1.2)
- One-Way/Pull with bounded in-memory pull store per MPC partition
- WS-Security XMLDSig signing โ RSA-SHA256 and ECDSA-SHA256; algorithm is
auto-detected from the signing certificate key type. Works with NIST P-256/P-384/P-521
and BSI BrainpoolP256r1/P384r1 (required by BDEW AS4-Profil ยง2.2.6.2.1 / BSI TR-03116-3 ยง9.1).
The signature covers the entire
eb:Messagingheader block (all UserMessage routing/authorization metadata), the SOAP Body, and each payload attachment. On receive, the consumedeb:Messagingblock is bound to the verified signature (XML-Signature-Wrapping defence). Signing certificates weaker than RSA-2048 are rejected. - XML Encryption (XMLenc11) โ auto-dispatches on recipient certificate key type:
- RSA recipient โ RSA-OAEP (SHA-256/MGF1-SHA-256) โ PEPPOL / CEF eDelivery
- EC recipient โ ECDH-ES + ConcatKDF (NIST SP 800-56A ยง5.8.1) + AES-128 Key Wrap (RFC 3394) โ BDEW AS4-Profil ยง2.2.6.2.2 / BSI TR-03116-3 ยง9.2
X509PKIPathv1BST token type (WsSecOutboundKeyInfoProfile::X509PKIPathv1)- Reception Awareness / Non-Repudiation of Receipt, verified end to end โ
verify_sync_responseparses the counterparty's synchronouseb:Receiptnamespace-correctly (prefix-agnostic, CDATA-aware), verifies its WS-Security signature against the pinned partner certificate, and checks everyMessagePartNRInformationdigest against the digests the sent message was actually signed over. The signal it acts on must itself be covered by that signature, so a counterparty cannot leave a genuinely signed element in place and append an unsigned acknowledgement (XML Signature Wrapping). Aneb:Errorreply comes back as a typed signal with the ebMS3 code intact, so retry-vs-dead-letter routing keys on the real reason.As4HttpTransport::send_and_verifydoes send + verify in one call. require_encrypted_inboundpolicy field โ rejects unencrypted push messages at the policy layer (symmetric torequire_signed_push); useAs4PushPolicy::regulated_with_decryption_key(pem)to set it together with the key that satisfies it- Streaming receive path with bounded memory; conversation-ordered delivery gate
- P-Mode registry for per-partner MEP/security configuration
- SBDH 1.3 envelope wrap/unwrap for PEPPOL and CEF eDelivery
๐ Security & Reliability
- Type-state lifecycle machine โ compiler enforces
UntrustedBytes โ StructurallyParsed โ CryptographicallyVerified โ ContentDecrypted โ DomainReady; payloads cannot reach application code without every gate passing - OCSP stapling + responder-based revocation checking (
async-ocspfeature) - PKIX certificate chain validation (fail-closed on empty trust store)
TtlDedupStorage/BoundedFifoDedupStorageprevents replay attacks- Reconciliation hooks for async delivery confirmation
๐ HTTP Transport
- Axum server integration (
serverfeature) โ drop-inRouterfor AS2 and AS4 ingress - Async HTTP egress via
reqwest(clientfeature) - Inbound endpoint governance against unexpected sources
๐ Observability
EventBuswith fan-out broadcast and ordered mpsc audit channelDurableAuditSinktrait for pluggable persistent audit backendsEventBusMetricswith lock-freeAtomicU64counters- Optional Prometheus/OpenMetrics sink (
prometheusfeature)
๐งฉ Interop
- Profile stacking with regional packs and per-partner overlays
interop-strict(default) andinterop-relaxedfeature-gated modes- Exception policies (
InteropExceptionPolicy) for well-known deviations
๐งช Testing (testing feature โ never enable in production)
InsecureBypassAs4Verifierโ skips all WS-Security verification; parity withInsecureBypassTrustVerifieron the AS2 sideMockAs4Endpoint(testing + server) โ local HTTP AS4 server that accepts any push (signed, unsigned, encrypted, or plain), records messages in an async channel, and returns synchronous AS4 receipts. Configure decryption viaMockAs4Endpoint::builder().with_decryption_key_pem(key_pem).bind(addr).await?, and signed NRR receipts via.with_receipt_signing_material(cert_pem, key_pem)so the full receipt-verification round trip is exercisable without real PKIEventBus::new_for_testing()โ zero-configBestEffortbus; never fails on emit when no subscriber is active โ no morenew_with_config_and_mode(...)boilerplateDurableInMemoryDedupBackendโ in-memory dedup withis_durable() = true; passes the strict durable-backend guard without a real persistent storeAs4HttpTransport::new_for_localhost_testing()โ plain-HTTP transport for integration tests againstMockAs4Endpointonhttp://127.0.0.1:โฆ; SSRF guards disabled for the test binary only.send_and_verify_to_localhostis the receipt-verifying counterpartgenerate_self_signed_ec_keypair(cn, curve)โ self-signed EC cert+key (P-256 through BrainpoolP512r1); no openssl/rcgen dev-dependency needed in downstream cratesgenerate_self_signed_rsa_keypair(cn, bits)โ same for RSAverifier_sealโ re-export of theAs4Verifiersealed trait; allows downstream crates to write custom verifiers (recording, fault-injection, etc.)
๐ Quick Start
[]
# AS4 โ PEPPOL / CEF eDelivery (RSA) or BDEW (EC/ECDH-ES) โ same code, key type decides
= { = "0.11", = ["as4", "client", "server", "async-ocsp"] }
# AS2 + AS4 with compression
= { = "0.11", = ["as2", "as4", "compression", "client", "server", "async-ocsp"] }
[]
# Testing without PKI certificates (MockAs4Endpoint, bypass verifier, keypair generators)
= { = "0.11", = ["as4", "testing", "server"] }
as2andas4are not in the default feature set โ add them explicitly. Thetestingfeature is compile-error-guarded against release profile builds.
๐ Examples
AS4 โ Send a signed push message
The signing algorithm is chosen automatically from the key type โ no configuration needed:
- RSA certificate โ RSA-SHA256 (PEPPOL / CEF eDelivery)
- EC certificate (P-256, BrainpoolP256r1, โฆ) โ ECDSA-SHA256 (BDEW, BSI TR-03116-3 ยง9.1)
use ;
use SessionContextBuilder;
use EventBus;
// Session with trust anchor + signing material โ one fluent chain:
let session = new
.with_signing_material
.with_trust_anchor_pem
.with_fingerprint_sha256 // optional hard pin
.build?;
// RSA or EC signing key โ auto-detected:
let = new
.action
.service
.signing_cert_pem // RSA or EC
.signing_key_pem
.recipient_cert_pem // RSA โ RSA-OAEP; EC โ ECDH-ES
.encrypt
.build?;
let bus = new?;
let output = send_async.await?;
// output.soap_envelope.body โ multipart/related bytes to POST
// output.http_content_type โ HTTP Content-Type header value
AS4 โ Send and verify the receipt (Non-Repudiation of Receipt)
A receipt is only evidence of delivery once its digests have been checked
against the message you actually sent. Never scan the response body for
<eb:Receipt โ a conformant counterparty may use a different namespace prefix
or CDATA, and a substring match cannot verify NRR at all.
use ;
use ;
let transport = new?;
let outcome = transport
.send_and_verify
.await?;
match outcome.signal
Already have the response bytes? Verify them directly:
let receipt = verify_sync_response?
.into_receipt?; // eb:Error becomes an Err here
As4ReceiptPolicy::relaxed() accepts unsigned acknowledgements for interop
bring-up โ but a digest or signature that is present is always verified, and a
mismatch is always an error.
AS4 โ Receive push with encryption enforcement
use ;
use Arc;
// Sets the decryption key and `require_encrypted_inbound = true` together, so
// the invariant cannot be split โ there is no ordering to get wrong.
let policy = regulated
.with_mandatory_inbound_encryption
.build?;
let outcome = receive_push_with_dedup_async.await?;
AS4 โ Testing without PKI certificates
use MockAs4Endpoint;
use EventBus;
use ;
// Plain receive: bind to a random port โ no cert, no WIRK, no PEPPOL test PKI needed
let endpoint = bind.await?;
// Receive encrypted messages: configure decryption key via builder
let endpoint = builder
.with_decryption_key_pem
.bind
.await?;
let url = endpoint.local_url; // "http://127.0.0.1:PORT/as4/inbox"
// Test EventBus: zero-config, never fails on emit (BestEffort)
let bus = new_for_testing; // requires `testing` feature
// Wait for the first message (returns None if the endpoint is dropped).
let msg = timeout
.await??;
assert_eq!;
assert!;
// from_party_ids contains the sender GLN; to_party_ids the receiver GLN
AS4 โ Generate EC or RSA test keypairs
use ;
// BrainpoolP256r1 โ required by BDEW AS4-Profil ยง2.2.6.2.1
let = generate_self_signed_ec_keypair;
// P-256 โ standard for PEPPOL Access Points
let = generate_self_signed_ec_keypair;
// RSA-2048 โ classic PEPPOL style
let = generate_self_signed_rsa_keypair;
AS4 โ Custom verifier (testing feature)
use ;
use ;
AS2 โ Send a signed message
use ;
use SessionContext;
use EventBus;
let policy = As2SendPolicy ;
let creds = As2SendCredentials ;
let session = new?;
let bus = new?;
let output = send_async.await?;
Strict production startup
use ;
use ;
use Arc;
let bus = strict_production_event_bus?;
let _token = issue_strict_runtime_bootstrap_token_with_as4_topology?;
// _token must be passed to session_with_strict_runtime_bootstrap_token()
// before any protocol entry point is called
SBDH โ PEPPOL / CEF eDelivery
use ;
let wrapped = StandardBusinessDocument .wrap?;
๐๏ธ Feature Flags
| Flag | Enables | Default |
|---|---|---|
as2 |
AS2 send/receive (as2::send_sync/async, as2::receive_sync/async) |
โ |
as4 |
AS4 send/receive, pull store, WS-Security, ECDH-ES + ECDSA support | โ |
client |
HTTP egress via reqwest |
โ |
server |
Axum router integration (as2_router, as4_router) |
โ |
compression |
Zlib/GZIP payload compression (RFC 5402) | โ |
async-ocsp |
Async OCSP responder fetching | โ |
interop-strict |
Strict interop mode as compile-time default | โ |
interop-relaxed |
Relaxed mode helpers for legacy partners | โ |
trace |
tracing instrumentation on hot paths |
โ |
prometheus |
Built-in Prometheus/OpenMetrics MetricsSink |
โ |
opentelemetry |
OpenTelemetry metrics MetricsSink adapter |
โ |
testing |
InsecureBypassAs4Verifier, DurableInMemoryDedupBackend, keypair generators, verifier_seal, interop matrix executor, EventBus::new_for_testing() |
โ |
testing + server |
Also: MockAs4Endpoint (with builder().with_decryption_key_pem()) |
โ |
Security: The
testingfeature triggers acompile_error!in release profile builds. It must never appear in production binaries.
๐๏ธ Architecture
asx-rs
โโโ as2/ AS2 send, receive, MDN handling
โโโ as4/ AS4 push/pull, P-Mode registry, pull store, conversation gate
โ โโโ pmode.rs P-Mode registry + resolution
โ โโโ parser.rs ebMS3 UserMessage XML parser
โ โโโ pull_store/ Bounded in-memory pull queue (per-MPC)
โ โโโ mock_endpoint/ In-process test endpoint (testing + server)
โโโ crypto/
โ โโโ as2_smime CMS/S/MIME signing + verification
โ โโโ wssec/ WS-Security (XMLDSig, XMLenc11, OCSP, Exc-C14N)
โ โ โโโ xmlenc AES-GCM + (RSA-OAEP | ECDH-ES + ConcatKDF + AES-KW)
โ โโโ soap_builder SOAP envelope construction
โโโ transport/
โ โโโ ingress HTTP request normalisation + validation
โ โโโ egress HTTP send with endpoint governance
โ โโโ server Axum router builders (server feature)
โโโ lifecycle Type-state trust transition machine
โโโ reliability Retry classification, dedup, reconciliation
โโโ storage/ DedupStorage + ReconciliationStorage traits + in-memory impls
โโโ observability/ EventBus, audit sink, back-pressure policy
โโโ interop Profile stacks, regional packs, exception policies
โโโ sbdh UN/CEFACT SBDH 1.3 wrap/unwrap
โโโ wire Bounded stream reading, MIME utilities
โโโ core Error types, SessionContext, shared utilities
๐ Trust lifecycle
UntrustedBytes
โ structural parse (MIME / SOAP envelope)
โผ
StructurallyParsed
โ cryptographic verify (S/MIME or XMLDSig)
โผ
CryptographicallyVerified
โ decrypt (S/MIME EnvelopedData or XMLenc11)
โผ
ContentDecrypted
โ dedup check + domain validation
โผ
DomainReady โ your application code starts here
๐ XML Encryption: automatic key transport selection
encrypt_payload_xmlenc inspects the recipient certificate's key type at call time:
| Recipient cert key | Key transport | Key reference | Profiles |
|---|---|---|---|
| RSA | RSA-OAEP (SHA-256 / MGF1-SHA-256) | BinarySecurityToken |
PEPPOL, CEF eDelivery |
| EC (any named curve) | ECDH-ES ephemeral + ConcatKDF (NIST SP 800-56A) + AES-128-KW (RFC 3394) | X509SKI |
BDEW AS4-Profil ยง2.2.6.2.2, BSI TR-03116-3 ยง9.2 |
No configuration knob is needed โ pass the recipient's certificate and the right algorithm follows from the key type.
๐ Security Notes
InsecureBypassAs4Verifier/InsecureBypassTrustVerifierbypass all cryptographic checks. They are strictly limited to thetestingfeature, which is blocked in release builds viacompile_error!.- PKIX chain validation is fail-closed: an empty
trust_anchor_pemsstore withrequire_chain_validation = truerejects every certificate. - Minimum signing-key strength is enforced: RSA moduli below 2048 bits are rejected.
- OCSP is disabled by default (
OcspMode::Disabled). SetOcspMode::ResponderOnlyorOcspMode::StapledAndResponderin production. - SSRF-hardened egress: URL scheme and private/loopback/link-local/CGNAT/IPv4-mapped-IPv6 ranges are blocked, DNS resolution is pinned against rebinding, and HTTP redirects are never followed (a redirect cannot escape the validated target). Applies to AS2/AS4 egress, OCSP, and SMP clients.
- AS4 message integrity: the WS-Security signature covers the whole
eb:Messagingmetadata block; the receive path binds the consumed block to the verified signature (XML-Signature-Wrapping defence).
๐ Status
asx-rs is beta quality. Core AS2 and AS4 push/pull flows are implemented, tested
(950+ tests across unit + integration suites), and exercised in production integrations.
- โ AS2 send/receive (signed, encrypted, compressed, sync + async)
- โ AS4 push send/receive (signed, encrypted, dedup, fragment reassembly)
- โ AS4 pull (with reliability classification)
- โ WS-Security (RSA-SHA256, ECDSA-SHA256, ECDH-ES + ConcatKDF + AES-KW, XMLenc11)
- โ AS4 Reception Awareness / Non-Repudiation of Receipt, verified end to end
- โ OCSP + PKIX chain validation
- โ Strict-mode production validation gate
- โ Comprehensive testing infrastructure (bypass verifier, mock endpoint, keypair generators)
- โ ๏ธ Persistent storage backends (dedup/reconciliation) are trait-defined but not shipped in-tree; deployers provide their own Redis/PostgreSQL/DynamoDB implementations.
Development
Common tasks are wrapped in a justfile (just):
Recipes mirror .github/workflows/rust.yml and docs/release.md,
so a local run and CI enforce the same thing.
Versioning
asx-rs is pre-1.0, so minor bumps (0.x โ 0.x+1) are the breaking ones.
Wire/crypto behaviour changes and security-default tightenings always get their
own minor version and are called out separately from API changes โ see the
CHANGELOG and its SemVer policy
before moving between minor versions.
๐ License
Licensed under either of:
at your option.