package webauthn import ( "encoding/binary" "fmt" ) // cborDecode is a minimal CBOR (RFC 8949) decoder covering only the subset // WebAuthn actually uses: unsigned/negative integers, byte strings, text // strings, arrays, maps, and the true/false/null simple values — // definite-length items only (WebAuthn's attestationObject/COSE keys never // use CBOR's indefinite-length encoding). This is not a general-purpose // CBOR library, the same way internal/dnsutil is not a general DNS library. // // All integers (major types 0 and 1) decode to Go int64 — COSE key labels // mix small positive (kty=1, alg=3) and negative (crv=-1, x=-2, y=-3) // values, and normalizing to one Go type avoids uint64-vs-int64 mismatches // when looking values up in a decoded map. func cborDecode(data []byte) (any, error) { d := &cborDecoder{data: data} return d.decodeValue() } // cborDecodeWithLength is cborDecode plus how many bytes were consumed — // needed when a CBOR item (a COSE key) is embedded inside a larger binary // structure (authData) followed by more data, not the whole buffer. func cborDecodeWithLength(data []byte) (any, int, error) { d := &cborDecoder{data: data} v, err := d.decodeValue() if err != nil { return nil, 0, err } return v, d.pos, nil } type cborDecoder struct { data []byte pos int } func (d *cborDecoder) readByte() (byte, error) { if d.pos >= len(d.data) { return 0, fmt.Errorf("cbor: unexpected end of data") } b := d.data[d.pos] d.pos++ return b, nil } func (d *cborDecoder) readBytes(n int) ([]byte, error) { if n < 0 || d.pos+n > len(d.data) { return nil, fmt.Errorf("cbor: unexpected end of data (need %d bytes, have %d)", n, len(d.data)-d.pos) } b := d.data[d.pos : d.pos+n] d.pos += n return b, nil } // readLength reads the additional-info length/value encoding shared by // every major type: 0-23 is a literal value, 24/25/26/27 mean 1/2/4/8 // following bytes hold it. Indefinite length (additional info 31) is // rejected — not used by anything this package parses. func (d *cborDecoder) readLength(additionalInfo byte) (uint64, error) { switch { case additionalInfo < 24: return uint64(additionalInfo), nil case additionalInfo == 24: b, err := d.readByte() return uint64(b), err case additionalInfo == 25: b, err := d.readBytes(2) if err != nil { return 0, err } return uint64(binary.BigEndian.Uint16(b)), nil case additionalInfo == 26: b, err := d.readBytes(4) if err != nil { return 0, err } return uint64(binary.BigEndian.Uint32(b)), nil case additionalInfo == 27: b, err := d.readBytes(8) if err != nil { return 0, err } return binary.BigEndian.Uint64(b), nil default: return 0, fmt.Errorf("cbor: indefinite-length items are not supported (additional info %d)", additionalInfo) } } func (d *cborDecoder) decodeValue() (any, error) { head, err := d.readByte() if err != nil { return nil, err } majorType := head >> 5 additionalInfo := head & 0x1F switch majorType { case 0: // unsigned integer n, err := d.readLength(additionalInfo) if err != nil { return nil, err } return int64(n), nil case 1: // negative integer: value = -1 - n n, err := d.readLength(additionalInfo) if err != nil { return nil, err } return -1 - int64(n), nil case 2: // byte string n, err := d.readLength(additionalInfo) if err != nil { return nil, err } return d.readBytes(int(n)) case 3: // text string n, err := d.readLength(additionalInfo) if err != nil { return nil, err } b, err := d.readBytes(int(n)) if err != nil { return nil, err } return string(b), nil case 4: // array n, err := d.readLength(additionalInfo) if err != nil { return nil, err } arr := make([]any, n) for i := range arr { v, err := d.decodeValue() if err != nil { return nil, err } arr[i] = v } return arr, nil case 5: // map n, err := d.readLength(additionalInfo) if err != nil { return nil, err } m := make(map[any]any, n) for i := uint64(0); i < n; i++ { k, err := d.decodeValue() if err != nil { return nil, err } v, err := d.decodeValue() if err != nil { return nil, err } m[k] = v } return m, nil case 7: // simple values: only false/true/null are meaningful here switch additionalInfo { case 20: return false, nil case 21: return true, nil case 22, 23: return nil, nil default: return nil, fmt.Errorf("cbor: unsupported simple/float value (additional info %d)", additionalInfo) } default: return nil, fmt.Errorf("cbor: unsupported major type %d", majorType) } }