BDEW AS4 Guide

How to configure makod for BDEW AS4-Profil v1.2 production deployments. Covers the certificate triplet (TLS / signing / encryption), P-Mode registry, BDEW Verzeichnisdienst endpoint discovery, and testing without WIRK certificates.

BDEW AS4 Guide

AS4 became the mandatory transport for BDEW MaKo since 1 April 2024 (Strom, BK6-22-024) and 1 April 2025 (Gas, BK7-22-023). This guide covers everything needed to operate makod in a BDEW production environment.


Signature scope

BDEW AS4-Profil §2.2.6.2.1 requires PMode[1].Security.X509.Sign to be set "nach Maßgabe der Abschnitte 5.1.4 und 5.1.5 von [AS4]", and those sections place the eb:Messaging SOAP header block inside the signature.

makod signs the whole eb:Messaging block, referenced by wsu:Id="as4-messaging". That scope is what makes the signature meaningful: PartyInfo, CollaborationInfo and Action are the fields the pipeline routes and authorizes on, so a signature covering only eb:MessageId would leave every routing decision unauthenticated.

On receive, the eb:Messaging block the parser consumes is bound to the block the signature verified. Without that binding an attacker can relocate the signed element so it still resolves by wsu:Id, then feed the parser an unsigned injected replacement — XML Signature Wrapping.

Interop

Verification is strict in one direction. A receiver that requires the full block rejects a sender that signs less than it; a receiver that checks only that some signature verified accepts a conformant sender either way.

Conformant partner stacks sign the block, so strict verification rejects only non-conformant senders.

Payload encryption

ConcatKDF uses the SP 800-56A raw-concatenation form — no keydatalen, no JOSE-style length prefixes — which is what BSI TR-03116-3 §9.2 requires. The derivation determines the KEK, so a peer deriving it differently cannot decrypt the payload.

Further hardening on this profile

  • The wsse:BinarySecurityToken PKIPath parse is bounds-checked. PKIPath is the token type BDEW mandates (§2.2.6.2.1), so this path is reached on every inbound BDEW AS4 message, before authentication.
  • RSA signing keys below 2048 bits are rejected. BDEW mandates ECDSA on BrainpoolP256r1, so a conformant deployment never presents one.
  • CertHandle's Debug redacts signing_key_pem.

BDEW AS4-Profil v1.2 vs PEPPOL/CEF

BDEW AS4 is based on the CEF eDelivery profile but extends it with mandatory BSI TR-03116-3 cryptographic algorithms. The differences are significant enough to cause interoperability failures if you treat them as equivalent.

AspectPEPPOL / CEF eDeliveryBDEW AS4-Profil v1.2 (01.04.2026)
Signing algorithmRSA-SHA256ECDSA-SHA256 + BrainpoolP256r1
EncryptionOptionalMandatory
Key agreementRSA-OAEPECDH-ES + ConcatKDF
Payload cipherAES-256-GCMAES-128-GCM
EC curveP-256 (NIST)BrainpoolP256r1 (RFC 5639)
PKIPEPPOL PKISM-PKI (BSI) / BDEW Marktpartner PKI
Service URIPEPPOL identifierurn:bdew:as4:service
Action URIPEPPOL BIS typeurn:bdew:as4:service:<EDIFACT_TYPE>
ReceiptOptional synchronousMandatory synchronous (§4.6.3)
Dedup windowNot prescribed72 hours (§4.2)
Certificate triplet2 (TLS + signing)3 (TLS + signing + encryption)

The signing algorithm is auto-detected from the key type: EC keys use ECDSA-SHA256, RSA keys use RSA-SHA256. Encryption uses ECDH-ES + ConcatKDF + AES-128-GCM when the recipient certificate carries an EC public key. Supply BrainpoolP256r1 material and the correct paths are selected without any explicit configuration.


Certificate triplet

BDEW requires three separate X.509 keypairs per market participant, all using BrainpoolP256r1. These are called the WIRK certificates:

