Partnerships
A partnership is one directed relationship between an AS2 identity of
yours (local_id) and one of a partner’s (remote_id). All of them live
in partnerships.toml; one server can host many local identities.
Anatomy
[[partnership]]
id = "acme-outbound" # unique config key (appears in logs/metrics)
local_id = "MYCOMPANY" # our AS2-From when sending / AS2-To inbound
remote_id = "ACME" # the partner's AS2 name
remote_url = "https://as2.acme.example/as2" # present = outbound-capable
cert_aliases = { local = "me", remote = "acme" }
[partnership.security]
sign = "sha-256" # omit for unsigned
encrypt = "aes256-cbc" # omit for unencrypted
compress = "before-signing" # or "after-signing"; omit for none
content_transfer_encoding = "binary" # "binary" (default) | "base64" | "quoted-printable"
[partnership.mdn]
request = true # ask for a receipt when we send
signed_micalg = "sha-256" # ask for a SIGNED receipt with this digest
# async_url = "https://my.example/as2/mdn" # asynchronous receipt delivery
# [partnership.http_auth] # HTTP Basic for the partner's endpoint
# username = "u"
# password = "p"
# [partnership.interop] # per-partner compatibility switches, see below
How inbound routing works
An arriving message’s AS2-From/AS2-To headers select the partnership
(quoted AS2 names are handled per RFC 4130 §6.2). Unknown pairs are
rejected with an authentication-failed disposition — there is no
“accept anyone” mode.
The security policy is a floor, not a hint
Declaring sign and/or encrypt does two things:
- Outbound: those layers are applied when sending.
- Inbound: messages from this partner must carry those layers or
they are rejected with
insufficient-message-security. A partner (or an attacker) cannot downgrade the exchange to plaintext.
Migration escape hatches (interop.disable_decrypt,
interop.disable_verify, interop.disable_decompress) opt out per
layer, explicitly, per partner.
Onboarding gotcha — mirror the security attributes. In the BouncyCastle-derived AS2 world (and in as2d, for compatibility with it), whether MIME headers are included in the receipt digest is driven by each side’s own partnership attributes, not by the message. If you declare compression and the partner’s side does not, both implementations can be correct and the receipt MICs will still disagree. When receipts mismatch, compare partnership attributes with your partner first.
Interop switches
All default to sensible values; set them per partner when a counterpart needs it (see Working with specific products):
| Flag | Default | Purpose |
|---|---|---|
include_cert_in_signature | true | embed our certificate in signatures (Peppol requires it) |
rfc3851_micalgs | false | use sha256-style spellings instead of sha-256 |
quote_header_values | false | quote outbound header values (some IBM Sterling setups) |
block_error_mdn | false | never send negative receipts to this partner |
bc_style_gcm | false | send AES-GCM the BouncyCastle way (Java AS2 partners) |
force_decrypt / force_verify | false | attempt the layer even when the content-type does not announce it |
disable_decrypt / disable_verify / disable_decompress | false | skip a layer (weakens the floor; migration only) |
verify_use_cert_in_body_part | false | trust the certificate embedded in the message instead of the pinned one (weaker; for partners that rotate certificates in-band) |
The full field list with types is in the partnerships.toml reference.