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

  1. Bypass any TLS-intercepting proxy for the relay endpoint (allowlist the relay domain/port).
  2. Verify client and iroh-relay versions both use the same handshake/keying-material derivation.
  3. Check that the relay serves the correct TLS certificate chain and is not fronted by a re-terminating load balancer.
  4. 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

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)