blob: 98ec29b9e071adab77ff5b8be62b0d098ec9f8e4 [file] [log] [blame]
Scott Baker2c1c4822019-10-16 11:02:41 -07001package 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 "gopkg.in/jcmturner/gokrb5.v7/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}