diff options
author | Greg Hudson <ghudson@mit.edu> | 2013-10-23 11:17:00 -0400 |
---|---|---|
committer | Greg Hudson <ghudson@mit.edu> | 2013-10-25 11:36:12 -0400 |
commit | 3c56f1f8584e4e170b3bc19f077d18c5c5acfd38 (patch) | |
tree | a17846b0f2a9e5f7e3c143d7b5ec76643ef3de98 /src/tests | |
parent | ec560fac83912abaa15fb158101c8174497081c5 (diff) | |
download | krb5-3c56f1f8584e4e170b3bc19f077d18c5c5acfd38.zip krb5-3c56f1f8584e4e170b3bc19f077d18c5c5acfd38.tar.gz krb5-3c56f1f8584e4e170b3bc19f077d18c5c5acfd38.tar.bz2 |
Add master key rollover tests in k5test framework
Add a new script t_mkey.py using the k5test framework. Test the fixes
for #6507, #7685, and #7686 as well as basic functionality and
old-stashfile compatibility.
dump.16 was created by running "kdb5_util create -s -P footes" and
"kdb5_util dump dumpfile" with krb5 1.6. The key from the resulting
stash file was extracted and placed in the struct.pack() call in the
new test script.
Diffstat (limited to 'src/tests')
-rw-r--r-- | src/tests/Makefile.in | 1 | ||||
-rw-r--r-- | src/tests/dumpfiles/dump.16 | 7 | ||||
-rw-r--r-- | src/tests/t_mkey.py | 307 |
3 files changed, 315 insertions, 0 deletions
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in index d50d513..ac944a9 100644 --- a/src/tests/Makefile.in +++ b/src/tests/Makefile.in @@ -114,6 +114,7 @@ check-pytests:: t_init_creds t_localauth $(RUNPYTEST) $(srcdir)/t_kadmin_acl.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_kdb.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_keydata.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_mkey.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_cve-2012-1014.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_cve-2012-1015.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_cve-2013-1416.py $(PYTESTFLAGS) diff --git a/src/tests/dumpfiles/dump.16 b/src/tests/dumpfiles/dump.16 new file mode 100644 index 0000000..bea2ff6 --- /dev/null +++ b/src/tests/dumpfiles/dump.16 @@ -0,0 +1,7 @@ +kdb5_util load_dump version 5 +princ 38 15 1 1 0 K/M@KRBTEST.COM 64 36000 604800 0 0 0 0 0 2 28 18de675264625f6372656174696f6e404b5242544553542e434f4d00 1 1 16 54 180001361a6c58b3a273e484574c6c5739fb114ffe7d2298e767b545f332e3bda573021c97728028e7ec4942708f23f4445d4419f4ad -1; +princ 38 24 3 2 0 kadmin/admin@KRBTEST.COM 4 10800 604800 0 0 0 0 0 3 24 12345c010000000000000000000000000000000200000000 2 26 18de67526b6462355f7574696c404b5242544553542e434f4d00 1 4 18de6752 1 2 16 54 1800555ff1892cdb7dd7c62b659f9659981205ebb4f9fd4446e5243f58dfc0b99a4f096080d435702e052793a90c66aea062c8b8964e 1 2 1 38 0800ca62598d281381a22fc6d5bdf25653002b6afeb8f24af6ea01c9301359527304a08bb8f5 -1; +princ 38 27 3 2 0 kadmin/changepw@KRBTEST.COM 8196 300 604800 0 0 0 0 0 3 24 12345c010000000000000000000000000000000200000000 2 26 18de67526b6462355f7574696c404b5242544553542e434f4d00 1 4 18de6752 1 2 16 54 1800237a5df1fc3295e08ba18986f51f553c2d0dfff3fb8e17d19ac7777a1cd516713e5496521ba362261ab61c063090705ecf7bca01 1 2 1 38 0800685d1f82188b712b2947b63a259269b1fe53bf383bb05cdc802e2cb9d680631e512af4d4 -1; +princ 38 38 3 2 0 kadmin/equal-rites.mit.edu@KRBTEST.COM 4 10800 604800 0 0 0 0 0 3 24 12345c010000000000000000000000000000000200000000 2 26 18de67526b6462355f7574696c404b5242544553542e434f4d00 1 4 18de6752 1 2 16 54 1800ed9ad2e91940a41abc9b05d7dfb736c353e3ff18272b1d3bc31e5ebc3204e15d5fd9c3caa0c57be4736831b6f03c1741d5423ff1 1 2 1 38 0800eb82c30aa8e1f5a10f0f099372e2ff3385cb5437b41abee02491673cf45c79b2c4466364 -1; +princ 38 26 3 1 0 kadmin/history@KRBTEST.COM 0 64 604800 0 0 0 0 0 3 24 12345c010000000000000000000000000000000200000000 2 26 18de67526b6462355f7574696c404b5242544553542e434f4d00 1 4 18de6752 1 2 16 54 180070b4f132c63aa7d8b91f1323daa8d59bf2e04663b9b8931bcbe9a953c4fab6dda7c820db0cf9922af5ebfb7bd09101849e81f054 -1; +princ 38 30 1 2 0 krbtgt/KRBTEST.COM@KRBTEST.COM 0 36000 604800 0 0 0 0 0 2 28 18de675264625f6372656174696f6e404b5242544553542e434f4d00 1 1 16 54 1800010572e45515fc5ee028a59a48bc86896ab5014c265304b2340d37a46c0185312e475e9245e70df0f6874c9348a2ca4389c7168f 1 1 1 38 0800d8b88190a5e7f49cdca68c0e018bafd971f1606f9af2f2e3d9e31c69071556896443ade2 -1; diff --git a/src/tests/t_mkey.py b/src/tests/t_mkey.py new file mode 100644 index 0000000..35d14f7 --- /dev/null +++ b/src/tests/t_mkey.py @@ -0,0 +1,307 @@ +#!/usr/bin/python +from k5test import * +import random +import re +import struct + +# Convenience constants for use as expected enctypes. defetype is the +# default enctype for master keys. +aes256 = 'aes256-cts-hmac-sha1-96' +aes128 = 'aes128-cts-hmac-sha1-96' +des3 = 'des3-cbc-sha1' +defetype = aes256 + +realm = K5Realm(create_host=False, start_kadmind=True) +realm.prep_kadmin() +stash_file = os.path.join(realm.testdir, 'stash') + +# Count the number of principals in the realm. +nprincs = len(realm.run_kadminl('listprincs').splitlines()) - 1 + +# List the currently active mkeys and compare against expected +# results. Each argument must be a sequence of four elements: an +# expected kvno, an expected enctype, whether the key is expected to +# have an activation time, and whether the key is expected to be +# currently active. +list_mkeys_re = re.compile(r'^KVNO: (\d+), Enctype: (\S+), ' + '(Active on: [^\*]+|No activate time set)( \*)?$') +def check_mkey_list(*expected): + # Split the output of kdb5_util list_mkeys into lines and ignore the first. + outlines = realm.run([kdb5_util, 'list_mkeys']).splitlines()[1:] + if len(outlines) != len(expected): + fail('Unexpected number of list_mkeys output lines') + for line, ex in zip(outlines, expected): + m = list_mkeys_re.match(line) + if not m: + fail('Unrecognized list_mkeys output line') + kvno, enctype, act_time, active = m.groups() + exp_kvno, exp_enctype, exp_act_time_present, exp_active = ex + if kvno != str(exp_kvno): + fail('Unexpected master key version') + if enctype != exp_enctype: + fail('Unexpected master key enctype') + if act_time.startswith('Active on: ') != exp_act_time_present: + fail('Unexpected presence or absence of mkey activation time') + if (active == ' *') != exp_active: + fail('Master key unexpectedly active or inactive') + + +# Get the K/M principal. Verify that it has the expected mkvno. Each +# remaining argment must be a sequence of two elements: an expected +# key version and an expected enctype. +keyline_re = re.compile(r'^Key: vno (\d+), (\S+), ') +def check_master_dbent(expected_mkvno, *expected_keys): + outlines = realm.run_kadminl('getprinc K/M').splitlines() + mkeyline = [l for l in outlines if l.startswith('MKey: vno ')] + if len(mkeyline) != 1 or mkeyline[0] != ('MKey: vno %d' % expected_mkvno): + fail('Unexpected mkvno in K/M DB entry') + keylines = [l for l in outlines if l.startswith('Key: vno ')] + if len(keylines) != len(expected_keys): + fail('Unexpected number of key lines in K/M DB entry') + for line, ex in zip(keylines, expected_keys): + m = keyline_re.match(line) + if not m: + fail('Unrecognized key line in K/M DB entry') + kvno, enctype = m.groups() + exp_kvno, exp_enctype = ex + if kvno != str(exp_kvno): + fail('Unexpected key version in K/M DB entry') + if enctype != exp_enctype: + fail('Unexpected enctype in K/M DB entry') + + +# Check the stash file. Each argument must be a sequence of two +# elements: an expected key version and an expected enctype. +klist_re = re.compile(r'^\s*(\d+) K/M@KRBTEST.COM \((\S+)\)') +def check_stash(*expected): + # Split the output of klist -e -k into lines and ignore the first three. + outlines = realm.run([klist, '-e', '-k', stash_file]).splitlines()[3:] + if len(outlines) != len(expected): + fail('Unexpected number of lines in stash file klist') + for line, ex in zip(outlines, expected): + m = klist_re.match(line) + if not m: + fail('Unrecognized stash file klist line') + kvno, enctype = m.groups() + exp_kvno, exp_enctype = ex + if kvno != str(exp_kvno): + fail('Unexpected stash file klist kvno') + if enctype != exp_enctype: + fail('Unexpected stash file klist enctype') + + +# Verify that the user principal has the expected mkvno. +def check_mkvno(princ, expected_mkvno): + out = realm.run_kadminl('getprinc ' + princ) + if ('MKey: vno %d\n' % expected_mkvno) not in out: + fail('Unexpected mkvno in user DB entry') + + +# Change the password using either kadmin.local or kadmin, then check +# the mkvno of the principal against expected_mkvno and verify that +# the running KDC can access the new key. +def change_password_check_mkvno(local, princ, password, expected_mkvno): + cmd = 'cpw -pw %s %s' % (password, princ) + out = local and realm.run_kadminl(cmd) or realm.run_kadmin(cmd) + if 'changed.' not in out: + fail('Failed to change password') + check_mkvno(princ, expected_mkvno) + realm.kinit(princ, password) + + +# Add a master key with the specified options and a random password. +def add_mkey(options): + pw = ''.join(random.choice(string.ascii_uppercase) for x in range(5)) + realm.run([kdb5_util, 'add_mkey'] + options, input=(pw + '\n' + pw + '\n')) + + +# Run kdb5_util update_princ_encryption (with the dry-run option if +# specified) and verify the output against the expected mkvno, number +# of updated principals, and number of already-current principals. +mkvno_re = {False: re.compile(r'^Principals whose keys are being re-encrypted ' + 'to master key vno (\d+) if necessary:$'), + True: re.compile(r'^Principals whose keys WOULD BE re-encrypted ' + 'to master key vno (\d+):$')} +count_re = {False: re.compile(r'^(\d+) principals processed: (\d+) updated, ' + '(\d+) already current$'), + True: re.compile(r'^(\d+) principals processed: (\d+) would be ' + 'updated, (\d+) already current$')} +def update_princ_encryption(dry_run, expected_mkvno, expected_updated, + expected_current): + opts = ['-f', '-v'] + if dry_run: + opts += ['-n'] + out = realm.run([kdb5_util, 'update_princ_encryption'] + opts) + lines = out.splitlines() + # Parse the first line to get the target mkvno. + m = mkvno_re[dry_run].match(lines[0]) + if not m: + fail('Unexpected first line of update_princ_encryption output') + if m.group(1) != str(expected_mkvno): + fail('Unexpected master key version in update_princ_encryption output') + # Parse the last line to get the principal counts. + m = count_re[dry_run].match(lines[-1]) + if not m: + fail('Unexpected last line of update_princ_encryption output') + total, updated, current = m.groups() + if (total != str(expected_updated + expected_current) or + updated != str(expected_updated) or current != str(expected_current)): + fail('Unexpected counts from update_princ_encryption') + + +# Check the initial state of the realm. +check_mkey_list((1, defetype, True, True)) +check_master_dbent(1, (1, defetype)) +check_stash((1, defetype)) +check_mkvno(realm.user_princ, 1) + +# Add a new master key with no options. Verify that: +# 1. The new key appears in list_mkeys but has no activation time and +# is not active. +# 2. The new key appears in the K/M DB entry and is the current key to +# encrypt that entry. +# 3. The stash file is not modified (since we did not pass -s). +# 4. The old key is used for password changes. +add_mkey([]) +check_mkey_list((2, defetype, False, False), (1, defetype, True, True)) +check_master_dbent(2, (2, defetype), (1, defetype)) +change_password_check_mkvno(True, realm.user_princ, 'abcd', 1) +change_password_check_mkvno(False, realm.user_princ, 'user', 1) + +# Verify that use_mkey won't make all master keys inactive. +out = realm.run([kdb5_util, 'use_mkey', '1', 'now+1day'], expected_code=1) +if 'there must be one master key currently active' not in out: + fail('Unexpected error from use_mkey making all mkeys inactive') +check_mkey_list((2, defetype, False, False), (1, defetype, True, True)) + +# Make the new master key active. Verify that: +# 1. The new key has an activation time in list_mkeys and is active. +# 2. The new key is used for password changes. +# 3. The running KDC can access the new key. +realm.run([kdb5_util, 'use_mkey', '2', 'now-1day']) +check_mkey_list((2, defetype, True, True), (1, defetype, True, False)) +change_password_check_mkvno(True, realm.user_princ, 'abcd', 2) +change_password_check_mkvno(False, realm.user_princ, 'user', 2) + +# Check purge_mkeys behavior with both master keys still in use. +out = realm.run([kdb5_util, 'purge_mkeys', '-f', '-v']) +if 'All keys in use, nothing purged.' not in out: + fail('Unexpected output from purge_mkeys with both mkeys in use') + +# Do an update_princ_encryption dry run and for real. Verify that: +# 1. The target master key is 2 (the active mkvno). +# 2. nprincs - 2 principals were updated and one principal was +# skipped (K/M is not included in the output and user was updated +# above). +# 3. The dry run doesn't change user/admin's mkvno but the real update +# does. +# 4. The old stashed master key is sufficient to access the DB (via +# MKEY_AUX tl-data which keeps the current master key encrypted in +# each of the old master keys). +update_princ_encryption(True, 2, nprincs - 2, 1) +check_mkvno(realm.admin_princ, 1) +update_princ_encryption(False, 2, nprincs - 2, 1) +check_mkvno(realm.admin_princ, 2) +realm.stop_kdc() +realm.start_kdc() +realm.kinit(realm.user_princ, 'user') + +# Update all principals back to mkvno 1 and to mkvno 2 again, to +# verify that update_princ_encryption targets the active master key. +realm.run([kdb5_util, 'use_mkey', '2', 'now+1day']) +update_princ_encryption(False, 1, nprincs - 1, 0) +check_mkvno(realm.user_princ, 1) +realm.run([kdb5_util, 'use_mkey', '2', 'now-1day']) +update_princ_encryption(False, 2, nprincs - 1, 0) +check_mkvno(realm.user_princ, 2) + +# Test the safety check for purging with an outdated stash file. +out = realm.run([kdb5_util, 'purge_mkeys', '-f'], expected_code=1) +if 'stash file needs updating' not in out: + fail('Unexpected error from purge_mkeys safety check') + +# Update the master stash file and check it. Save a copy of the old +# one for a later test. +shutil.copy(stash_file, stash_file + '.old') +realm.run([kdb5_util, 'stash']) +check_stash((2, defetype), (1, defetype)) + +# Do a purge_mkeys dry run and for real. Verify that: +# 1. Master key 1 is purged. +# 2. The dry run doesn't remove mkvno 1 but the real one does. +# 3. The old stash file is no longer sufficient to access the DB. +# 4. If the stash file is updated, it no longer contains mkvno 1. +# 5. use_mkey now gives an error if we refer to mkvno 1. +# 6. A second purge_mkeys gives the right message. +out = realm.run([kdb5_util, 'purge_mkeys', '-v', '-n', '-f']) +if 'KVNO: 1' not in out or '1 key(s) would be purged' not in out: + fail('Unexpected output from purge_mkeys dry-run') +check_mkey_list((2, defetype, True, True), (1, defetype, True, False)) +check_master_dbent(2, (2, defetype), (1, defetype)) +out = realm.run([kdb5_util, 'purge_mkeys', '-v', '-f']) +check_mkey_list((2, defetype, True, True)) +check_master_dbent(2, (2, defetype)) +os.rename(stash_file, stash_file + '.save') +os.rename(stash_file + '.old', stash_file) +out = realm.run([kadmin_local, '-q', 'getprinc user'], expected_code=1) +if 'Unable to decrypt latest master key' not in out: + fail('Unexpected error from kadmin.local with old stash file') +os.rename(stash_file + '.save', stash_file) +realm.run([kdb5_util, 'stash']) +check_stash((2, defetype)) +out = realm.run([kdb5_util, 'use_mkey', '1'], expected_code=1) +if '1 is an invalid KVNO value' not in out: + fail('Unexpected error from use_mkey with invalid kvno') +out = realm.run([kdb5_util, 'purge_mkeys', '-f', '-v']) +if 'There is only one master key which can not be purged.' not in out: + fail('Unexpected output from purge_mkeys with one mkey') + +# Add a third master key with a specified enctype. Verify that: +# 1. The new master key receives the correct number. +# 2. The enctype argument is respected. +# 3. The new master key is stashed (by itself, at the moment). +# 4. We can roll over to the new master key and use it. +add_mkey(['-s', '-e', aes128]) +check_mkey_list((3, aes128, False, False), (2, defetype, True, True)) +check_master_dbent(3, (3, aes128), (2, defetype)) +check_stash((3, aes128)) +realm.run([kdb5_util, 'use_mkey', '3', 'now-1day']) +update_princ_encryption(False, 3, nprincs - 1, 0) +check_mkey_list((3, aes128, True, True), (2, defetype, True, False)) +check_mkvno(realm.user_princ, 3) + +realm.stop() + +# Load a dump file created with krb5 1.6, before the master key +# rollover changes were introduced. Write out an old-format stash +# file consistent with the dump's master password ("footes"). The K/M +# entry in this database will not have actkvno tl-data because it was +# created prior to master key rollover support. Verify that: +# 1. We can access the database using the old-format stash file. +# 2. list_mkeys displays the same list as for a post-1.7 KDB. +dumpfile = os.path.join(srctop, 'tests', 'dumpfiles', 'dump.16') +os.remove(stash_file) +f = open(stash_file, 'w') +f.write(struct.pack('=HL24s', 16, 24, + '\xF8\x3E\xFB\xBA\x6D\x80\xD9\x54\xE5\x5D\xF2\xE0' + '\x94\xAD\x6D\x86\xB5\x16\x37\xEC\x7C\x8A\xBC\x86')) +f.close() +realm.run([kdb5_util, 'load', dumpfile]) +nprincs = len(realm.run_kadminl('listprincs').splitlines()) - 1 +check_mkvno('K/M', 1) +check_mkey_list((1, des3, True, True)) + +# Create a new master key and verify that, without actkvkno tl-data: +# 1. list_mkeys displays the same as for a post-1.7 KDB. +# 2. update_princ_encryption still targets mkvno 1. +# 3. libkadm5 still uses mkvno 1 for key changes. +# 4. use_mkey creates the same list as for a post-1.7 KDB. +add_mkey([]) +check_mkey_list((2, defetype, False, False), (1, des3, True, True)) +update_princ_encryption(False, 1, 0, nprincs - 1) +realm.run_kadminl('addprinc -randkey ' + realm.user_princ) +check_mkvno(realm.user_princ, 1) +realm.run([kdb5_util, 'use_mkey', '2', 'now-1day']) +check_mkey_list((2, defetype, True, True), (1, des3, True, False)) + +success('Master key rollover tests') |