zeroclaw-labs/zeroclaw · error

Authenticator data relying party ID hash mismatch

Error message

Authenticator data relying party ID hash mismatch

What it means

The first 32 bytes of authenticator data must equal SHA-256(rp_id). validate_assertion_authenticator_data recomputes the hash of the configured RP ID and compares; a mismatch means the credential was produced for a different relying party (or the blob is corrupt), so the assertion is rejected to prevent cross-site credential use.

Source

Thrown at crates/zeroclaw-runtime/src/security/webauthn.rs:595

            std::fs::set_permissions(
                &self.credentials_path,
                std::fs::Permissions::from_mode(0o600),
            )
            .context("Failed to set credentials file permissions")?;
        }

        Ok(())
    }
}

fn validate_assertion_authenticator_data(auth_data: &[u8], rp_id: &str) -> Result<u32> {
    anyhow::ensure!(
        auth_data.len() >= AUTHENTICATOR_DATA_FIXED_LEN,
        "Authenticator data is shorter than the required fixed fields"
    );

    let expected_rp_id_hash = ring::digest::digest(&ring::digest::SHA256, rp_id.as_bytes());
    anyhow::ensure!(
        &auth_data[..32] == expected_rp_id_hash.as_ref(),
        "Authenticator data relying party ID hash mismatch"
    );
    anyhow::ensure!(
        auth_data[32] & AUTHENTICATOR_FLAG_UP != 0,
        "Authenticator data does not assert user presence"
    );

    Ok(u32::from_be_bytes([
        auth_data[33],
        auth_data[34],
        auth_data[35],
        auth_data[36],
    ]))
}

// ── Attestation parsing ─────────────────────────────────────────

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Compare the rp_id passed to finish_authentication with the rp_id stored on the credential record from registration; they must be byte-identical.
  2. Use the eTLD+1 of the origin the browser actually runs on ("example.com" covers app.example.com; "localhost" only works on localhost).
  3. If the RP ID changed intentionally, old credentials cannot be salvaged: delete them and re-run the registration ceremony.
  4. Check for payload corruption if the RP ID is definitely correct (hash mismatch can also indicate mangled authenticatorData).

Example fix

// before: asserting with a hardcoded host that differs from registration
webauthn::finish_authentication(&cred, &assertion, "api.example.com").await?;

// after: always derive rp_id from the stored credential / single config source
let rp_id = cred.rp_id.clone(); // recorded at begin_registration time
webauthn::finish_authentication(&cred, &assertion, &rp_id).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn rp_id_matches_registration(cred: &Credential, rp_id: &str) -> bool {
    cred.rp_id == rp_id
}

// derive the assertion rp_id from the stored credential, never from the request:
let rp_id = cred.rp_id.as_str();
assert!(rp_id_matches_registration(&cred, rp_id));

Try / catch

match webauthn::finish_authentication(&cred, &assertion, &cred.rp_id).await {
    Err(e) if e.to_string().contains("relying party ID hash mismatch") => {
        // credential belongs to another RP: prompt re-registration, never auto-retry
        prompt_re_registration(&user).await;
        Err(e)
    }
    other => other,
}

Prevention

When it happens

Trigger: finish_authentication invoked with an rp_id different from the one used at registration (e.g. registered on "example.com", asserting against "api.example.com" or "localhost"), a frontend served from a different origin than the configured RP ID, or a credential key copied from another deployment.

Common situations: Domain changes during deployment (apex vs www), running the web UI on localhost while the server expects the production host, staging and production sharing a credential database, RP ID typos in config, proxy frontends that change the effective origin.

Understand the failure class

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/0c2ab728327a9800. Report an issue: GitHub.