┌─────────────────────────────────────────────────────────────────────┐
│  Certificate triplet (SM-PKI / BDEW Marktpartner PKI)               │
│                                                                     │
│  ┌──────────────────┐   ┌───────────────────┐   ┌────────────────┐  │
│  │   TLS cert       │   │  Signing cert      │   │ Encryption cert│  │
│  │  (mTLS auth)     │   │  (WS-Security sig) │   │ (ECDH-ES KEX)  │  │
│  │  KeyUsage:       │   │  KeyUsage:         │   │ KeyUsage:      │  │
│  │  digitalSig +    │   │  digitalSignature  │   │ keyAgreement   │  │
│  │  keyEncipherment │   │                    │   │                │  │
│  └──────────────────┘   └───────────────────┘   └────────────────┘  │
│                                                                     │
│  All three: EC BrainpoolP256r1 · BSI TR-03116-3 §9.1/§9.2          │
└─────────────────────────────────────────────────────────────────────┘

Obtaining WIRK certificates

  1. Register as a Marktpartner at bdew-codes.de
  2. Apply for a WIRK certificate at the BDEW Marktpartner portal
  3. The CA signs three certificates (TLS, signing, encryption) for your GLN
  4. Download as PKCS#12 bundles and extract PEM with:
# Extract signing cert + key
openssl pkcs12 -in signing.p12 -nocerts -nodes -out as4-signing.key.pem
openssl pkcs12 -in signing.p12 -nokeys        -out as4-signing.cert.pem

# Extract encryption cert + key
openssl pkcs12 -in encryption.p12 -nocerts -nodes -out as4-encrypt.key.pem
openssl pkcs12 -in encryption.p12 -nokeys        -out as4-encrypt.cert.pem

# Extract BDEW CA trust anchor
openssl pkcs12 -in signing.p12 -cacerts -nokeys  -out bdew-ca.cert.pem

makod configuration

Each [[party]] entry registers one BDEW market-participant identity (mp_id). A single operator (tenant) typically holds multiple mp_ids — one per Marktrolle/Sparte combination:

  • Strom roles (LF, NB, MSB, BKV) use a BDEW-Codenummer (99…, NAD DE3055 293)
  • Gas roles (LFG, GNB, GMSB) use a DVGW-Codenummer (98…, NAD DE3055 332)

A combined Strom+Gas operator therefore needs at least two [[party]] entries:

# makod.toml — combined Strom + Gas operator (LG)

# Strom identity: BDEW-Codenummer (starts with 99)
[[party]]
mp_id   = "9900357000004"
primary = true              # becomes the AS4 receipt sender-ID and storage partition key
roles   = ["LF", "MSB"]

# Gas identity: DVGW-Codenummer (starts with 98)
[[party]]
mp_id   = "9800357000005"
roles   = ["LFG", "GMSB"]

One AS4 endpoint for all mp_ids

One URL serves all mp_ids — but each mp_id still needs its own certificate triplet.

Per BDEW Allgemeine Festlegungen §2.14 and SM-PKI rules, every mp_id must have a separately issued WIRK certificate triplet (TLS + signing + encryption) registered in the BDEW portal (Strom: bdew-codes.de, Gas: codevergabe.dvgw-sc.de). A company operating as LF Strom (99…) and LF Gas (98…) therefore holds two certificate triplets — one registered against each mp_id.

However, all those identities can share one --as4-addr. A single makod instance accepts inbound AS4 messages addressed to any registered mp_id; the As4PushPolicy does not filter by <eb:To>/<eb:PartyId>. Routing to the correct workflow happens at the EDIFACT ingest layer based on the Prüfidentifikator.

Company X
  MP-ID Strom  9900357000004  (BDEW cert A) ─┐
  MP-ID Gas    9800357000005  (DVGW cert B) ─┤─► https://as4.company.de:4080/as4/inbox

