Building an RCS E2EE Test Harness with Containerized Tooling
Hands-on guide to build a reproducible, containerized RCS E2EE test harness that emulates Android/iOS endpoints and MLS key exchanges for CI.
Build a local, containerized RCS E2EE test harness for reproducible key-exchange and endpoint flows
If you’re responsible for QA, integration testing, or security validation of RCS (Rich Communication Services) E2EE flows, you know the pain: vendor devices, carrier toggles, and opaque server logic make it almost impossible to reproduce a failure locally. This tutorial gives you a practical path forward in 2026: a containerized test harness that emulates Android/iOS endpoints, performs MLS-style key exchanges, and automates integration tests in CI so you can validate E2EE properties repeatedly and deterministically.
Why a containerized harness matters in 2026
In late 2025 and early 2026 the messaging ecosystem accelerated toward MLS-based RCS E2EE compliance (GSMA Universal Profile 3.x momentum, vendor betas like iOS 26.3), but interoperability testing remains fragmented. A local harness gives engineering teams a reliable way to exercise the entire control and media plane without waiting for carrier toggles or physical devices. Containers provide reproducibility, CI compatibility, and the ability to emulate constrained elements such as secure keystores and IMS signalling.
What you’ll build — architecture & goals
This guide walks you through a minimal, practical harness that demonstrates the key outcomes teams need:
- Emulated endpoints: Two containers representing Android and iOS clients that run lightweight RCS client logic and MLS key management.
- Signalling relay: A container that simulates the carrier/RCS server — it routes signalling but cannot decrypt message payloads.
- Key bootstrap & registry: A simple service that stores identity keys and simulates carrier public-key distribution for tests.
- Automated test runner: A test harness that asserts E2EE properties (server sees ciphertext only, endpoints decrypt reliably, replay/mitm checks).
- CI integration: GitHub Actions (or your CI) example to run full integration tests on every push.
High-level flow
- Each endpoint container generates an identity keypair and registers a KeyPackage with the bootstrap registry.
- When endpoints start a conversation, they perform a simplified MLS-style group/session setup: they fetch each other’s KeyPackages, build an MLS group, and derive symmetric session keys.
- Messages are encrypted locally, sent to the signalling relay, and routed to the peer. The relay only sees ciphertext + metadata necessary for routing.
- Tests validate that the relay cannot decrypt payloads and that endpoints can verify message authenticity and sequence.
Core components and why we containerize them
1) Endpoint container
The endpoint runs a small client program that implements:
- Identity key generation (X25519/ED25519 or MLS-supported suites)
- KeyPackage packaging and registration
- MLS group/session establishment using a library (we show an example using OpenMLS from a Rust binary)
- Message encryption/decryption and transport over HTTP or WebSocket to the relay
2) Signalling relay container
The relay emulates carrier server behaviour for the control plane: user lookup, message routing, and state simulation. It should intentionally not implement E2EE decryption.
3) Key bootstrap/registry
A simple HTTP service that accepts KeyPackages and responds to queries. In the real world carriers distribute KeyPackages via secure provisioning; in the harness the registry makes tests reproducible by storing deterministic keys (seeded in CI) or ephemeral keys during local runs.
Practical: folder layout and docker-compose
harness/
├── registry/
│ └── Dockerfile
├── relay/
│ └── Dockerfile
├── endpoint/
│ ├── Dockerfile
│ └── src/ (Rust or Go client)
├── tests/
│ └── integration_tests.sh
└── docker-compose.yml
Sample docker-compose (stripped to essentials):
version: '3.8'
services:
registry:
build: ./registry
ports: ['8081:8081']
relay:
build: ./relay
ports: ['8080:8080']
endpoint-a:
build: ./endpoint
environment:
- NAME=alice
- REGISTRY_URL=http://registry:8081
- RELAY_URL=http://relay:8080
depends_on: ['registry','relay']
endpoint-b:
build: ./endpoint
environment:
- NAME=bob
- REGISTRY_URL=http://registry:8081
- RELAY_URL=http://relay:8080
depends_on: ['registry','relay']
Endpoint implementation — pragmatic choices
Implement the endpoint as a small Rust binary linking to openmls (Rust crate). OpenMLS is actively maintained and suitable for CI builds in 2026. If your team uses Go/Python, you can run a small Rust binary as the canonical MLS engine and call it over stdin/stdout or via a local RPC.
Example: simplified Rust pseudocode (endpoint)
// main.rs (simplified)
fn main() {
// 1) load or generate identity keys
// 2) build KeyPackage and POST to registry
// 3) wait for peer discovery; fetch peer KeyPackage
// 4) create/open MLS group, derive keys
// 5) send encrypted message to relay
// 6) receive message, decrypt and assert content
}
Important implementation notes:
- Use deterministic entropy for CI runs (e.g., read seed from env var) so test vectors are reproducible.
- Persist ephemeral keystore in-memory for local runs; optionally map to host path for debugging.
- Log cryptographic digests, not secrets. For CI, tests should assert the presence/absence of plaintext in relay logs and verify mac/tag values.
Bootstrap registry — API contract
Keep the registry API intentionally small. Example endpoints:
- POST /keypackages {name, keypackage} — register
- GET /keypackages/{name} — retrieve
- GET /health
The registry should return metadata such as supported cipher suites so endpoints can negotiate the same suite in tests.
Relay behaviour and test assertions
Make the relay as dumb as possible about payload content. Its responsibilities:
- Accept POST /messages {from, to, ciphertext, metadata}
- Route to the correct destination container using service discovery
- Log messages to a file for test assertions (but never store plaintext)
Tests should assert these properties:
- Confidentiality: Relay logs never contain message plaintext.
- Integrity: Recipient verifies the message authentication tag and sender identity.
- Replay protection: Sequence counters or PSKs are validated.
Integration test examples
Create an integration script that runs the harness, sends messages, and checks artifacts. Here’s an outline:
#!/usr/bin/env bash
set -e
# 1) bring up compose
docker-compose up -d --build
# 2) wait for services to be healthy
sleep 5
# 3) call endpoint A to initiate chat with B
docker exec endpoint-a /app/client --start-chat bob --message "hello bob"
# 4) wait and fetch logs from relay
docker logs relay > /tmp/relay.log
# 5) assert that relay.log does not contain "hello bob"
if grep -q "hello bob" /tmp/relay.log; then
echo "FAIL: relay logged plaintext"; exit 1
fi
# 6) assert endpoints have matching decrypted payloads
docker exec endpoint-b cat /tmp/received_messages | grep -q "hello bob"
echo "PASS: E2EE validated"
CI integration — GitHub Actions example
Hook the integration script into CI so each PR validates E2EE properties. A minimal workflow:
name: rcs-e2ee-integration
on: [push, pull_request]
jobs:
integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Start harness
run: docker-compose up -d --build
- name: Run tests
run: ./tests/integration_tests.sh
- name: Tear down
if: always()
run: docker-compose down -v
Validation strategies and forensic checks
Beyond the simple checks above, add these validations for stronger guarantees:
- Cryptographic proof: endpoints log the message MAC and session ID; tests verify those values were never present in relay logs.
- Tamper detection: inject a modified ciphertext at the relay and assert the recipient rejects it.
- Backward compatibility: run the harness with multiple cipher suites to validate negotiation and downgrade protections.
- Timing and metadata leakage: measure metadata exposed to the relay (message sizes, timing) and assert acceptable boundaries for your threat model.
Emulating platform keystores and hardware
Real devices use secure elements and platform keystores. For higher-fidelity tests:
- Run a SoftHSM v2 container and configure the endpoint to use PKCS#11 — useful to simulate private-key extraction restrictions.
- For Android-specific behaviour, use the Android Keystore abstraction in a device lab; in local harnesses, emulate via mTLS and PKCS#11 layers.
- For iOS-ish semantics, emulate keychain policies by restricting file permissions and using ephemeral tokens.
Security considerations and best practices
This harness is for testing. Don’t reuse test keys in production. Follow these rules:
- Secrets management: store CI seeds and any persistent keys in your CI secret store (not in source control).
- Deterministic tests: use seeded RNG in CI to reproduce cryptographic states, but isolate those seeds from production.
- Network isolation: run the harness in a VLAN or runner with restricted egress to avoid accidental leakage of keys during tests.
- Audit logs: keep signed test artifacts to prove when and how tests ran for compliance audits.
Common pitfalls and troubleshooting
- Relay appears to decrypt — check if endpoints are accidentally sending plaintext due to a bug in the client wrapper.
- Non-deterministic failures in CI — switch to seeded RNG and snapshot KeyPackages to stabilize tests.
- Library compatibility — MLS has evolving drafts; lock your dependency versions and add a compatibility matrix to the repo.
Real-world teams in late-2025 found the largest friction point was key distribution: carriers and vendors used divergent provisioning pipelines. A local registry collapses that variance into deterministic API calls you control.
Extending the harness: scaling to multi-device and cross-platform tests
Once you have a stable 2-device flow, scale up:
- Spin up N endpoint containers to test group conversations and MLS group state transitions.
- Introduce network jitter, packet loss and reorder tests to validate MLS re-synchronization behaviour.
- Integrate real device clouds (e.g., Firebase Test Lab, private device farm) to run the endpoint client built for Android/iOS and cross-check results with containerized emulations.
2026 trends and why this approach is future-proof
By 2026 the industry moved toward standardized MLS adoption across RCS stacks. That means:
- More consistent key-exchange semantics — your harness that models MLS will remain relevant.
- Carrier sandboxes and vendor-provided test suites are emerging, but they’re often gated. A local harness lets teams iterate faster.
- Tooling for cryptographic verification is improving — integrating OpenMLS or similar libraries gives you testable proof objects that vendors can accept as evidence during interop debugging.
Actionable checklist to get started (30–90 minutes)
- Clone a starter repo (or create the folder layout above).
- Implement a minimal registry that accepts and returns KeyPackages.
- Build a tiny endpoint binary that generates one keypair and registers it (use deterministic seed for now).
- Create a relay that routes POST /messages and logs only ciphertext.
- Wire together docker-compose and run the integration script; assert that relay logs contain no plaintext.
Wrap-up and next steps
Building a reproducible, containerized RCS E2EE test harness lets engineering, QA, and security teams validate critical properties of messaging flows without waiting on device vendors or carriers. In 2026, as MLS becomes the de facto standard and vendor betas (for example those visible in iOS 26.3) continue to surface, a local harness is one of the best investments teams can make to ensure correct, interoperable, and auditable E2EE behaviour.
Ready to try it? Start with the minimal layout above. If you want a jumpstart, the companion repository (starter templates and Dockerfiles) contains an OpenMLS-based endpoint, a registry, and a relay you can extend for your specific carrier emulation needs.
Call to action: Clone the starter harness, run the CI workflow, and open an issue or PR with your carrier/emulator tweaks. If you need help mapping this harness to a production testbed or integrating real devices, contact details.cloud for a hands-on workshop.
Related Reading
- Make STEM Kits Truly Inclusive: Applying Sanibel’s Accessibility Choices to Planet Model Boxes
- Hytale’s Darkwood as a Slot Theme: Visual & Audio Design Tips to Build Immersion
- De-Escalate on the Dock: 2 Calm Responses to Avoid Defensiveness During Day Trips
- Is Driving for Lyft Worth It If You Want to Travel Full-Time? A 58-Year-Old’s Perspective
- 10 Prompt Templates to Reduce AI Cleanup When Editing Images and Video
Related Topics
Unknown
Contributor
Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.
Up Next
More stories handpicked for you
The Cloud Provider Acquisition Playbook: Lessons from Brex and Capital One
Assessing Microsoft's Cloud Reliability: Lessons from Windows 365 Outages
Remastering Legacy Applications: Breaking Down the Process
Privacy and Anonymity: Strategies Beyond Traditional Protectors
Green Fuel Initiatives in Cloud Hosting: Aligning with Climate Goals
From Our Network
Trending stories across our publication group