n0-computer/iroh · error · VerificationError
MismatchedSuffix
MismatchedSuffix
Error message
Client didn't extract the same keying material, the suffix mismatched: expected {expected:X?} but got {actual:X?} What it means
VerificationError::MismatchedSuffix is returned by the server side of the relay handshake when the client's derived keying material has a different 16-byte suffix than the server's. This means both ends did not derive the same shared secret from the TLS channel, typically because a middlebox (TLS proxy/MITM) altered the channel. It distinguishes this failure from a bad client signature.
Solutions
- Bypass any TLS-intercepting proxy for the relay endpoint (allowlist the relay domain/port).
- Verify client and iroh-relay versions both use the same handshake/keying-material derivation.
- Check that the relay serves the correct TLS certificate chain and is not fronted by a re-terminating load balancer.
- Retry from a different network to confirm the middlebox is the cause.
Defensive patterns
Strategy: try-catch
Try / catch
// Rust
match client.connect(relay_url).await {
Err(err) if matches!(err, ConnectError::Handshake(VerificationError::MismatchedSuffix { .. })) => {
eprintln!("TLS interception detected: keying material mismatch — bypass proxy or use a trusted network");
}
other => other?,
} Prevention
- Allowlist iroh relay endpoints in TLS-inspection proxies.
- Pin relay TLS certificates where possible.
- Keep iroh client and relay versions in sync.
- Test connectivity from networks without TLS interception before deploying.
When it happens
Trigger: Client connects to the relay through a TLS-intercepting proxy or corporate MITM appliance; mismatched TLS exporter configuration between client and server during handshake verification (serverside::verify).
Common situations: Corporate networks with TLS inspection proxies; VPNs or firewalls that re-terminate TLS; version mismatches where client and relay derive keying material differently.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
AI-assisted analysis of n0-computer/iroh@2b4de030ce (2026-09-08).
Data as JSON: /api/errors/488d7f48bc8528e7.
Report an issue: GitHub.
Appendix: source
Thrown at iroh-relay/src/protos/handshake.rs:309
) -> Result<(), Box<VerificationError>> {
let key_material = io
.export_keying_material(
[0u8; 32],
DOMAIN_SEP_TLS_EXPORT_LABEL,
Some(self.public_key.as_bytes()),
)
.ok_or_else(|| e!(VerificationError::NoKeyingMaterial))?;
// We split the export and only sign the first 16 bytes, and
// pass through the last 16 bytes.
// Passing on the suffix helps the verifying end figure out what
// went wrong: If there's a suffix mismatch, then the exported keying
// material on both ends wasn't the same - so perhaps there was a
// TLS proxy in between or similar.
// If the suffix does match, but the signature doesn't verify, then
// there must be something wrong with the client's secret key or signature.
let (message, suffix) = key_material.split_at(16);
let suffix: [u8; 16] = suffix.try_into().expect("hardcoded length");
ensure!(
suffix == self.key_material_suffix,
VerificationError::MismatchedSuffix {
expected: self.key_material_suffix,
actual: suffix
}
);
// NOTE: We don't blake3-hash here as we do it in [`ServerChallenge::message_to_sign`],
// because we already have a domain separation string and keyed hashing step in
// the TLS export keying material above.
self.public_key
.verify(message, &Signature::from_bytes(&self.signature))
.map_err(|err| {
e!(VerificationError::SignatureInvalid {
source: err,
message: message.to_vec(),
public_key: self.public_key,
signature: self.signature
})View on GitHub (pinned to 2b4de030ce)