For outbound messages the EDIFACT NAD+MS sender is set automatically per workflow (Strom workflows use the BDEW code, Gas workflows use the DVGW code).

Signing cert and <eb:From> in the current release

makod maintains one outbound SessionContext backed by a single signing key/cert (configured via --as4-signing-key-pem / --as4-signing-cert-pem). This means:

  • All outbound AS4 messages are signed with that one key, regardless of which mp_id is the EDIFACT NAD+MS sender.
  • The AS4 <eb:From>/<eb:PartyId> for receipts and error signals is always the primary mp_id (or the explicit --as4-party-id).

In practice counterparties validate the cert against the BDEW PKI trust anchor (not against the <eb:From> value), so this works for the majority of deployments. For strict per-mp_id cert compliance in a combined Strom+Gas deployment, configure the primary mp_id's cert as the shared signing key and register both WIRK triplets in the BDEW/DVGW portal — using the same physical cert for both is permissible for same-entity mp_ids; separate certs are optional.

For a single-Sparte deployment one [[party]] block and one certificate triplet is sufficient.

CLI / environment variables

FlagEnv varPurpose
--as4-addr :4080MAKOD_AS4_ADDRAS4 inbound listen address
--as4-signing-key-pem <PEM>MAKOD_AS4_SIGNING_KEY_PEMSigning private key (ECDSA BrainpoolP256r1)
--as4-signing-cert-pem <PEM>MAKOD_AS4_SIGNING_CERT_PEMSigning X.509 certificate
--as4-trust-anchor-pem <PEM>MAKOD_AS4_TRUST_ANCHOR_PEMBDEW PKI CA certificate (trust anchor)
--as4-decryption-key-pem <PEM>MAKOD_AS4_DECRYPTION_KEY_PEMOwn EC decryption private key
--as4-party-id <GLN>MAKOD_AS4_PARTY_IDAS4 <eb:PartyId> (defaults to primary [[party]] mp_id)
--as4-partner <GLN=URL>MAKOD_AS4_PARTNERTrading partner endpoint (repeatable)
--as4-partner-cert <GLN=PEM>MAKOD_AS4_PARTNER_CERTPer-partner encryption cert (repeatable)

Example production startup

makod \
  --config /etc/makod/makod.toml \
  --as4-addr :4080 \
  --as4-signing-key-pem  "$(cat /etc/certs/as4-signing.key.pem)" \
  --as4-signing-cert-pem "$(cat /etc/certs/as4-signing.cert.pem)" \
  --as4-trust-anchor-pem "$(cat /etc/certs/bdew-ca.cert.pem)" \
  --as4-decryption-key-pem "$(cat /etc/certs/as4-encrypt.key.pem)" \
  --as4-partner 9900000000001=https://netzbetreiber-a.example/as4/inbox \
  --as4-partner-cert "9900000000001=$(cat /etc/partner-certs/9900000000001-encrypt.pem)" \
  --http-addr :8080

The [[party]] entries in makod.toml register all operator mp_ids. --as4-party-id defaults to the primary entry's mp_id and can be omitted when they match.

Kubernetes secret example

# k8s/makod-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: makod-as4-certs
type: Opaque
stringData:
  AS4_SIGNING_KEY_PEM:    <base64-encoded PEM>
  AS4_SIGNING_CERT_PEM:   <base64-encoded PEM>
  AS4_DECRYPTION_KEY_PEM: <base64-encoded PEM>
  AS4_TRUST_ANCHOR_PEM:   <base64-encoded BDEW CA PEM>

Use _FILE suffix to load from a mounted secret:

MAKOD_AS4_SIGNING_KEY_PEM_FILE=/run/secrets/as4-signing-key  makod ...

P-Mode registry

Every trading partner requires registered P-Modes before AS4 messages can be delivered. makod registers one P-Mode per standard BDEW EDIFACT message type for each partner.

Registering partners with multiple mp_ids

