Documentation
Guides, how-tos, and integration examples for the Asynk specification and CLI.
Overview
What is Asynk
Asynk is an implementation of MLS RFC 9420 — the IETF Messaging Layer Security standard — that adds abstract interfaces for how the shared group state is kept and how the secret material is protected. The protocol itself is never modified: Asynk layers pluggable responsibilities on top of it, so the cryptographic guarantees come straight from MLS while the operational strategy can be tailored to the infrastructure.
At the base lies MLS RFC 9420, providing group end-to-end encryption, forward secrecy and post-compromise security. The streamMLS bulk extension (patent-pending) sits above the protocol, enabling bounded atomic encryption and decryption of massive files. The Runtime bundles a StateStore and a ProtectionProvider — pluggable implementations for where the group state lives (filesystem passphrase, relational databases such as Postgres or Oracle, HSM or HashiCorp Vault) and how secrets are guarded. On top of the stack sits the fundamental API surface and the CLI, used to create the group offline.
Once the group state is bootstrapped offline, it can be migrated for online use onto a database so that services can derive the symmetric keys they need and communicate online — inheriting MLS forward secrecy and post-compromise security without bespoke key-exchange logic. For the full protocol specification, see RFC 9420.
Concept
Runtime
Asynk decouples the MLS protocol from its storage and protection strategy through two pluggable interfaces: a StateStore and a ProtectionProvider. The runtime bundles a concrete pair of implementations and is selected when a workspace is scaffolded with asynk init --runtime=…. The runtime’s fingerprint is pinned into every later request, so state transitions stay auditable and reproducible.
The StateStore owns the shared group state — the cryptographic history and topology committed by MLS — while the ProtectionProvider owns the secret material (private keys, epoch secrets) and decides how it is encrypted and unlocked. Swapping the runtime never changes the protocol; it only changes the backing surface for those two responsibilities.
filesystem-passphraseState on the local filesystem, secrets guarded by a passphrase file. Simplest to run, fine for demos and single-host setups.
vault-kmsState in a protected store, secrets held by a HashiCorp Vault-backed KMS. Suited to shared, audited infrastructure.
hsmState in a tamper-resistant store, secrets inside an HSM. For regulated environments demanding hardware key custody.
Full reference for every runtime, the StateStore and ProtectionProvider interfaces, and migration between runtimes will be published as part of the complete documentation.
Reference
Scenarios
Every scenario below reuses the same two abstractions — a StateStore and a ProtectionProvider — and the same MLS group lifecycle. What changes is who the members are: applications, gateways, or workloads inside a cluster.
Scenario 01
App-to-app across organizations
The group is created offline. Each organization contributes a KeyPackage, the creator commits the membership change and hands out one Welcome per joining member. Welcomes and KeyPackages carry no private key material, so they can travel over any channel — including email or removable media.
From that point on each organization’s applications load the shared MLS state from their own runtime and communicate online, with key rotation guaranteed by MLS RFC 9420: forward secrecy, post-compromise security, and no static shared secret sitting at rest.
Control plane (offline, Asynk): organizations agree on group state and exchange Welcome bundles across boundaries. Data plane (online, MLS RFC 9420): each org’s applications load the shared state and exchange end-to-end encrypted messages with continuous key rotation.
The CLI flow for this scenario — from workspace scaffolding to the first bulk exchange — is walked through step by step in the three-organization workshop.
$ asynk init workspaces/payment-processor \ --member=payment-processor \ --certificate=workspaces/payment-processor/payment-processor.crt \ --private-key=workspaces/payment-processor/payment-processor.key \ --trust-anchor=workspaces/payment-processor/root-ca.crt \ --runtime=filesystem-passphrase \ --passphrase-file=workspaces/payment-processor/secrets/payment-processor.passphrase \ --cipher-suite="$SUITE" \ --no-initialize-group$ asynk group initialize --input=requests/groups/payment-channel/initialize.yaml$ asynk proposal create add --input=requests/groups/payment-channel/add-acquirer.yaml$ asynk commit create --input=requests/groups/payment-channel/commit.yaml$ asynk welcome process --input=requests/groups/payment-channel/welcome-acquirer.yamlScenario 02
Crypto gateways at the trusted boundary
Workloads that cannot embed MLS natively — trading platforms, settlement engines, custody systems — delegate to a local Asynk gateway deployed inside each organization. Replicas reach their own gateway over mTLS in-VPC; the gateways are the MLS members and carry end-to-end encrypted traffic across the public transport.
Adding a microservice becomes a local operation, not a new MLS onboarding: the group membership stays stable while the internal topology evolves.
Each organization runs one gateway holding the protected group state; internal replicas never see MLS material, only the mTLS channel to their gateway.
Scenario 03
Zero-trust internal mesh on Kubernetes
Dozens of microservices are treated as a single MLS group. Each pod is a leaf of the group: the pods of a given service share the same workload identity — the same X.509 credential issued to that service — but every pod joins with its own distinct KeyPackage, so it owns its private leaf material and never shares it with a sibling replica.
That distinction matters operationally. Scaling a deployment up means adding leaves under an identity the group already trusts; terminating a pod means removing exactly that leaf with a Commit, which starts a new epoch and rotates the key schedule. A compromised replica can therefore be evicted without re-onboarding the service and without touching the other pods.
Joining is an offline DevOps operation: the CI/CD pipeline issues the KeyPackage and receives a signed Welcome, and the next deployment rolls out the updated group state. At runtime each pod derives short-lived keys from the current epoch to wrap REST payloads as JWE (RFC 7516) — so the network, the service mesh and any intermediary only ever see ciphertext.
All pods share one MLS epoch and key schedule. Pods of the same service present the same workload identity, yet each one is a separate leaf with its own KeyPackage — so replicas can be added or evicted individually with a single Commit.