blob: 22c02044b31741266640278b29b9c5ebecb6bc3b [file] [log] [blame]
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +00001// Package keytab implements Kerberos keytabs: https://web.mit.edu/kerberos/krb5-devel/doc/formats/keytab_file_format.html.
2package keytab
3
4import (
5 "bytes"
6 "encoding/binary"
7 "errors"
8 "fmt"
9 "io"
10 "io/ioutil"
11 "time"
12 "unsafe"
13
14 "gopkg.in/jcmturner/gokrb5.v7/types"
15)
16
17const (
18 keytabFirstByte byte = 05
19)
20
21// Keytab struct.
22type Keytab struct {
23 version uint8
24 Entries []entry
25}
26
27// Keytab entry struct.
28type entry struct {
29 Principal principal
30 Timestamp time.Time
31 KVNO8 uint8
32 Key types.EncryptionKey
33 KVNO uint32
34}
35
36// Keytab entry principal struct.
37type principal struct {
38 NumComponents int16
39 Realm string
40 Components []string
41 NameType int32
42}
43
44// New creates new, empty Keytab type.
45func New() *Keytab {
46 var e []entry
47 return &Keytab{
48 version: 0,
49 Entries: e,
50 }
51}
52
53// GetEncryptionKey returns the EncryptionKey from the Keytab for the newest entry with the required kvno, etype and matching principal.
54func (kt *Keytab) GetEncryptionKey(princName types.PrincipalName, realm string, kvno int, etype int32) (types.EncryptionKey, error) {
55 //TODO (theme: KVNO from keytab) this function should return the kvno too
56 var key types.EncryptionKey
57 var t time.Time
58 for _, k := range kt.Entries {
59 if k.Principal.Realm == realm && len(k.Principal.Components) == len(princName.NameString) &&
60 k.Key.KeyType == etype &&
61 (k.KVNO == uint32(kvno) || kvno == 0) &&
62 k.Timestamp.After(t) {
63 p := true
64 for i, n := range k.Principal.Components {
65 if princName.NameString[i] != n {
66 p = false
67 break
68 }
69 }
70 if p {
71 key = k.Key
72 t = k.Timestamp
73 }
74 }
75 }
76 if len(key.KeyValue) < 1 {
77 return key, fmt.Errorf("matching key not found in keytab. Looking for %v realm: %v kvno: %v etype: %v", princName.NameString, realm, kvno, etype)
78 }
79 return key, nil
80}
81
82// Create a new Keytab entry.
83func newKeytabEntry() entry {
84 var b []byte
85 return entry{
86 Principal: newPrincipal(),
87 Timestamp: time.Time{},
88 KVNO8: 0,
89 Key: types.EncryptionKey{
90 KeyType: 0,
91 KeyValue: b,
92 },
93 KVNO: 0,
94 }
95}
96
97// Create a new principal.
98func newPrincipal() principal {
99 var c []string
100 return principal{
101 NumComponents: 0,
102 Realm: "",
103 Components: c,
104 NameType: 0,
105 }
106}
107
108// Load a Keytab file into a Keytab type.
109func Load(ktPath string) (*Keytab, error) {
110 kt := new(Keytab)
111 b, err := ioutil.ReadFile(ktPath)
112 if err != nil {
113 return kt, err
114 }
115 err = kt.Unmarshal(b)
116 return kt, err
117}
118
119// Marshal keytab into byte slice
120func (kt *Keytab) Marshal() ([]byte, error) {
121 b := []byte{keytabFirstByte, kt.version}
122 for _, e := range kt.Entries {
123 eb, err := e.marshal(int(kt.version))
124 if err != nil {
125 return b, err
126 }
127 b = append(b, eb...)
128 }
129 return b, nil
130}
131
132// Write the keytab bytes to io.Writer.
133// Returns the number of bytes written
134func (kt *Keytab) Write(w io.Writer) (int, error) {
135 b, err := kt.Marshal()
136 if err != nil {
137 return 0, fmt.Errorf("error marshaling keytab: %v", err)
138 }
139 return w.Write(b)
140}
141
142// Unmarshal byte slice of Keytab data into Keytab type.
143func (kt *Keytab) Unmarshal(b []byte) error {
Scott Baker105df152020-04-13 15:55:14 -0700144 if len(b) < 2 {
145 return fmt.Errorf("byte array is less than 2 bytes: %d", len(b))
146 }
147
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000148 //The first byte of the file always has the value 5
149 if b[0] != keytabFirstByte {
150 return errors.New("invalid keytab data. First byte does not equal 5")
151 }
152 //Get keytab version
153 //The 2nd byte contains the version number (1 or 2)
154 kt.version = b[1]
155 if kt.version != 1 && kt.version != 2 {
156 return errors.New("invalid keytab data. Keytab version is neither 1 nor 2")
157 }
158 //Version 1 of the file format uses native byte order for integer representations. Version 2 always uses big-endian byte order
159 var endian binary.ByteOrder
160 endian = binary.BigEndian
161 if kt.version == 1 && isNativeEndianLittle() {
162 endian = binary.LittleEndian
163 }
164 /*
165 After the two-byte version indicator, the file contains a sequence of signed 32-bit record lengths followed by key records or holes.
166 A positive record length indicates a valid key entry whose size is equal to or less than the record length.
167 A negative length indicates a zero-filled hole whose size is the inverse of the length.
168 A length of 0 indicates the end of the file.
169 */
170 // n tracks position in the byte array
171 n := 2
Scott Baker105df152020-04-13 15:55:14 -0700172 l, err := readInt32(b, &n, &endian)
173 if err != nil {
174 return err
175 }
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000176 for l != 0 {
177 if l < 0 {
178 //Zero padded so skip over
179 l = l * -1
180 n = n + int(l)
181 } else {
182 //fmt.Printf("Bytes for entry: %v\n", b[n:n+int(l)])
Scott Baker105df152020-04-13 15:55:14 -0700183 if n < 0 {
184 return fmt.Errorf("%d can't be less than zero", n)
185 }
186 if n+int(l) > len(b) {
187 return fmt.Errorf("%s's length is less than %d", b, n+int(l))
188 }
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000189 eb := b[n : n+int(l)]
190 n = n + int(l)
191 ke := newKeytabEntry()
192 // p keeps track as to where we are in the byte stream
193 var p int
Scott Baker105df152020-04-13 15:55:14 -0700194 var err error
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000195 parsePrincipal(eb, &p, kt, &ke, &endian)
Scott Baker105df152020-04-13 15:55:14 -0700196 ke.Timestamp, err = readTimestamp(eb, &p, &endian)
197 if err != nil {
198 return err
199 }
200 rei8, err := readInt8(eb, &p, &endian)
201 if err != nil {
202 return err
203 }
204 ke.KVNO8 = uint8(rei8)
205 rei16, err := readInt16(eb, &p, &endian)
206 if err != nil {
207 return err
208 }
209 ke.Key.KeyType = int32(rei16)
210 rei16, err = readInt16(eb, &p, &endian)
211 if err != nil {
212 return err
213 }
214 kl := int(rei16)
215 ke.Key.KeyValue, err = readBytes(eb, &p, kl, &endian)
216 if err != nil {
217 return err
218 }
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000219 //The 32-bit key version overrides the 8-bit key version.
220 // To determine if it is present, the implementation must check that at least 4 bytes remain in the record after the other fields are read,
221 // and that the value of the 32-bit integer contained in those bytes is non-zero.
222 if len(eb)-p >= 4 {
223 // The 32-bit key may be present
Scott Baker105df152020-04-13 15:55:14 -0700224 ri32, err := readInt32(eb, &p, &endian)
225 if err != nil {
226 return err
227 }
228 ke.KVNO = uint32(ri32)
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000229 }
230 if ke.KVNO == 0 {
231 // 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
232 ke.KVNO = uint32(ke.KVNO8)
233 }
234 // Add the entry to the keytab
235 kt.Entries = append(kt.Entries, ke)
236 }
237 // Check if there are still 4 bytes left to read
Scott Baker105df152020-04-13 15:55:14 -0700238 // Also check that n is greater than zero
239 if n < 0 || n > len(b) || len(b[n:]) < 4 {
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000240 break
241 }
242 // Read the size of the next entry
Scott Baker105df152020-04-13 15:55:14 -0700243 l, err = readInt32(b, &n, &endian)
244 if err != nil {
245 return err
246 }
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000247 }
248 return nil
249}
250
251func (e entry) marshal(v int) ([]byte, error) {
252 var b []byte
253 pb, err := e.Principal.marshal(v)
254 if err != nil {
255 return b, err
256 }
257 b = append(b, pb...)
258
259 var endian binary.ByteOrder
260 endian = binary.BigEndian
261 if v == 1 && isNativeEndianLittle() {
262 endian = binary.LittleEndian
263 }
264
265 t := make([]byte, 9)
266 endian.PutUint32(t[0:4], uint32(e.Timestamp.Unix()))
267 t[4] = e.KVNO8
268 endian.PutUint16(t[5:7], uint16(e.Key.KeyType))
269 endian.PutUint16(t[7:9], uint16(len(e.Key.KeyValue)))
270 b = append(b, t...)
271
272 buf := new(bytes.Buffer)
273 err = binary.Write(buf, endian, e.Key.KeyValue)
274 if err != nil {
275 return b, err
276 }
277 b = append(b, buf.Bytes()...)
278
279 t = make([]byte, 4)
280 endian.PutUint32(t, e.KVNO)
281 b = append(b, t...)
282
283 // Add the length header
284 t = make([]byte, 4)
285 endian.PutUint32(t, uint32(len(b)))
286 b = append(t, b...)
287 return b, nil
288}
289
290// Parse the Keytab bytes of a principal into a Keytab entry's principal.
291func parsePrincipal(b []byte, p *int, kt *Keytab, ke *entry, e *binary.ByteOrder) error {
Scott Baker105df152020-04-13 15:55:14 -0700292 var err error
293 ke.Principal.NumComponents, err = readInt16(b, p, e)
294 if err != nil {
295 return err
296 }
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000297 if kt.version == 1 {
298 //In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2
299 ke.Principal.NumComponents--
300 }
Scott Baker105df152020-04-13 15:55:14 -0700301 lenRealm, err := readInt16(b, p, e)
302 if err != nil {
303 return err
304 }
305 realmB, err := readBytes(b, p, int(lenRealm), e)
306 if err != nil {
307 return err
308 }
309 ke.Principal.Realm = string(realmB)
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000310 for i := 0; i < int(ke.Principal.NumComponents); i++ {
Scott Baker105df152020-04-13 15:55:14 -0700311 l, err := readInt16(b, p, e)
312 if err != nil {
313 return err
314 }
315 compB, err := readBytes(b, p, int(l), e)
316 if err != nil {
317 return err
318 }
319 ke.Principal.Components = append(ke.Principal.Components, string(compB))
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000320 }
321 if kt.version != 1 {
322 //Name Type is omitted in version 1
Scott Baker105df152020-04-13 15:55:14 -0700323 ke.Principal.NameType, err = readInt32(b, p, e)
324 if err != nil {
325 return err
326 }
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000327 }
328 return nil
329}
330
331func (p principal) marshal(v int) ([]byte, error) {
332 //var b []byte
333 b := make([]byte, 2)
334 var endian binary.ByteOrder
335 endian = binary.BigEndian
336 if v == 1 && isNativeEndianLittle() {
337 endian = binary.LittleEndian
338 }
339 endian.PutUint16(b[0:], uint16(p.NumComponents))
340 realm, err := marshalString(p.Realm, v)
341 if err != nil {
342 return b, err
343 }
344 b = append(b, realm...)
345 for _, c := range p.Components {
346 cb, err := marshalString(c, v)
347 if err != nil {
348 return b, err
349 }
350 b = append(b, cb...)
351 }
352 if v != 1 {
353 t := make([]byte, 4)
354 endian.PutUint32(t, uint32(p.NameType))
355 b = append(b, t...)
356 }
357 return b, nil
358}
359
360func marshalString(s string, v int) ([]byte, error) {
361 sb := []byte(s)
362 b := make([]byte, 2)
363 var endian binary.ByteOrder
364 endian = binary.BigEndian
365 if v == 1 && isNativeEndianLittle() {
366 endian = binary.LittleEndian
367 }
368 endian.PutUint16(b[0:], uint16(len(sb)))
369 buf := new(bytes.Buffer)
370 err := binary.Write(buf, endian, sb)
371 if err != nil {
372 return b, err
373 }
374 b = append(b, buf.Bytes()...)
375 return b, err
376}
377
378// Read bytes representing a timestamp.
Scott Baker105df152020-04-13 15:55:14 -0700379func readTimestamp(b []byte, p *int, e *binary.ByteOrder) (time.Time, error) {
380 i32, err := readInt32(b, p, e)
381 if err != nil {
382 return time.Time{}, err
383 }
384 return time.Unix(int64(i32), 0), nil
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000385}
386
387// Read bytes representing an eight bit integer.
Scott Baker105df152020-04-13 15:55:14 -0700388func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8, err error) {
389 if *p < 0 {
390 return 0, fmt.Errorf("%d cannot be less than zero", *p)
391 }
392
393 if (*p + 1) > len(b) {
394 return 0, fmt.Errorf("%s's length is less than %d", b, *p+1)
395 }
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000396 buf := bytes.NewBuffer(b[*p : *p+1])
397 binary.Read(buf, *e, &i)
398 *p++
399 return
400}
401
402// Read bytes representing a sixteen bit integer.
Scott Baker105df152020-04-13 15:55:14 -0700403func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16, err error) {
404 if *p < 0 {
405 return 0, fmt.Errorf("%d cannot be less than zero", *p)
406 }
407
408 if (*p + 2) > len(b) {
409 return 0, fmt.Errorf("%s's length is less than %d", b, *p+2)
410 }
411
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000412 buf := bytes.NewBuffer(b[*p : *p+2])
413 binary.Read(buf, *e, &i)
414 *p += 2
415 return
416}
417
418// Read bytes representing a thirty two bit integer.
Scott Baker105df152020-04-13 15:55:14 -0700419func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32, err error) {
420 if *p < 0 {
421 return 0, fmt.Errorf("%d cannot be less than zero", *p)
422 }
423
424 if (*p + 4) > len(b) {
425 return 0, fmt.Errorf("%s's length is less than %d", b, *p+4)
426 }
427
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000428 buf := bytes.NewBuffer(b[*p : *p+4])
429 binary.Read(buf, *e, &i)
430 *p += 4
431 return
432}
433
Scott Baker105df152020-04-13 15:55:14 -0700434func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) ([]byte, error) {
435 if s < 0 {
436 return nil, fmt.Errorf("%d cannot be less than zero", s)
437 }
438 i := *p + s
439 if i > len(b) {
440 return nil, fmt.Errorf("%s's length is greater than %d", b, i)
441 }
442 buf := bytes.NewBuffer(b[*p:i])
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000443 r := make([]byte, s)
Scott Baker105df152020-04-13 15:55:14 -0700444 if err := binary.Read(buf, *e, &r); err != nil {
445 return nil, err
446 }
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000447 *p += s
Scott Baker105df152020-04-13 15:55:14 -0700448 return r, nil
Dinesh Belwalkare63f7f92019-11-22 23:11:16 +0000449}
450
451func isNativeEndianLittle() bool {
452 var x = 0x012345678
453 var p = unsafe.Pointer(&x)
454 var bp = (*[4]byte)(p)
455
456 var endian bool
457 if 0x01 == bp[0] {
458 endian = false
459 } else if (0x78 & 0xff) == (bp[0] & 0xff) {
460 endian = true
461 } else {
462 // Default to big endian
463 endian = false
464 }
465 return endian
466}