177 lines
4.5 KiB
Go
177 lines
4.5 KiB
Go
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)
|
|
}
|
|
}
|