A counterpart operating Strom and Gas has two distinct mp_ids. Register each separately — both can point to the same inbox URL:

# NB Strom (BDEW-Codenummer, 99…)
--as4-partner 9900000000001=https://nb-a.example/as4/inbox \
--as4-partner-cert "9900000000001=$(cat /etc/partner-certs/9900000000001-enc.pem)"

# Same NB, Gas role (DVGW-Codenummer, 98…) — same URL, different mp_id
--as4-partner 9800000000002=https://nb-a.example/as4/inbox \
--as4-partner-cert "9800000000002=$(cat /etc/partner-certs/9800000000002-enc.pem)"

The P-Mode registry does an exact mp_id match on every outbound message. A missing Gas mp_id registration causes PartnerUnknown dead-letters on Gas workflows.

P-Modes are registered at startup. To add a new trading partner, restart makod with the new --as4-partner / --as4-partner-cert flags.


BDEW Verzeichnisdienst — API-Webdienste endpoint discovery

The BDEW Verzeichnisdienst ("Regelungen zum Verzeichnisdienst" v1.0, applicable 03.04.2025) is a registry for API-Webdienste Strom endpoints (SM-PKI certificate class EMT.API). It is not used for AS4 EDIFACT delivery — AS4 inbox endpoints are carried in EMT.MAK certificates and exchanged via PARTIN Kommunikationsdaten (PIDs 37000–37014).

makod uses the Verzeichnisdienst exclusively to discover MaLo-Identifikation callback URLs (API-Kennung maloIdV1, stored as CommunicationChannel { qualifier: "AW" } in the partner store). Configure it so that the LF's MaLo-ID response endpoint is resolved automatically instead of hard-coded:

--verzeichnisdienst-url https://<verzeichnisdienst-host>/api/directory/v1

At startup and every 5 minutes, makod queries GET <base-url>/record/<lf_mp_id>/maloIdV1/1/v1 for each known LF. Static --maloid-partner GLN=URL entries always take priority.

AS4 partner endpoints (PARTIN COM qualifier AK) are populated by inbound PARTIN messages (PIDs 37000–37014) or seeded at startup via --as4-partner. The Verzeichnisdienst is not involved in AS4 endpoint resolution.


Message flow

sequenceDiagram
    autonumber
    participant LF as LF counterparty
    participant TLS as mTLS (port 4080)
    participant makod as makod AS4 ingest
    participant sig as WS-Security verify
    participant dec as XML decrypt
    participant edifact as edi-energy

    LF->>TLS: HTTPS POST (mutual TLS — TLS cert)
    TLS->>makod: As4HttpIngress (body bytes)
    makod->>sig: verify ECDSA-SHA256 signature
    Note over sig: trust anchor = BDEW PKI CA cert
    sig->>dec: decrypt AES-128-GCM payload
    Note over dec: ECDH-ES key agreement<br/>using own EC decryption key
    dec->>edifact: raw EDIFACT bytes
    edifact->>makod: edi-energy parse + validate
    makod-->>LF: synchronous eb:Receipt (signed)

Testing without WIRK certificates

WIRK certificate registration takes days to weeks. Use mako-as4's built-in test helpers to run integration tests locally immediately:

Rust unit/integration tests

[dev-dependencies]
mako-as4 = { path = "../mako-as4", features = ["testing"] }
asx-rs   = { version = "0.11", features = ["as4", "testing"] }

No manual CertHandle construction or direct zeroize dependency is needed:

use mako_as4::testing::{BdewTestPki, MockAs4Endpoint};
use asx_rs::core::SessionContextBuilder;
use asx_rs::observability::EventBus;
use asx_rs::transport::As4HttpTransport;

