blob: 5c2e9d79a1b930626036e09c9f762bbdc792bcc8 [file] [log] [blame]
khenaidoo7d3c5582021-08-11 18:09:44 -04001// 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 "encoding/json"
8 "errors"
9 "fmt"
10 "io"
11 "io/ioutil"
12 "strings"
13 "time"
14 "unsafe"
15
16 "github.com/jcmturner/gokrb5/v8/crypto"
17 "github.com/jcmturner/gokrb5/v8/types"
18)
19
20const (
21 keytabFirstByte byte = 05
22)
23
24// Keytab struct.
25type Keytab struct {
26 version uint8
27 Entries []entry
28}
29
30// Keytab entry struct.
31type entry struct {
32 Principal principal
33 Timestamp time.Time
34 KVNO8 uint8
35 Key types.EncryptionKey
36 KVNO uint32
37}
38
39func (e entry) String() string {
40 return fmt.Sprintf("% 4d %s %-56s %2d %-64x",
41 e.KVNO8,
42 e.Timestamp.Format("02/01/06 15:04:05"),
43 e.Principal.String(),
44 e.Key.KeyType,
45 e.Key.KeyValue,
46 )
47}
48
49// Keytab entry principal struct.
50type principal struct {
51 NumComponents int16 `json:"-"`
52 Realm string
53 Components []string
54 NameType int32
55}
56
57func (p principal) String() string {
58 return fmt.Sprintf("%s@%s", strings.Join(p.Components, "/"), p.Realm)
59}
60
61// New creates new, empty Keytab type.
62func New() *Keytab {
63 var e []entry
64 return &Keytab{
65 version: 2,
66 Entries: e,
67 }
68}
69
70// GetEncryptionKey returns the EncryptionKey from the Keytab for the newest entry with the required kvno, etype and matching principal.
71// If the kvno is zero then the latest kvno will be returned. The kvno is also returned for
72func (kt *Keytab) GetEncryptionKey(princName types.PrincipalName, realm string, kvno int, etype int32) (types.EncryptionKey, int, error) {
73 var key types.EncryptionKey
74 var t time.Time
75 var kv int
76 for _, k := range kt.Entries {
77 if k.Principal.Realm == realm && len(k.Principal.Components) == len(princName.NameString) &&
78 k.Key.KeyType == etype &&
79 (k.KVNO == uint32(kvno) || kvno == 0) &&
80 k.Timestamp.After(t) {
81 p := true
82 for i, n := range k.Principal.Components {
83 if princName.NameString[i] != n {
84 p = false
85 break
86 }
87 }
88 if p {
89 key = k.Key
90 kv = int(k.KVNO)
91 t = k.Timestamp
92 }
93 }
94 }
95 if len(key.KeyValue) < 1 {
96 return key, 0, fmt.Errorf("matching key not found in keytab. Looking for %v realm: %v kvno: %v etype: %v", princName.NameString, realm, kvno, etype)
97 }
98 return key, kv, nil
99}
100
101// Create a new Keytab entry.
102func newEntry() entry {
103 var b []byte
104 return entry{
105 Principal: newPrincipal(),
106 Timestamp: time.Time{},
107 KVNO8: 0,
108 Key: types.EncryptionKey{
109 KeyType: 0,
110 KeyValue: b,
111 },
112 KVNO: 0,
113 }
114}
115
116func (kt Keytab) String() string {
117 var s string
118 s = `KVNO Timestamp Principal ET Key
119---- ----------------- -------------------------------------------------------- -- ----------------------------------------------------------------
120`
121 for _, entry := range kt.Entries {
122 s += entry.String() + "\n"
123 }
124 return s
125}
126
127// 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.
128func (kt *Keytab) AddEntry(principalName, realm, password string, ts time.Time, KVNO uint8, encType int32) error {
129 // Generate a key from the password
130 princ, _ := types.ParseSPNString(principalName)
131 key, _, err := crypto.GetKeyFromPassword(password, princ, realm, encType, types.PADataSequence{})
132 if err != nil {
133 return err
134 }
135
136 // Populate the keytab entry principal
137 ktep := newPrincipal()
138 ktep.NumComponents = int16(len(princ.NameString))
139 if kt.version == 1 {
140 ktep.NumComponents += 1
141 }
142
143 ktep.Realm = realm
144 ktep.Components = princ.NameString
145 ktep.NameType = princ.NameType
146
147 // Populate the keytab entry
148 e := newEntry()
149 e.Principal = ktep
150 e.Timestamp = ts
151 e.KVNO8 = KVNO
152 e.KVNO = uint32(KVNO)
153 e.Key = key
154
155 kt.Entries = append(kt.Entries, e)
156 return nil
157}
158
159// Create a new principal.
160func newPrincipal() principal {
161 var c []string
162 return principal{
163 NumComponents: 0,
164 Realm: "",
165 Components: c,
166 NameType: 0,
167 }
168}
169
170// Load a Keytab file into a Keytab type.
171func Load(ktPath string) (*Keytab, error) {
172 kt := new(Keytab)
173 b, err := ioutil.ReadFile(ktPath)
174 if err != nil {
175 return kt, err
176 }
177 err = kt.Unmarshal(b)
178 return kt, err
179}
180
181// Marshal keytab into byte slice
182func (kt *Keytab) Marshal() ([]byte, error) {
183 b := []byte{keytabFirstByte, kt.version}
184 for _, e := range kt.Entries {
185 eb, err := e.marshal(int(kt.version))
186 if err != nil {
187 return b, err
188 }
189 b = append(b, eb...)
190 }
191 return b, nil
192}
193
194// Write the keytab bytes to io.Writer.
195// Returns the number of bytes written
196func (kt *Keytab) Write(w io.Writer) (int, error) {
197 b, err := kt.Marshal()
198 if err != nil {
199 return 0, fmt.Errorf("error marshaling keytab: %v", err)
200 }
201 return w.Write(b)
202}
203
204// Unmarshal byte slice of Keytab data into Keytab type.
205func (kt *Keytab) Unmarshal(b []byte) error {
206 if len(b) < 2 {
207 return fmt.Errorf("byte array is less than 2 bytes: %d", len(b))
208 }
209
210 //The first byte of the file always has the value 5
211 if b[0] != keytabFirstByte {
212 return errors.New("invalid keytab data. First byte does not equal 5")
213 }
214 //Get keytab version
215 //The 2nd byte contains the version number (1 or 2)
216 kt.version = b[1]
217 if kt.version != 1 && kt.version != 2 {
218 return errors.New("invalid keytab data. Keytab version is neither 1 nor 2")
219 }
220 //Version 1 of the file format uses native byte order for integer representations. Version 2 always uses big-endian byte order
221 var endian binary.ByteOrder
222 endian = binary.BigEndian
223 if kt.version == 1 && isNativeEndianLittle() {
224 endian = binary.LittleEndian
225 }
226 // n tracks position in the byte array
227 n := 2
228 l, err := readInt32(b, &n, &endian)
229 if err != nil {
230 return err
231 }
232 for l != 0 {
233 if l < 0 {
234 //Zero padded so skip over
235 l = l * -1
236 n = n + int(l)
237 } else {
238 if n < 0 {
239 return fmt.Errorf("%d can't be less than zero", n)
240 }
241 if n+int(l) > len(b) {
242 return fmt.Errorf("%s's length is less than %d", b, n+int(l))
243 }
244 eb := b[n : n+int(l)]
245 n = n + int(l)
246 ke := newEntry()
247 // p keeps track as to where we are in the byte stream
248 var p int
249 var err error
250 parsePrincipal(eb, &p, kt, &ke, &endian)
251 ke.Timestamp, err = readTimestamp(eb, &p, &endian)
252 if err != nil {
253 return err
254 }
255 rei8, err := readInt8(eb, &p, &endian)
256 if err != nil {
257 return err
258 }
259 ke.KVNO8 = uint8(rei8)
260 rei16, err := readInt16(eb, &p, &endian)
261 if err != nil {
262 return err
263 }
264 ke.Key.KeyType = int32(rei16)
265 rei16, err = readInt16(eb, &p, &endian)
266 if err != nil {
267 return err
268 }
269 kl := int(rei16)
270 ke.Key.KeyValue, err = readBytes(eb, &p, kl, &endian)
271 if err != nil {
272 return err
273 }
274 // The 32-bit key version overrides the 8-bit key version.
275 // If at least 4 bytes are left after the other fields are read and they are non-zero
276 // this indicates the 32-bit version is present.
277 if len(eb)-p >= 4 {
278 // The 32-bit key may be present
279 ri32, err := readInt32(eb, &p, &endian)
280 if err != nil {
281 return err
282 }
283 ke.KVNO = uint32(ri32)
284 }
285 if ke.KVNO == 0 {
286 // 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
287 ke.KVNO = uint32(ke.KVNO8)
288 }
289 // Add the entry to the keytab
290 kt.Entries = append(kt.Entries, ke)
291 }
292 // Check if there are still 4 bytes left to read
293 // Also check that n is greater than zero
294 if n < 0 || n > len(b) || len(b[n:]) < 4 {
295 break
296 }
297 // Read the size of the next entry
298 l, err = readInt32(b, &n, &endian)
299 if err != nil {
300 return err
301 }
302 }
303 return nil
304}
305
306func (e entry) marshal(v int) ([]byte, error) {
307 var b []byte
308 pb, err := e.Principal.marshal(v)
309 if err != nil {
310 return b, err
311 }
312 b = append(b, pb...)
313
314 var endian binary.ByteOrder
315 endian = binary.BigEndian
316 if v == 1 && isNativeEndianLittle() {
317 endian = binary.LittleEndian
318 }
319
320 t := make([]byte, 9)
321 endian.PutUint32(t[0:4], uint32(e.Timestamp.Unix()))
322 t[4] = e.KVNO8
323 endian.PutUint16(t[5:7], uint16(e.Key.KeyType))
324 endian.PutUint16(t[7:9], uint16(len(e.Key.KeyValue)))
325 b = append(b, t...)
326
327 buf := new(bytes.Buffer)
328 err = binary.Write(buf, endian, e.Key.KeyValue)
329 if err != nil {
330 return b, err
331 }
332 b = append(b, buf.Bytes()...)
333
334 t = make([]byte, 4)
335 endian.PutUint32(t, e.KVNO)
336 b = append(b, t...)
337
338 // Add the length header
339 t = make([]byte, 4)
340 endian.PutUint32(t, uint32(len(b)))
341 b = append(t, b...)
342 return b, nil
343}
344
345// Parse the Keytab bytes of a principal into a Keytab entry's principal.
346func parsePrincipal(b []byte, p *int, kt *Keytab, ke *entry, e *binary.ByteOrder) error {
347 var err error
348 ke.Principal.NumComponents, err = readInt16(b, p, e)
349 if err != nil {
350 return err
351 }
352 if kt.version == 1 {
353 //In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2
354 ke.Principal.NumComponents--
355 }
356 lenRealm, err := readInt16(b, p, e)
357 if err != nil {
358 return err
359 }
360 realmB, err := readBytes(b, p, int(lenRealm), e)
361 if err != nil {
362 return err
363 }
364 ke.Principal.Realm = string(realmB)
365 for i := 0; i < int(ke.Principal.NumComponents); i++ {
366 l, err := readInt16(b, p, e)
367 if err != nil {
368 return err
369 }
370 compB, err := readBytes(b, p, int(l), e)
371 if err != nil {
372 return err
373 }
374 ke.Principal.Components = append(ke.Principal.Components, string(compB))
375 }
376 if kt.version != 1 {
377 //Name Type is omitted in version 1
378 ke.Principal.NameType, err = readInt32(b, p, e)
379 if err != nil {
380 return err
381 }
382 }
383 return nil
384}
385
386func (p principal) marshal(v int) ([]byte, error) {
387 //var b []byte
388 b := make([]byte, 2)
389 var endian binary.ByteOrder
390 endian = binary.BigEndian
391 if v == 1 && isNativeEndianLittle() {
392 endian = binary.LittleEndian
393 }
394 endian.PutUint16(b[0:], uint16(p.NumComponents))
395 realm, err := marshalString(p.Realm, v)
396 if err != nil {
397 return b, err
398 }
399 b = append(b, realm...)
400 for _, c := range p.Components {
401 cb, err := marshalString(c, v)
402 if err != nil {
403 return b, err
404 }
405 b = append(b, cb...)
406 }
407 if v != 1 {
408 t := make([]byte, 4)
409 endian.PutUint32(t, uint32(p.NameType))
410 b = append(b, t...)
411 }
412 return b, nil
413}
414
415func marshalString(s string, v int) ([]byte, error) {
416 sb := []byte(s)
417 b := make([]byte, 2)
418 var endian binary.ByteOrder
419 endian = binary.BigEndian
420 if v == 1 && isNativeEndianLittle() {
421 endian = binary.LittleEndian
422 }
423 endian.PutUint16(b[0:], uint16(len(sb)))
424 buf := new(bytes.Buffer)
425 err := binary.Write(buf, endian, sb)
426 if err != nil {
427 return b, err
428 }
429 b = append(b, buf.Bytes()...)
430 return b, err
431}
432
433// Read bytes representing a timestamp.
434func readTimestamp(b []byte, p *int, e *binary.ByteOrder) (time.Time, error) {
435 i32, err := readInt32(b, p, e)
436 if err != nil {
437 return time.Time{}, err
438 }
439 return time.Unix(int64(i32), 0), nil
440}
441
442// Read bytes representing an eight bit integer.
443func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8, err error) {
444 if *p < 0 {
445 return 0, fmt.Errorf("%d cannot be less than zero", *p)
446 }
447
448 if (*p + 1) > len(b) {
449 return 0, fmt.Errorf("%s's length is less than %d", b, *p+1)
450 }
451 buf := bytes.NewBuffer(b[*p : *p+1])
452 binary.Read(buf, *e, &i)
453 *p++
454 return
455}
456
457// Read bytes representing a sixteen bit integer.
458func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16, err error) {
459 if *p < 0 {
460 return 0, fmt.Errorf("%d cannot be less than zero", *p)
461 }
462
463 if (*p + 2) > len(b) {
464 return 0, fmt.Errorf("%s's length is less than %d", b, *p+2)
465 }
466
467 buf := bytes.NewBuffer(b[*p : *p+2])
468 binary.Read(buf, *e, &i)
469 *p += 2
470 return
471}
472
473// Read bytes representing a thirty two bit integer.
474func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32, err error) {
475 if *p < 0 {
476 return 0, fmt.Errorf("%d cannot be less than zero", *p)
477 }
478
479 if (*p + 4) > len(b) {
480 return 0, fmt.Errorf("%s's length is less than %d", b, *p+4)
481 }
482
483 buf := bytes.NewBuffer(b[*p : *p+4])
484 binary.Read(buf, *e, &i)
485 *p += 4
486 return
487}
488
489func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) ([]byte, error) {
490 if s < 0 {
491 return nil, fmt.Errorf("%d cannot be less than zero", s)
492 }
493 i := *p + s
494 if i > len(b) {
495 return nil, fmt.Errorf("%s's length is greater than %d", b, i)
496 }
497 buf := bytes.NewBuffer(b[*p:i])
498 r := make([]byte, s)
499 if err := binary.Read(buf, *e, &r); err != nil {
500 return nil, err
501 }
502 *p += s
503 return r, nil
504}
505
506func isNativeEndianLittle() bool {
507 var x = 0x012345678
508 var p = unsafe.Pointer(&x)
509 var bp = (*[4]byte)(p)
510
511 var endian bool
512 if 0x01 == bp[0] {
513 endian = false
514 } else if (0x78 & 0xff) == (bp[0] & 0xff) {
515 endian = true
516 } else {
517 // Default to big endian
518 endian = false
519 }
520 return endian
521}
522
523// JSON return information about the keys held in the keytab in a JSON format.
524func (kt *Keytab) JSON() (string, error) {
525 b, err := json.MarshalIndent(kt, "", " ")
526 if err != nil {
527 return "", err
528 }
529 return string(b), nil
530}