... |
... |
@@ -13,7 +13,7 @@ import yaml
|
13 |
13 |
import struct
|
14 |
14 |
import argparse
|
15 |
15 |
from datetime import datetime
|
16 |
|
-from binascii import hexlify
|
|
16 |
+from binascii import hexlify, unhexlify
|
17 |
17 |
|
18 |
18 |
# Documents used as reference to implement the keytab format:
|
19 |
19 |
# [1] https://web.mit.edu/kerberos/krb5-1.12/doc/formats/keytab_file_format.html
|
... |
... |
@@ -91,6 +91,9 @@ class KeytabParsingError(Exception):
|
91 |
91 |
def __str__(self):
|
92 |
92 |
return __class__.MESSAGE.format(**self.__dict__, length=len(self.data))
|
93 |
93 |
|
|
94 |
+class KeytabComposingError(Exception):
|
|
95 |
+ pass
|
|
96 |
+
|
94 |
97 |
def lookup(lookup_value, dictionary, default):
|
95 |
98 |
for name, value in dictionary.items():
|
96 |
99 |
if value == lookup_value:
|
... |
... |
@@ -302,9 +305,97 @@ def keytab_to_yaml(buf, args):
|
302 |
305 |
else:
|
303 |
306 |
json.dump(final_keytab, sys.stdout, indent=4)
|
304 |
307 |
|
305 |
|
-def yaml_to_keytab(fd):
|
306 |
|
- data = yaml.load(fd.read(), Loader=yaml.SafeLoader)
|
307 |
|
- print('YAML:', data)
|
|
308 |
+def pack_data(data):
|
|
309 |
+ return struct.pack('>H', len(data)) + data
|
|
310 |
+
|
|
311 |
+def pack_str(string, encoding=DEFAULT_ENCODING):
|
|
312 |
+ return pack_data(string.encode(encoding))
|
|
313 |
+
|
|
314 |
+def raw_principal_to_binary(principal, index):
|
|
315 |
+ for key in ('realm', 'components'):
|
|
316 |
+ if key not in principal:
|
|
317 |
+ raise KeytabComposingError('Mandatory key "%s" not found in principal #%d' % (key, index))
|
|
318 |
+ name_type_raw = principal.get('name_type_raw', NAME_TYPES['KRB5_NT_PRINCIPAL'])
|
|
319 |
+ try:
|
|
320 |
+ _ = int(name_type_raw)
|
|
321 |
+ except:
|
|
322 |
+ raise KeytabComposingError('invalid name_type_raw value in principal #%d' % index)
|
|
323 |
+ data = struct.pack('>H', len(principal['components']))
|
|
324 |
+ data += pack_str(principal['realm'])
|
|
325 |
+ for component in principal['components']:
|
|
326 |
+ data += pack_str(component)
|
|
327 |
+ data += struct.pack('>i', name_type_raw)
|
|
328 |
+ return data
|
|
329 |
+
|
|
330 |
+def raw_entry_to_binary(entry, index):
|
|
331 |
+ for key in ('principal', 'timestamp', 'kvno', 'enctype_raw', 'key'):
|
|
332 |
+ if key not in entry:
|
|
333 |
+ raise KeytabComposingError('Mandatory key "%s" not found in entry #%d' % (key, index))
|
|
334 |
+ for key in ('timestamp', 'kvno', 'enctype_raw'):
|
|
335 |
+ try:
|
|
336 |
+ assert(int(entry[key]) >= 0)
|
|
337 |
+ except:
|
|
338 |
+ raise KeytabComposingError('invalid %s data in entry #%d' % (key, index))
|
|
339 |
+ data = raw_principal_to_binary(entry['principal'], index)
|
|
340 |
+ try:
|
|
341 |
+ key_data = unhexlify(entry['key'])
|
|
342 |
+ except:
|
|
343 |
+ raise KeytabComposingError('invalid key data in entry #%d' % index)
|
|
344 |
+ data += struct.pack('>IBHH', entry['timestamp'], entry['kvno'], entry['enctype_raw'], len(key_data))
|
|
345 |
+ data += key_data
|
|
346 |
+ return data
|
|
347 |
+
|
|
348 |
+def raw_record_to_binary(record, index):
|
|
349 |
+ data = b''
|
|
350 |
+ if 'entry' not in record:
|
|
351 |
+ raise KeytabComposingError('missing entry in record #%d' % index)
|
|
352 |
+ data += raw_entry_to_binary(record['entry'], index)
|
|
353 |
+ if 'tail' in record:
|
|
354 |
+ try:
|
|
355 |
+ data += unhexlify(record['tail'])
|
|
356 |
+ except:
|
|
357 |
+ raise KeytabComposingError('invalid tail data in record #%d' % index)
|
|
358 |
+ return struct.pack('>i', len(data)) + data
|
|
359 |
+
|
|
360 |
+def raw_hole_to_binary(hole, index):
|
|
361 |
+ data = b''
|
|
362 |
+ if 'length' not in record:
|
|
363 |
+ raise KeytabComposingError('missing length in record #%d' % index)
|
|
364 |
+ try:
|
|
365 |
+ length = abs(int(record['length']))
|
|
366 |
+ if not length:
|
|
367 |
+ raise KeytabComposingError('illegal zero-length hole in record #%d' % index)
|
|
368 |
+ data += struct.pack('>i', -length)
|
|
369 |
+ except:
|
|
370 |
+ raise KeytabComposingError('invalid length in record #%d' % index)
|
|
371 |
+ if 'data' in record:
|
|
372 |
+ try:
|
|
373 |
+ hole_data = unhexlify(record['data'])
|
|
374 |
+ except:
|
|
375 |
+ raise KeytabComposingError('invalid data in record #%d' % index)
|
|
376 |
+ if len(hole_data) != length:
|
|
377 |
+ raise KeytabComposingError('length and data do not match in record #%d' % index)
|
|
378 |
+ data += hole_data
|
|
379 |
+ else:
|
|
380 |
+ data += b'\x00' * length
|
|
381 |
+ return data
|
|
382 |
+
|
|
383 |
+def raw_keytab_to_binary(indata):
|
|
384 |
+ data = bytes([KEYTAB_FIRST_BYTE, 2])
|
|
385 |
+ for index, record in enumerate(indata.get('records', [])):
|
|
386 |
+ record_type = record.get('type', 'record')
|
|
387 |
+ if record_type == 'hole':
|
|
388 |
+ data += raw_hole_to_binary(record, index)
|
|
389 |
+ elif record_type == 'record':
|
|
390 |
+ data += raw_record_to_binary(record, index)
|
|
391 |
+ else:
|
|
392 |
+ raise KeytabComposingError('Unknown record type in record #%d' % index)
|
|
393 |
+ return data
|
|
394 |
+
|
|
395 |
+def yaml_to_keytab(buf, args):
|
|
396 |
+ data = yaml.load(buf, Loader=yaml.SafeLoader)
|
|
397 |
+ result = raw_keytab_to_binary(data)
|
|
398 |
+ sys.stdout.buffer.write(result)
|
308 |
399 |
|
309 |
400 |
def parse_args():
|
310 |
401 |
parser = argparse.ArgumentParser(description='Keytab <-> YAML/JSON convertor.')
|