#[tokio::test]
async fn test_as4_full_round_trip() {
    let sender_pki = BdewTestPki::generate("Test NB 9900357000004");
    let receiver_pki = BdewTestPki::generate("Test LF 9900357000005");

    // The mock must hold the decryption key to accept ECDH-ES messages;
    // without it, an encrypted message returns HTTP 400.
    let mock = MockAs4Endpoint::builder()
        .with_decryption_key_pem(receiver_pki.encryption.key_pem.clone())
        .bind("127.0.0.1:0")
        .await
        .unwrap();

    // `with_signing_material` sets cert and key together and derives `key_id`
    // from the partner id, so the two cannot be set inconsistently.
    let session = std::sync::Arc::new(
        SessionContextBuilder::new("sess-test", "9900357000004")
            .with_signing_material(
                sender_pki.signing.cert_pem_str(),
                sender_pki.signing.key_pem_str(),
            )
            .with_trust_anchor_pem(sender_pki.signing.cert_pem_str())
            .build()
            .unwrap(),
    );

    // BestEffort mode — no audit sink required for a test.
    let event_bus = std::sync::Arc::new(EventBus::new_for_testing());

    // Partial credentials: only `recipient_cert_pem` is required, since signing
    // falls back to the session's cert handle.

    // Localhost transport: `send_to_localhost` bypasses the SSRF URL guard that
    // `send` applies. Test-only — the guard exists to stop a partner-supplied
    // endpoint pointing at internal infrastructure.
    let transport = As4HttpTransport::new_for_localhost_testing().unwrap();
    // transport.send_to_localhost(&mock.local_url(), &output).await.unwrap();

    // `next_received` delivers the decrypted plaintext payload.
    // let received = mock.next_received().await.unwrap();
    // assert!(received.payload.starts_with(b"UNB"));
}

For a complete, runnable example with SOAP envelope verification, see services/makod/tests/as4_security.rs — covers the full BDEW AS4 security envelope including tampered-signature rejection and require_encrypted_inbound enforcement.

Generating test certificates manually with OpenSSL

# Generate BrainpoolP256r1 EC signing key and self-signed cert
openssl ecparam -name brainpoolP256r1 -genkey -noout -out test-signing.key.pem
openssl req -new -x509 -key test-signing.key.pem \
  -out test-signing.cert.pem -days 365 \
  -subj "/CN=Test NB 9900357000004"

# Generate encryption keypair
openssl ecparam -name brainpoolP256r1 -genkey -noout -out test-encrypt.key.pem
openssl req -new -x509 -key test-encrypt.key.pem \
  -out test-encrypt.cert.pem -days 365 \
  -subj "/CN=Test NB 9900357000004 (enc)"

# Verify curve
openssl ec -in test-signing.key.pem -text -noout | grep "NIST CURVE\|ASN1 OID"

Supported EDIFACT message types (BdewAction)

mako_as4::BdewAction covers all 16 standard BDEW EDIFACT message types. Each variant maps to the AS4 action URI urn:bdew:as4:service:<TYPE>.

VariantEDIFACT typeUsed in
UtilmdUTILMDGPKE, WiM, GeLi Gas, WiM Gas
AperakAPERAKAll processes
ContrlCONTRLAll processes
MsconsMSCONSMeter readings / time series
InvoicINVOICGPKE NNE, WiM MSB, GeLi Gas AWH, MABIS
RemadvREMADVGPKE, MABIS billing
IftstaIFTSTAGPKE Sperrung / Entsperrung
OrdrspORDRSPGPKE Konfiguration, WiM Geräteübernahme
OrdersORDERSWiM Geräteübernahme, GPKE Sperrung
OrdchgORDCHGWiM Stornierung, GeLi Gas AWH Stornierung
ReqoteREQOTEWiM Preisanfrage MSB
InsrptINSRPTWiM Ablesesteuerung / Gerätebefund
PricatPRICATWiM Preisliste MSB
QuotesQUOTESWiM Preisanfrage MSB response
PartinPARTINGPKE Kommunikationsdaten (PIDs 37000–37006), GeLi Gas (PIDs 37008–37014)
UtiltsUTILTSBerechnungsformel, Zählzeit- und Schaltzeitdefinitionen

