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