{"record":{"id":"663ed118b2b525d5","repo":"zeroclaw-labs/zeroclaw","slug":"unable-to-extract-public-key-from-attestation-obje","errorCode":null,"errorMessage":"Unable to extract public key from attestation object ({} bytes)","messagePattern":"Unable to extract public key from attestation object \\((.+?) bytes\\)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/security/webauthn.rs","lineNumber":648,"sourceCode":"            attestation_bytes[36],\n        ]);\n\n        // Check if attested credential data is present (bit 6 of flags)\n        let flags = attestation_bytes[32];\n        if flags & 0x40 != 0 && attestation_bytes.len() > 55 {\n            // AAGUID (16) + credIdLen (2) + credId (variable) + COSE key\n            let cred_id_len =\n                u16::from_be_bytes([attestation_bytes[53], attestation_bytes[54]]) as usize;\n            let cose_key_start = 55 + cred_id_len;\n            if attestation_bytes.len() > cose_key_start {\n                let cose_key = &attestation_bytes[cose_key_start..];\n                let pk = extract_p256_from_cose(cose_key)?;\n                return Ok((pk, sign_count));\n            }\n        }\n    }\n\n    anyhow::bail!(\n        \"Unable to extract public key from attestation object ({} bytes)\",\n        attestation_bytes.len()\n    )\n}\n\n/// Simplified attestation object for the enrollment UI.\n#[derive(Deserialize)]\nstruct AttestationObject {\n    /// Base64url-encoded public key (uncompressed P-256 or DER SPKI).\n    public_key: String,\n    /// Initial sign counter.\n    sign_count: Option<u32>,\n}\n\nfn extract_p256_from_cose(cose: &[u8]) -> Result<Vec<u8>> {\n    // If it starts with 0x04 and is 65 bytes, it's already uncompressed P-256\n    if cose.len() >= 65 && cose[0] == 0x04 {\n        return Ok(cose[..65].to_vec());","sourceCodeStart":630,"sourceCodeEnd":666,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/security/webauthn.rs#L630-L666","documentation":"During finish_registration, extract_public_key_from_attestation walks the CBOR attestation object looking for a COSE key; if none is found (or the layout is unexpected) it bails, reporting the attestation byte length (webauthn.rs:643-649). Registration cannot proceed without the public key.","triggerScenarios":"finish_registration with an attestation object that lacks a COSE key in the expected position: a truncated authenticator response, or an attestation statement layout the extractor does not traverse.","commonSituations":"A browser/authenticator pair produces an attestation format outside the parsed paths; upstream base64url decoding dropped bytes; the client deserialized and re-serialized the response incorrectly.","solutions":["Log the attestation format identifier ('fmt') and byte length, then retry registration — many authenticators work on retry with a different format","Ensure the client passes the raw base64url attestationObject without re-encoding","If one authenticator model always fails, capture its attestation object and check whether the COSE key is embedded in a different CBOR layout"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"Catch around finish_registration, return a user-facing 'registration failed — try again or use another passkey/security key', and log the attestation format and byte length (not the raw object) for diagnostics.","preventionTips":["Pass authenticator responses through byte-for-byte — never re-serialize them","Test enrollment with more than one browser and authenticator model","Log WebAuthn clientData type and origin alongside failures to spot client-side bugs"],"tags":["webauthn","attestation","cbor","registration","rust"],"backgroundTag":"webauthn-attestation-parse-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}