This commit is contained in:
2026-08-10 21:15:19 +01:00
parent d7ca591b76
commit 4da942786e
97 changed files with 105039 additions and 3370 deletions
+176
View File
@@ -0,0 +1,176 @@
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)
}
}