// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT // A minimal DER encoder and decoder for the Kerberos protocol messages // of RFC 4120: just the constructs the AP-REQ and AP-REP exchanges of // the GSS context establishment need. package krb5 import ( "encoding/binary" "errors" ) // DER tag octets used below. const ( tagInteger = 0x02 tagBitString = 0x03 tagOctet = 0x04 tagNull = 0x05 tagOID = 0x06 tagSequence = 0x30 tagGeneral = 0x80 // the high bits of a context or application tag ) // ErrDER marks a malformed DER input. var ErrDER = errors.New("krb5: malformed DER") // derLen encodes a DER length octet string. func derLen(n int) []byte { if n < 0x80 { return []byte{byte(n)} } var buf [8]byte i := len(buf) for n > 0 { i-- buf[i] = byte(n) n >>= 8 } return append([]byte{0x80 | byte(len(buf)-i)}, buf[i:]...) } // derTLV wraps a payload in one tag-length-value record. func derTLV(tag byte, payload []byte) []byte { return append(append([]byte{tag}, derLen(len(payload))...), payload...) } // derInt encodes a DER integer. func derInt(v uint64) []byte { var raw [8]byte binary.BigEndian.PutUint64(raw[:], v) i := 0 for i < 7 && raw[i] == 0 && raw[i+1]&0x80 == 0 { i++ } for i < 8 && raw[i] == 0xff && i+1 < 8 && raw[i+1]&0x80 != 0 { i++ } return derTLV(tagInteger, raw[i:]) } // derGeneralString encodes a Kerberos string. func derGeneralString(s string) []byte { return derTLV(0x1b, []byte(s)) } // derOID encodes the krb5 mechanism object identifier. var derOID = []byte{0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02} // derReader walks a DER structure. type derReader struct { b []byte off int } func newDERReader(b []byte) *derReader { return &derReader{b: b} } // next reads one TLV and answers the tag and payload. func (r *derReader) next() (byte, []byte, error) { if r.off+2 > len(r.b) { return 0, nil, ErrDER } tag := r.b[r.off] length := int(r.b[r.off+1]) start := r.off + 2 if length&0x80 != 0 { n := length & 0x7f if n == 0 || n > 4 || start+n > len(r.b) { return 0, nil, ErrDER } length = 0 for i := range n { length = length<<8 | int(r.b[start+i]) } start += n } if start+length > len(r.b) { return 0, nil, ErrDER } r.off = start + length return tag, r.b[start : start+length], nil } // expect reads one TLV and requires the tag. func (r *derReader) expect(tag byte) ([]byte, error) { got, payload, err := r.next() if err != nil { return nil, err } if got != tag { return nil, ErrDER } return payload, nil } // derUint decodes a DER integer payload. func derUint(payload []byte) (uint64, error) { if len(payload) == 0 || len(payload) > 8 { return 0, ErrDER } var v uint64 for _, b := range payload { v = v<<8 | uint64(b) } return v, nil } // derBitString decodes a DER bit string payload: the leading unused bit // count octet followed by the bits. func derBitString(payload []byte) ([]byte, error) { if len(payload) == 0 { return nil, ErrDER } return payload[1:], nil }