zeroclaw-labs/zeroclaw · error · anyhow::Error

Unsupported COSE key format (expected uncompressed P-256, go

Error message

Unsupported COSE key format (expected uncompressed P-256, got {} bytes starting with 0x{:02x})

What it means

extract_p256_from_cose accepts only a 65-byte uncompressed EC2 P-256 point starting with 0x04 (webauthn.rs:661-671); anything else — compressed points, P-384/Ed25519/RSA COSE keys, or short buffers — is rejected. The message includes length and first byte so you can identify the actual key type.

Source

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

    )
}

/// Simplified attestation object for the enrollment UI.
#[derive(Deserialize)]
struct AttestationObject {
    /// Base64url-encoded public key (uncompressed P-256 or DER SPKI).
    public_key: String,
    /// Initial sign counter.
    sign_count: Option<u32>,
}

fn extract_p256_from_cose(cose: &[u8]) -> Result<Vec<u8>> {
    // If it starts with 0x04 and is 65 bytes, it's already uncompressed P-256
    if cose.len() >= 65 && cose[0] == 0x04 {
        return Ok(cose[..65].to_vec());
    }

    anyhow::bail!(
        "Unsupported COSE key format (expected uncompressed P-256, got {} bytes starting with 0x{:02x})",
        cose.len(),
        cose.first().copied().unwrap_or(0)
    )
}

// ── Signature verification ──────────────────────────────────────

fn verify_es256_signature(public_key: &[u8], message: &[u8], sig: &[u8]) -> Result<()> {
    // ring's UnparsedPublicKey expects the raw uncompressed point for P-256
    // (not wrapped in SPKI). If we have SPKI, we'd need to extract the point.
    // For our use case the stored key is always the raw uncompressed point.
    let pk = signature::UnparsedPublicKey::new(&signature::ECDSA_P256_SHA256_ASN1, public_key);

    pk.verify(message, sig).map_err(|_| {
        ::zeroclaw_log::record!(
            WARN,
            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Request only ES256 (alg -7, EC2, P-256) in your WebAuthn creation options
  2. Read the reported length and first byte: 33 bytes starting 0x02/0x03 means compressed; 97 bytes starting 0x04 suggests P-384
  3. If you must support other key types, extend the extractor — current code intentionally supports only uncompressed P-256

Example fix

// before: broad algorithm set
const creationOptions = {
  pubKeyCredParams: [
    { type: "public-key", alg: -7 },
    { type: "public-key", alg: -8 },   // EdDSA
    { type: "public-key", alg: -257 }, // RS256
  ],
};

// after: only ES256 / uncompressed P-256, which the server can extract
const creationOptions = {
  pubKeyCredParams: [{ type: "public-key", alg: -7 }],
};
Defensive patterns

Strategy: validation

Validate before calling

// Browser side: restrict creation options so non-ES256 credentials are never made
const creationOptions = {
  pubKeyCredParams: [{ type: "public-key", alg: -7 }], // ES256 only
  authenticatorSelection: { userVerification: "preferred" },
};

Try / catch

Catch around finish_registration, read the reported byte length/first byte to classify the key type, and tell the user to enroll a credential with a standard passkey (ES256); log the key shape for support triage.

Prevention

When it happens

Trigger: finish_registration with an authenticator that used ES384 or EdDSA, or a COSE key serialized as separate x/y coordinates instead of the 0x04||x||y form.

Common situations: The WebAuthn creation options allow broader algorithms than ES256 (alg -7); some platform authenticators default to other curves; a custom client builds the COSE structure by hand.

Related errors


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