CBOR, and why Webauthn insisted on it

Open a Webauthn attestation object and you will not find JSON. You will find CBOR, defined by RFC 8949: the same data model as JSON, encoded in binary.

Why not JSON

The code that produces an attestation runs on a security key: a microcontroller with very little memory and no appetite for string parsing. CBOR is written and read with a few hundred bytes of state, which JSON is not.

CBOR also carries binary strings natively. A public key in JSON has to be base64-encoded, which costs a third more bytes and an encoding step at each end. In CBOR it is just bytes.

The part that trips people up

CBOR map keys are not always strings. In COSE, the key format Webauthn uses, they are small integers: 1 for the key type, 3 for the algorithm, -1 and -2 for curve parameters. Decode a COSE key into a PHP array and you get numeric keys with no names, which is disconcerting the first time.

Worse for anyone verifying a signature: the encoding must be canonical. The same data can be encoded several valid ways, and a signature covers the bytes, not the meaning. Re-encode a structure differently and the signature fails, with nothing to tell you why.

The practical rule

Never re-encode what you are about to verify. Keep the original bytes, verify against those, and use the decoded form only to read values. It is the single most common cause of a Webauthn signature that refuses to validate for no apparent reason.

cbor-php encodes and decodes CBOR, and keeps the original bytes available for exactly that reason.