Non-standard types use BdewAction::custom("TYPENAME") which composes the full URI.

Idiomatic Rust conversions

BdewAction implements Display (EDIFACT type name) and FromStr (parses type name, unknown types map to Custom — never fails):

use mako_as4::pmode::BdewAction;

// Display shows the EDIFACT type name
assert_eq!(BdewAction::Partin.to_string(), "PARTIN");

// Parse from a string (used in outbox delivery loop)
let action: BdewAction = "UTILTS".parse().unwrap();
assert_eq!(action, BdewAction::Utilts);

// Unknown types → Custom (delivery is attempted, not rejected)
let action: BdewAction = "SLSFCT".parse().unwrap();
assert!(matches!(action, BdewAction::Custom(_)));

// Access the EDIFACT type name without heap allocation
let name: &str = BdewAction::Partin.as_edifact_type(); // "PARTIN"

// Access the full AS4 action URI
let uri: String = BdewAction::Partin.as_uri(); // "urn:bdew:as4:service:PARTIN"

one as sender, one as receiver — using self-signed test certificates:

# nb.toml
[[party]]
mp_id   = "9900357000004"
primary = true
roles   = ["NB"]

[storage]
allow_volatile = true
# lf.toml
[[party]]
mp_id   = "4012345000023"
primary = true
roles   = ["LF"]

[storage]
allow_volatile = true
# Terminal 1 — receiver (NB)
makod \
  --config nb.toml \
  --as4-addr :4080 \
  --as4-signing-key-pem "$(cat test-signing.key.pem)" \
  --as4-signing-cert-pem "$(cat test-signing.cert.pem)" \
  --as4-trust-anchor-pem "$(cat test-signing.cert.pem)" \
  --as4-decryption-key-pem "$(cat test-encrypt.key.pem)" \
  --http-addr :8080 \
  --allow-no-as4-signing

# Terminal 2 — sender (LF)
makod \
  --config lf.toml \
  --as4-signing-key-pem "$(cat lf-signing.key.pem)" \
  --as4-signing-cert-pem "$(cat lf-signing.cert.pem)" \
  --as4-trust-anchor-pem "$(cat test-signing.cert.pem)" \
  --as4-partner "9900357000004=http://localhost:4080" \
  --as4-partner-cert "9900357000004=$(cat test-encrypt.cert.pem)" \
  --http-addr :8081

BDEW cryptographic algorithm reference

Signing (§2.2.6.2.1, BSI TR-03116-3 §9.1)

SignatureMethod Algorithm = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"
HashFunction Algorithm   = "http://www.w3.org/2001/04/xmlenc#sha256"
EC Curve                 = BrainpoolP256r1 (RFC 5639)
Token Type               = BinarySecurityToken / X509PKIPathv1
Exclusive C14N           = "http://www.w3.org/2001/10/xml-exc-c14n#"

Encryption (§2.2.6.2.2, BSI TR-03116-3 §9.2)

EncryptedData algorithm   = "http://www.w3.org/2009/xmlenc11#aes128-gcm"
Key wrap (CEK)            = "http://www.w3.org/2001/04/xmlenc#kw-aes128"
Key agreement             = "http://www.w3.org/2009/xmlenc11#ECDH-ES"
Key derivation            = "http://www.w3.org/2009/xmlenc11#ConcatKDF"
ConcatKDF digest          = "http://www.w3.org/2001/04/xmlenc#sha256"
EC Curve                  = BrainpoolP256r1
Key reference in KeyInfo  = X509SKI
AlgorithmID/PartyUInfo/PartyVInfo = empty strings (§2.2.6.2.2)

Troubleshooting

