123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- // Package keytab implements Kerberos keytabs: https://web.mit.edu/kerberos/krb5-devel/doc/formats/keytab_file_format.html.
- package keytab
- import (
- "bytes"
- "encoding/binary"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "os"
- "strings"
- "time"
- "unsafe"
- "github.com/jcmturner/gokrb5/v8/crypto"
- "github.com/jcmturner/gokrb5/v8/types"
- )
- const (
- keytabFirstByte byte = 05
- )
- // Keytab struct.
- type Keytab struct {
- version uint8
- Entries []entry
- }
- // Keytab entry struct.
- type entry struct {
- Principal principal
- Timestamp time.Time
- KVNO8 uint8
- Key types.EncryptionKey
- KVNO uint32
- }
- func (e entry) String() string {
- return fmt.Sprintf("% 4d %s %-56s %2d %-64x",
- e.KVNO8,
- e.Timestamp.Format("02/01/06 15:04:05"),
- e.Principal.String(),
- e.Key.KeyType,
- e.Key.KeyValue,
- )
- }
- // Keytab entry principal struct.
- type principal struct {
- NumComponents int16 `json:"-"`
- Realm string
- Components []string
- NameType int32
- }
- func (p principal) String() string {
- return fmt.Sprintf("%s@%s", strings.Join(p.Components, "/"), p.Realm)
- }
- // New creates new, empty Keytab type.
- func New() *Keytab {
- var e []entry
- return &Keytab{
- version: 2,
- Entries: e,
- }
- }
- // GetEncryptionKey returns the EncryptionKey from the Keytab for the newest entry with the required kvno, etype and matching principal.
- // If the kvno is zero then the latest kvno will be returned. The kvno is also returned for
- func (kt *Keytab) GetEncryptionKey(princName types.PrincipalName, realm string, kvno int, etype int32) (types.EncryptionKey, int, error) {
- var key types.EncryptionKey
- var t time.Time
- var kv int
- for _, k := range kt.Entries {
- if k.Principal.Realm == realm && len(k.Principal.Components) == len(princName.NameString) &&
- k.Key.KeyType == etype &&
- (k.KVNO == uint32(kvno) || kvno == 0) &&
- k.Timestamp.After(t) {
- p := true
- for i, n := range k.Principal.Components {
- if princName.NameString[i] != n {
- p = false
- break
- }
- }
- if p {
- key = k.Key
- kv = int(k.KVNO)
- t = k.Timestamp
- }
- }
- }
- if len(key.KeyValue) < 1 {
- return key, 0, fmt.Errorf("matching key not found in keytab. Looking for %q realm: %v kvno: %v etype: %v", princName.PrincipalNameString(), realm, kvno, etype)
- }
- return key, kv, nil
- }
- // Create a new Keytab entry.
- func newEntry() entry {
- var b []byte
- return entry{
- Principal: newPrincipal(),
- Timestamp: time.Time{},
- KVNO8: 0,
- Key: types.EncryptionKey{
- KeyType: 0,
- KeyValue: b,
- },
- KVNO: 0,
- }
- }
- func (kt Keytab) String() string {
- var s string
- s = `KVNO Timestamp Principal ET Key
- ---- ----------------- -------------------------------------------------------- -- ----------------------------------------------------------------
- `
- for _, entry := range kt.Entries {
- s += entry.String() + "\n"
- }
- return s
- }
- // AddEntry adds an entry to the keytab. The password should be provided in plain text and it will be converted using the defined enctype to be stored.
- func (kt *Keytab) AddEntry(principalName, realm, password string, ts time.Time, KVNO uint8, encType int32) error {
- // Generate a key from the password
- princ, _ := types.ParseSPNString(principalName)
- key, _, err := crypto.GetKeyFromPassword(password, princ, realm, encType, types.PADataSequence{})
- if err != nil {
- return err
- }
- // Populate the keytab entry principal
- ktep := newPrincipal()
- ktep.NumComponents = int16(len(princ.NameString))
- if kt.version == 1 {
- ktep.NumComponents += 1
- }
- ktep.Realm = realm
- ktep.Components = princ.NameString
- ktep.NameType = princ.NameType
- // Populate the keytab entry
- e := newEntry()
- e.Principal = ktep
- e.Timestamp = ts
- e.KVNO8 = KVNO
- e.KVNO = uint32(KVNO)
- e.Key = key
- kt.Entries = append(kt.Entries, e)
- return nil
- }
- // Create a new principal.
- func newPrincipal() principal {
- var c []string
- return principal{
- NumComponents: 0,
- Realm: "",
- Components: c,
- NameType: 0,
- }
- }
- // Load a Keytab file into a Keytab type.
- func Load(ktPath string) (*Keytab, error) {
- kt := new(Keytab)
- b, err := os.ReadFile(ktPath)
- if err != nil {
- return kt, err
- }
- err = kt.Unmarshal(b)
- return kt, err
- }
- // Marshal keytab into byte slice
- func (kt *Keytab) Marshal() ([]byte, error) {
- b := []byte{keytabFirstByte, kt.version}
- for _, e := range kt.Entries {
- eb, err := e.marshal(int(kt.version))
- if err != nil {
- return b, err
- }
- b = append(b, eb...)
- }
- return b, nil
- }
- // Write the keytab bytes to io.Writer.
- // Returns the number of bytes written
- func (kt *Keytab) Write(w io.Writer) (int, error) {
- b, err := kt.Marshal()
- if err != nil {
- return 0, fmt.Errorf("error marshaling keytab: %v", err)
- }
- return w.Write(b)
- }
- // Unmarshal byte slice of Keytab data into Keytab type.
- func (kt *Keytab) Unmarshal(b []byte) error {
- if len(b) < 2 {
- return fmt.Errorf("byte array is less than 2 bytes: %d", len(b))
- }
- //The first byte of the file always has the value 5
- if b[0] != keytabFirstByte {
- return errors.New("invalid keytab data. First byte does not equal 5")
- }
- //Get keytab version
- //The 2nd byte contains the version number (1 or 2)
- kt.version = b[1]
- if kt.version != 1 && kt.version != 2 {
- return errors.New("invalid keytab data. Keytab version is neither 1 nor 2")
- }
- //Version 1 of the file format uses native byte order for integer representations. Version 2 always uses big-endian byte order
- var endian binary.ByteOrder
- endian = binary.BigEndian
- if kt.version == 1 && isNativeEndianLittle() {
- endian = binary.LittleEndian
- }
- // n tracks position in the byte array
- n := 2
- l, err := readInt32(b, &n, &endian)
- if err != nil {
- return err
- }
- for l != 0 {
- if l < 0 {
- //Zero padded so skip over
- l = l * -1
- n = n + int(l)
- } else {
- if n < 0 {
- return fmt.Errorf("%d can't be less than zero", n)
- }
- if n+int(l) > len(b) {
- return fmt.Errorf("%s's length is less than %d", b, n+int(l))
- }
- eb := b[n : n+int(l)]
- n = n + int(l)
- ke := newEntry()
- // p keeps track as to where we are in the byte stream
- var p int
- var err error
- parsePrincipal(eb, &p, kt, &ke, &endian)
- ke.Timestamp, err = readTimestamp(eb, &p, &endian)
- if err != nil {
- return err
- }
- rei8, err := readInt8(eb, &p, &endian)
- if err != nil {
- return err
- }
- ke.KVNO8 = uint8(rei8)
- rei16, err := readInt16(eb, &p, &endian)
- if err != nil {
- return err
- }
- ke.Key.KeyType = int32(rei16)
- rei16, err = readInt16(eb, &p, &endian)
- if err != nil {
- return err
- }
- kl := int(rei16)
- ke.Key.KeyValue, err = readBytes(eb, &p, kl, &endian)
- if err != nil {
- return err
- }
- // The 32-bit key version overrides the 8-bit key version.
- // If at least 4 bytes are left after the other fields are read and they are non-zero
- // this indicates the 32-bit version is present.
- if len(eb)-p >= 4 {
- // The 32-bit key may be present
- ri32, err := readInt32(eb, &p, &endian)
- if err != nil {
- return err
- }
- ke.KVNO = uint32(ri32)
- }
- if ke.KVNO == 0 {
- // Handles if the value from the last 4 bytes was zero and also if there are not the 4 bytes present. Makes sense to put the same value here as KVNO8
- ke.KVNO = uint32(ke.KVNO8)
- }
- // Add the entry to the keytab
- kt.Entries = append(kt.Entries, ke)
- }
- // Check if there are still 4 bytes left to read
- // Also check that n is greater than zero
- if n < 0 || n > len(b) || len(b[n:]) < 4 {
- break
- }
- // Read the size of the next entry
- l, err = readInt32(b, &n, &endian)
- if err != nil {
- return err
- }
- }
- return nil
- }
- func (e entry) marshal(v int) ([]byte, error) {
- var b []byte
- pb, err := e.Principal.marshal(v)
- if err != nil {
- return b, err
- }
- b = append(b, pb...)
- var endian binary.ByteOrder
- endian = binary.BigEndian
- if v == 1 && isNativeEndianLittle() {
- endian = binary.LittleEndian
- }
- t := make([]byte, 9)
- endian.PutUint32(t[0:4], uint32(e.Timestamp.Unix()))
- t[4] = e.KVNO8
- endian.PutUint16(t[5:7], uint16(e.Key.KeyType))
- endian.PutUint16(t[7:9], uint16(len(e.Key.KeyValue)))
- b = append(b, t...)
- buf := new(bytes.Buffer)
- err = binary.Write(buf, endian, e.Key.KeyValue)
- if err != nil {
- return b, err
- }
- b = append(b, buf.Bytes()...)
- t = make([]byte, 4)
- endian.PutUint32(t, e.KVNO)
- b = append(b, t...)
- // Add the length header
- t = make([]byte, 4)
- endian.PutUint32(t, uint32(len(b)))
- b = append(t, b...)
- return b, nil
- }
- // Parse the Keytab bytes of a principal into a Keytab entry's principal.
- func parsePrincipal(b []byte, p *int, kt *Keytab, ke *entry, e *binary.ByteOrder) error {
- var err error
- ke.Principal.NumComponents, err = readInt16(b, p, e)
- if err != nil {
- return err
- }
- if kt.version == 1 {
- //In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2
- ke.Principal.NumComponents--
- }
- lenRealm, err := readInt16(b, p, e)
- if err != nil {
- return err
- }
- realmB, err := readBytes(b, p, int(lenRealm), e)
- if err != nil {
- return err
- }
- ke.Principal.Realm = string(realmB)
- for i := 0; i < int(ke.Principal.NumComponents); i++ {
- l, err := readInt16(b, p, e)
- if err != nil {
- return err
- }
- compB, err := readBytes(b, p, int(l), e)
- if err != nil {
- return err
- }
- ke.Principal.Components = append(ke.Principal.Components, string(compB))
- }
- if kt.version != 1 {
- //Name Type is omitted in version 1
- ke.Principal.NameType, err = readInt32(b, p, e)
- if err != nil {
- return err
- }
- }
- return nil
- }
- func (p principal) marshal(v int) ([]byte, error) {
- //var b []byte
- b := make([]byte, 2)
- var endian binary.ByteOrder
- endian = binary.BigEndian
- if v == 1 && isNativeEndianLittle() {
- endian = binary.LittleEndian
- }
- endian.PutUint16(b[0:], uint16(p.NumComponents))
- realm, err := marshalString(p.Realm, v)
- if err != nil {
- return b, err
- }
- b = append(b, realm...)
- for _, c := range p.Components {
- cb, err := marshalString(c, v)
- if err != nil {
- return b, err
- }
- b = append(b, cb...)
- }
- if v != 1 {
- t := make([]byte, 4)
- endian.PutUint32(t, uint32(p.NameType))
- b = append(b, t...)
- }
- return b, nil
- }
- func marshalString(s string, v int) ([]byte, error) {
- sb := []byte(s)
- b := make([]byte, 2)
- var endian binary.ByteOrder
- endian = binary.BigEndian
- if v == 1 && isNativeEndianLittle() {
- endian = binary.LittleEndian
- }
- endian.PutUint16(b[0:], uint16(len(sb)))
- buf := new(bytes.Buffer)
- err := binary.Write(buf, endian, sb)
- if err != nil {
- return b, err
- }
- b = append(b, buf.Bytes()...)
- return b, err
- }
- // Read bytes representing a timestamp.
- func readTimestamp(b []byte, p *int, e *binary.ByteOrder) (time.Time, error) {
- i32, err := readInt32(b, p, e)
- if err != nil {
- return time.Time{}, err
- }
- return time.Unix(int64(i32), 0), nil
- }
- // Read bytes representing an eight bit integer.
- func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8, err error) {
- if *p < 0 {
- return 0, fmt.Errorf("%d cannot be less than zero", *p)
- }
- if (*p + 1) > len(b) {
- return 0, fmt.Errorf("%s's length is less than %d", b, *p+1)
- }
- buf := bytes.NewBuffer(b[*p : *p+1])
- binary.Read(buf, *e, &i)
- *p++
- return
- }
- // Read bytes representing a sixteen bit integer.
- func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16, err error) {
- if *p < 0 {
- return 0, fmt.Errorf("%d cannot be less than zero", *p)
- }
- if (*p + 2) > len(b) {
- return 0, fmt.Errorf("%s's length is less than %d", b, *p+2)
- }
- buf := bytes.NewBuffer(b[*p : *p+2])
- binary.Read(buf, *e, &i)
- *p += 2
- return
- }
- // Read bytes representing a thirty two bit integer.
- func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32, err error) {
- if *p < 0 {
- return 0, fmt.Errorf("%d cannot be less than zero", *p)
- }
- if (*p + 4) > len(b) {
- return 0, fmt.Errorf("%s's length is less than %d", b, *p+4)
- }
- buf := bytes.NewBuffer(b[*p : *p+4])
- binary.Read(buf, *e, &i)
- *p += 4
- return
- }
- func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) ([]byte, error) {
- if s < 0 {
- return nil, fmt.Errorf("%d cannot be less than zero", s)
- }
- i := *p + s
- if i > len(b) {
- return nil, fmt.Errorf("%s's length is greater than %d", b, i)
- }
- buf := bytes.NewBuffer(b[*p:i])
- r := make([]byte, s)
- if err := binary.Read(buf, *e, &r); err != nil {
- return nil, err
- }
- *p += s
- return r, nil
- }
- func isNativeEndianLittle() bool {
- var x = 0x012345678
- var p = unsafe.Pointer(&x)
- var bp = (*[4]byte)(p)
- var endian bool
- if 0x01 == bp[0] {
- endian = false
- } else if (0x78 & 0xff) == (bp[0] & 0xff) {
- endian = true
- } else {
- // Default to big endian
- endian = false
- }
- return endian
- }
- // JSON return information about the keys held in the keytab in a JSON format.
- func (kt *Keytab) JSON() (string, error) {
- b, err := json.MarshalIndent(kt, "", " ")
- if err != nil {
- return "", err
- }
- return string(b), nil
- }
|