update
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user