Browse code

Implement conversion from YAML/JSON to keytab.

This first implementation only supports raw and full YAML/JSON data lyaout.

Xavier G authored on26/04/2020 12:20:12
Showing2 changed files

... ...
@@ -1 +1 @@
1
-yamltab converts keytabs to YAML/JSON.
1
+yamltab converts keytabs to YAML/JSON and the other way around.
... ...
@@ -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.')