zeroclaw-labs/zeroclaw · error

Authenticator data does not assert user presence

Error message

Authenticator data does not assert user presence

What it means

The flags byte at auth_data[32] must have the User Presence (UP, 0x01) bit set. UP proves the authenticator physically confirmed the user (touch, biometric, PIN entry) during this assertion. The library requires it unconditionally, so an assertion without UP is rejected as unauthenticated.

Source

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

            .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 ─────────────────────────────────────────

fn extract_public_key_from_attestation(attestation_bytes: &[u8]) -> Result<(Vec<u8>, u32)> {
    // Try JSON format first (from our enrollment UI)
    if let Ok(att) = serde_json::from_slice::<AttestationObject>(attestation_bytes) {
        let pk = URL_SAFE_NO_PAD

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. In tests, set the UP bit when building authenticator data: flags |= 0x01 before signing.
  2. On real hardware, make sure the user actually performs the gesture (tap/biometric) and that the client requests userVerification appropriately.
  3. If a specific authenticator never sets UP, treat it as non-compliant for this flow; UP is mandatory and cannot be relaxed via configuration.
  4. Verify the signed bytes match the authenticatorData sent (a mismatch often indicates the client signed a different blob than it returned).

Example fix

// before: synthetic fixture with zeroed flags
let mut auth_data = [0u8; 37];
auth_data[..32].copy_from_slice(&rp_id_hash);
// flags byte left as 0 -> assertion rejected

// after: assert user presence in the fixture
let mut auth_data = [0u8; 37];
auth_data[..32].copy_from_slice(&rp_id_hash);
auth_data[32] = 0x01; // AUTHENTICATOR_FLAG_UP
auth_data[33..37].copy_from_slice(&sign_count.to_be_bytes());
Defensive patterns

Strategy: validation

Validate before calling

const AUTHENTICATOR_FLAG_UP: u8 = 0x01;

fn user_presence_asserted(encoded: &str) -> Option<bool> {
    let bytes = base64url::decode(encoded).ok()?;
    (bytes.len() >= 33).then(|| bytes[32] & AUTHENTICATOR_FLAG_UP != 0)
}

Try / catch

match webauthn::finish_authentication(&cred, &assertion, rp_id).await {
    Err(e) if e.to_string().contains("does not assert user presence") => {
        deny_login("user presence not confirmed"); // policy: UP is mandatory, no bypass
        Err(e)
    }
    other => other,
}

Prevention

When it happens

Trigger: finish_authentication receives authenticatorData whose flags byte has bit 0 clear: synthetic test fixtures that zero the flags, authenticators or platform flows that skip the user gesture, assertions replayed from a ceremony where the token was not tapped, or hand-built assertions from a test harness.

Common situations: Unit tests constructing authenticator data by hand (they forget to OR in 0x01), NFC security keys that time out before the tap, browsers or WebAuthn polyfills that misreport flags, conditional-mediation/silent flows on non-compliant authenticators.

Understand the failure class

Related errors


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