... |
... |
@@ -7,6 +7,7 @@
|
7 |
7 |
|
8 |
8 |
from io import BufferedReader, BytesIO
|
9 |
9 |
import os
|
|
10 |
+import re
|
10 |
11 |
import sys
|
11 |
12 |
import json
|
12 |
13 |
import yaml
|
... |
... |
@@ -398,8 +399,93 @@ def raw_keytab_to_binary(indata):
|
398 |
399 |
raise KeytabComposingError('Unknown record type in record #%d' % index)
|
399 |
400 |
return data
|
400 |
401 |
|
|
402 |
+def spn_to_principal(spn, name_type='KRB5_NT_PRINCIPAL'):
|
|
403 |
+ principal = {'name_type': name_type}
|
|
404 |
+ principal['name_type_raw'] = NAME_TYPES[name_type]
|
|
405 |
+ rem = re.match(r'((?P<comp1>[^/]+)/)?(?P<comp2>[^:]+)(?::(?P<comp3>[^@]+))?@(?P<realm>.+)', spn)
|
|
406 |
+ if not rem:
|
|
407 |
+ raise KeytabComposingError('Cannot parse SPN %s into principal' % spn)
|
|
408 |
+ principal['realm'] = rem.group('realm')
|
|
409 |
+ principal['components'] = []
|
|
410 |
+ for name, component in rem.groupdict():
|
|
411 |
+ if name.startswith('comp') and component is not None:
|
|
412 |
+ principal['components'].append(component)
|
|
413 |
+ return principal
|
|
414 |
+
|
|
415 |
+def simple_principal_to_full(inentry, index, entry):
|
|
416 |
+ if 'principal' in inentry:
|
|
417 |
+ principal = entry['principal'] = inentry['principal']
|
|
418 |
+ if 'name_type_raw' not in principal:
|
|
419 |
+ try:
|
|
420 |
+ principal['name_type_raw'] = NAME_TYPES[principal['name_type']]
|
|
421 |
+ except KeyError:
|
|
422 |
+ message = 'Invalid or unknown name_type specified in entry #%d'
|
|
423 |
+ message += '; use name_type_raw to enforce an arbitrary value'
|
|
424 |
+ raise KeytabParsingError(message % index)
|
|
425 |
+ elif 'spn' in inentry:
|
|
426 |
+ entry['principal'] = spn_to_principal(inentry['spn'])
|
|
427 |
+
|
|
428 |
+def simple_kvno_to_full(inentry, index, entry, record, kvno_in_tail=False):
|
|
429 |
+ if 'kvno' in inentry:
|
|
430 |
+ entry['actual_kvno'] = inentry['kvno']
|
|
431 |
+ kvno_too_big = inentry['kvno'] > 255
|
|
432 |
+ entry['kvno'] = 0 if kvno_too_big else inentry['kvno']
|
|
433 |
+ if kvno_in_tail:
|
|
434 |
+ entry['tail_kvno'] = inentry['kvno']
|
|
435 |
+ record['tail'] = hexlify(struct.pack('>I', inentry['kvno']))
|
|
436 |
+ elif kvno_too_big:
|
|
437 |
+ message = 'Cannot store kvno > 255 without kvno_in_tail in entry #%d'
|
|
438 |
+ raise KeytabComposingError(message % index)
|
|
439 |
+
|
|
440 |
+def simple_timestamp_to_full(inentry, index, entry):
|
|
441 |
+ if 'timestamp' in inentry:
|
|
442 |
+ entry['timestamp'] = inentry['timestamp']
|
|
443 |
+ elif 'date' in inentry:
|
|
444 |
+ if inentry['date'] == 'now':
|
|
445 |
+ entry['timestamp'] = now
|
|
446 |
+ else:
|
|
447 |
+ try:
|
|
448 |
+ parsed_date = datetime.strptime(inentry['date'], DATE_TIME_FORMAT)
|
|
449 |
+ entry['timestamp'] = int(parsed_date.timestamp())
|
|
450 |
+ except:
|
|
451 |
+ raise KeytabParsingError('Invalid date specified in entry #%d' % index)
|
|
452 |
+
|
|
453 |
+def simple_enctype_to_full(inentry, index, entry):
|
|
454 |
+ if 'enctype_raw' in inentry:
|
|
455 |
+ entry['enctype_raw'] = inentry['enctype_raw']
|
|
456 |
+ elif 'enctype' in inentry:
|
|
457 |
+ try:
|
|
458 |
+ entry['enctype_raw'] = ENC_TYPES[inentry['enctype']]
|
|
459 |
+ except KeyError:
|
|
460 |
+ message = 'Invalid or unknown enctype specified in entry #%d'
|
|
461 |
+ message += '; use enctype_raw to enforce an arbitrary value'
|
|
462 |
+ raise KeytabParsingError(message % index)
|
|
463 |
+
|
|
464 |
+def simple_keytab_to_full(indata):
|
|
465 |
+ now = int(datetime.now().timestamp())
|
|
466 |
+ data = {
|
|
467 |
+ 'version': indata.get('version', 2),
|
|
468 |
+ 'kvno_in_tail': indata.get('kvno_in_tail', False),
|
|
469 |
+ 'records': [],
|
|
470 |
+ }
|
|
471 |
+ for index, inentry in enumerate(indata.get('entries', [])):
|
|
472 |
+ entry = {}
|
|
473 |
+ record = {'entry': entry}
|
|
474 |
+ simple_principal_to_full(inentry, index, entry)
|
|
475 |
+ simple_kvno_to_full(inentry, index, entry, record, data['kvno_in_tail'])
|
|
476 |
+ simple_timestamp_to_full(inentry, index, entry)
|
|
477 |
+ simple_enctype_to_full(inentry, index, entry)
|
|
478 |
+ if 'key' in inentry:
|
|
479 |
+ entry['key'] = inentry['key']
|
|
480 |
+ data['records'].append(record)
|
|
481 |
+ return data
|
|
482 |
+
|
401 |
483 |
def yaml_to_keytab(buf, args):
|
402 |
484 |
data = yaml.load(buf, Loader=yaml.SafeLoader)
|
|
485 |
+ if 'entries' in data:
|
|
486 |
+ # If the provided structure exposes "entries" rather than "records",
|
|
487 |
+ # then it very likely features the "simple" data layout.
|
|
488 |
+ data = simple_keytab_to_full(data)
|
403 |
489 |
result = raw_keytab_to_binary(data)
|
404 |
490 |
sys.stdout.buffer.write(result)
|
405 |
491 |
|