SymptomLikely causeFix
Counterparty rejects signatureWrong key type (RSA instead of EC)Use EC (BrainpoolP256r1) signing key
Counterparty rejects our certCert not registered for sending mp_idRegister the cert in BDEW portal (bdew-codes.de) against the sending mp_id; for Gas use DVGW portal (codevergabe.dvgw-sc.de)
Counterparty cannot decryptMissing or wrong encryption cert--as4-partner-cert GLN=<partner-encrypt.cert.pem> (EC cert, not signing cert)
Own decryption failsDecryption key not configured--as4-decryption-key-pem with EC private key PEM
Trust verification failureSelf-signed cert or wrong CA--as4-trust-anchor-pem must be the BDEW PKI CA (not the signing cert itself)
Inbound messages not arrivingAS4 not configured--as4-addr :4080 + signing key/cert required
"AS4 inbox dedup is volatile" warningNo --data-dir setAdd --data-dir /var/lib/makod for durable dedup (required for BDEW conformance)
Partner endpoint not foundP-Mode not registeredAdd --as4-partner GLN=URL for the recipient GLN

Security test coverage

makod ships 12 automated tests in services/makod/tests/as4_security.rs that verify the full BDEW AS4-Profil v1.2 security envelope without WIRK certificates:

sequenceDiagram
    autonumber
    participant Test
    participant sender as BdewTestPki (sender)
    participant receiver as BdewTestPki (receiver)
    participant mock as MockAs4Endpoint<br/>(with_decryption_key_pem)
    participant verifier as As4WsSecVerifier<br/>(real PKI check)

    Test->>sender: generate("Test NB …")
    Test->>receiver: generate("Test LF …")
    Test->>mock: builder().with_decryption_key_pem(receiver.encryption.key_pem)
    Test->>sender: send_async(sign + encrypt, recipient_cert = receiver.encrypt)
    sender-->>mock: ECDH-ES encrypted SOAP over HTTP
    mock-->>Test: next_received() — decrypted plaintext ✓
    Test->>verifier: receive_push(tampered_body)
    verifier-->>Test: Err(SignatureVerificationFailure) ✓
TestVerifiesBDEW spec
sign_encrypt_pmode_defaultsbdew_pmode() forces encrypt=true by default§2.2.6.2.2
sign_only_pmode_disables_encryptionbdew_pmode_sign_only() is dev-only§2.2.6.2.2
policy_with_key_requires_encryptionbdew_push_policy(key) enforces require_encrypted_inbound§2.2.6.2.2
policy_without_key_no_encryption_requiredDev-mode without key does not block onboarding§2.2.6.2.2
fragment_scope_is_strict_defaultStrict RequireAuthenticatedScope default is kept — BDEW OneWayPush never fragments§2.2.5
sign_encrypt_policy_is_bdew_compliantSOAP policy constants satisfy §2.2.6.2.1 + §2.2.6.2.2§2.2.6
replay_dedup_blocks_duplicate_message_id72-hour dedup window prevents replays§4.2
tampered_signature_is_rejectedReal As4WsSecVerifier rejects payload tampering§2.2.6.2.1
inbound_encryption_enforced_when_decryption_key_setUnencrypted inbound is rejected when key is set§2.2.6.2.2
sign_encrypt_round_trip_via_mock_endpointFull sign+encrypt→transport→decrypt pipeline§2.2.6
sign_only_round_trip_envelope_contains_wssec_signatureSign-only maintains WS-Security elements§2.2.6.2.1
sync_receipt_is_verified_and_correlatedSynchronous eb:Receipt is parsed namespace-correctly and correlated to the sent message_id§4.6.3

Run with: cargo test -p makod --test as4_security


Further reading

ResourceDescription
BDEW AS4-Profil v1.2Official AS4 specification (01.04.2026)
BSI TR-03116-3Cryptographic requirements for Telematikinfrastruktur
RFC 5639BrainpoolP256r1 curve definition
RFC 6090Fundamental EC cryptography (referenced by BDEW §2.2.6.2.1/2)
makod operator guideFull makod configuration reference
ERP integration guideCloudEvents, Command API, HMAC

Edit this page ↗