1package credentials
2
3import (
4	"bytes"
5	"encoding/binary"
6	"errors"
7	"io/ioutil"
8	"strings"
9	"time"
10	"unsafe"
11
12	"github.com/jcmturner/gofork/encoding/asn1"
13	"github.com/jcmturner/gokrb5/v8/types"
14)
15
16const (
17	headerFieldTagKDCOffset = 1
18)
19
20// The first byte of the file always has the value 5.
21// The value of the second byte contains the version number (1 through 4)
22// Versions 1 and 2 of the file format use native byte order for integer representations.
23// Versions 3 and 4 always use big-endian byte order
24// After the two-byte version indicator, the file has three parts:
25//   1) the header (in version 4 only)
26//   2) the default principal name
27//   3) a sequence of credentials
28
29// CCache is the file credentials cache as define here: https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html
30type CCache struct {
31	Version          uint8
32	Header           header
33	DefaultPrincipal principal
34	Credentials      []*Credential
35	Path             string
36}
37
38type header struct {
39	length uint16
40	fields []headerField
41}
42
43type headerField struct {
44	tag    uint16
45	length uint16
46	value  []byte
47}
48
49// Credential cache entry principal struct.
50type principal struct {
51	Realm         string
52	PrincipalName types.PrincipalName
53}
54
55// Credential holds a Kerberos client's ccache credential information.
56type Credential struct {
57	Client       principal
58	Server       principal
59	Key          types.EncryptionKey
60	AuthTime     time.Time
61	StartTime    time.Time
62	EndTime      time.Time
63	RenewTill    time.Time
64	IsSKey       bool
65	TicketFlags  asn1.BitString
66	Addresses    []types.HostAddress
67	AuthData     []types.AuthorizationDataEntry
68	Ticket       []byte
69	SecondTicket []byte
70}
71
72// LoadCCache loads a credential cache file into a CCache type.
73func LoadCCache(cpath string) (*CCache, error) {
74	c := new(CCache)
75	b, err := ioutil.ReadFile(cpath)
76	if err != nil {
77		return c, err
78	}
79	err = c.Unmarshal(b)
80	return c, err
81}
82
83// Unmarshal a byte slice of credential cache data into CCache type.
84func (c *CCache) Unmarshal(b []byte) error {
85	p := 0
86	//The first byte of the file always has the value 5
87	if int8(b[p]) != 5 {
88		return errors.New("Invalid credential cache data. First byte does not equal 5")
89	}
90	p++
91	//Get credential cache version
92	//The second byte contains the version number (1 to 4)
93	c.Version = b[p]
94	if c.Version < 1 || c.Version > 4 {
95		return errors.New("Invalid credential cache data. Keytab version is not within 1 to 4")
96	}
97	p++
98	//Version 1 or 2 of the file format uses native byte order for integer representations. Versions 3 & 4 always uses big-endian byte order
99	var endian binary.ByteOrder
100	endian = binary.BigEndian
101	if (c.Version == 1 || c.Version == 2) && isNativeEndianLittle() {
102		endian = binary.LittleEndian
103	}
104	if c.Version == 4 {
105		err := parseHeader(b, &p, c, &endian)
106		if err != nil {
107			return err
108		}
109	}
110	c.DefaultPrincipal = parsePrincipal(b, &p, c, &endian)
111	for p < len(b) {
112		cred, err := parseCredential(b, &p, c, &endian)
113		if err != nil {
114			return err
115		}
116		c.Credentials = append(c.Credentials, cred)
117	}
118	return nil
119}
120
121func parseHeader(b []byte, p *int, c *CCache, e *binary.ByteOrder) error {
122	if c.Version != 4 {
123		return errors.New("Credentials cache version is not 4 so there is no header to parse.")
124	}
125	h := header{}
126	h.length = uint16(readInt16(b, p, e))
127	for *p <= int(h.length) {
128		f := headerField{}
129		f.tag = uint16(readInt16(b, p, e))
130		f.length = uint16(readInt16(b, p, e))
131		f.value = b[*p : *p+int(f.length)]
132		*p += int(f.length)
133		if !f.valid() {
134			return errors.New("Invalid credential cache header found")
135		}
136		h.fields = append(h.fields, f)
137	}
138	c.Header = h
139	return nil
140}
141
142// Parse the Keytab bytes of a principal into a Keytab entry's principal.
143func parsePrincipal(b []byte, p *int, c *CCache, e *binary.ByteOrder) (princ principal) {
144	if c.Version != 1 {
145		//Name Type is omitted in version 1
146		princ.PrincipalName.NameType = readInt32(b, p, e)
147	}
148	nc := int(readInt32(b, p, e))
149	if c.Version == 1 {
150		//In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2
151		nc--
152	}
153	lenRealm := readInt32(b, p, e)
154	princ.Realm = string(readBytes(b, p, int(lenRealm), e))
155	for i := 0; i < nc; i++ {
156		l := readInt32(b, p, e)
157		princ.PrincipalName.NameString = append(princ.PrincipalName.NameString, string(readBytes(b, p, int(l), e)))
158	}
159	return princ
160}
161
162func parseCredential(b []byte, p *int, c *CCache, e *binary.ByteOrder) (cred *Credential, err error) {
163	cred = new(Credential)
164	cred.Client = parsePrincipal(b, p, c, e)
165	cred.Server = parsePrincipal(b, p, c, e)
166	key := types.EncryptionKey{}
167	key.KeyType = int32(readInt16(b, p, e))
168	if c.Version == 3 {
169		//repeated twice in version 3
170		key.KeyType = int32(readInt16(b, p, e))
171	}
172	key.KeyValue = readData(b, p, e)
173	cred.Key = key
174	cred.AuthTime = readTimestamp(b, p, e)
175	cred.StartTime = readTimestamp(b, p, e)
176	cred.EndTime = readTimestamp(b, p, e)
177	cred.RenewTill = readTimestamp(b, p, e)
178	if ik := readInt8(b, p, e); ik == 0 {
179		cred.IsSKey = false
180	} else {
181		cred.IsSKey = true
182	}
183	cred.TicketFlags = types.NewKrbFlags()
184	cred.TicketFlags.Bytes = readBytes(b, p, 4, e)
185	l := int(readInt32(b, p, e))
186	cred.Addresses = make([]types.HostAddress, l, l)
187	for i := range cred.Addresses {
188		cred.Addresses[i] = readAddress(b, p, e)
189	}
190	l = int(readInt32(b, p, e))
191	cred.AuthData = make([]types.AuthorizationDataEntry, l, l)
192	for i := range cred.AuthData {
193		cred.AuthData[i] = readAuthDataEntry(b, p, e)
194	}
195	cred.Ticket = readData(b, p, e)
196	cred.SecondTicket = readData(b, p, e)
197	return
198}
199
200// GetClientPrincipalName returns a PrincipalName type for the client the credentials cache is for.
201func (c *CCache) GetClientPrincipalName() types.PrincipalName {
202	return c.DefaultPrincipal.PrincipalName
203}
204
205// GetClientRealm returns the reals of the client the credentials cache is for.
206func (c *CCache) GetClientRealm() string {
207	return c.DefaultPrincipal.Realm
208}
209
210// GetClientCredentials returns a Credentials object representing the client of the credentials cache.
211func (c *CCache) GetClientCredentials() *Credentials {
212	return &Credentials{
213		username: c.DefaultPrincipal.PrincipalName.PrincipalNameString(),
214		realm:    c.GetClientRealm(),
215		cname:    c.DefaultPrincipal.PrincipalName,
216	}
217}
218
219// Contains tests if the cache contains a credential for the provided server PrincipalName
220func (c *CCache) Contains(p types.PrincipalName) bool {
221	for _, cred := range c.Credentials {
222		if cred.Server.PrincipalName.Equal(p) {
223			return true
224		}
225	}
226	return false
227}
228
229// GetEntry returns a specific credential for the PrincipalName provided.
230func (c *CCache) GetEntry(p types.PrincipalName) (*Credential, bool) {
231	cred := new(Credential)
232	var found bool
233	for i := range c.Credentials {
234		if c.Credentials[i].Server.PrincipalName.Equal(p) {
235			cred = c.Credentials[i]
236			found = true
237			break
238		}
239	}
240	if !found {
241		return cred, false
242	}
243	return cred, true
244}
245
246// GetEntries filters out configuration entries an returns a slice of credentials.
247func (c *CCache) GetEntries() []*Credential {
248	creds := make([]*Credential, 0)
249	for _, cred := range c.Credentials {
250		// Filter out configuration entries
251		if strings.HasPrefix(cred.Server.Realm, "X-CACHECONF") {
252			continue
253		}
254		creds = append(creds, cred)
255	}
256	return creds
257}
258
259func (h *headerField) valid() bool {
260	// At this time there is only one defined header field.
261	// Its tag value is 1, its length is always 8.
262	// Its contents are two 32-bit integers giving the seconds and microseconds
263	// of the time offset of the KDC relative to the client.
264	// Adding this offset to the current time on the client should give the current time on the KDC, if that offset has not changed since the initial authentication.
265
266	// Done as a switch in case other tag values are added in the future.
267	switch h.tag {
268	case headerFieldTagKDCOffset:
269		if h.length != 8 || len(h.value) != 8 {
270			return false
271		}
272		return true
273	}
274	return false
275}
276
277func readData(b []byte, p *int, e *binary.ByteOrder) []byte {
278	l := readInt32(b, p, e)
279	return readBytes(b, p, int(l), e)
280}
281
282func readAddress(b []byte, p *int, e *binary.ByteOrder) types.HostAddress {
283	a := types.HostAddress{}
284	a.AddrType = int32(readInt16(b, p, e))
285	a.Address = readData(b, p, e)
286	return a
287}
288
289func readAuthDataEntry(b []byte, p *int, e *binary.ByteOrder) types.AuthorizationDataEntry {
290	a := types.AuthorizationDataEntry{}
291	a.ADType = int32(readInt16(b, p, e))
292	a.ADData = readData(b, p, e)
293	return a
294}
295
296// Read bytes representing a timestamp.
297func readTimestamp(b []byte, p *int, e *binary.ByteOrder) time.Time {
298	return time.Unix(int64(readInt32(b, p, e)), 0)
299}
300
301// Read bytes representing an eight bit integer.
302func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8) {
303	buf := bytes.NewBuffer(b[*p : *p+1])
304	binary.Read(buf, *e, &i)
305	*p++
306	return
307}
308
309// Read bytes representing a sixteen bit integer.
310func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16) {
311	buf := bytes.NewBuffer(b[*p : *p+2])
312	binary.Read(buf, *e, &i)
313	*p += 2
314	return
315}
316
317// Read bytes representing a thirty two bit integer.
318func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32) {
319	buf := bytes.NewBuffer(b[*p : *p+4])
320	binary.Read(buf, *e, &i)
321	*p += 4
322	return
323}
324
325func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) []byte {
326	buf := bytes.NewBuffer(b[*p : *p+s])
327	r := make([]byte, s)
328	binary.Read(buf, *e, &r)
329	*p += s
330	return r
331}
332
333func isNativeEndianLittle() bool {
334	var x = 0x012345678
335	var p = unsafe.Pointer(&x)
336	var bp = (*[4]byte)(p)
337
338	var endian bool
339	if 0x01 == bp[0] {
340		endian = false
341	} else if (0x78 & 0xff) == (bp[0] & 0xff) {
342		endian = true
343	} else {
344		// Default to big endian
345		endian = false
346	}
347	return endian
348}
349