Imported Upstream version 1.17
[platform/upstream/krb5.git] / src / tests / t_mkey.py
1 from k5test import *
2 import random
3 import re
4 import struct
5
6 # Convenience constants for use as expected enctypes.  defetype is the
7 # default enctype for master keys.
8 aes256 = 'aes256-cts-hmac-sha1-96'
9 aes128 = 'aes128-cts-hmac-sha1-96'
10 des3 = 'des3-cbc-sha1'
11 defetype = aes256
12
13 realm = K5Realm(create_host=False, start_kadmind=True)
14 realm.prep_kadmin()
15 stash_file = os.path.join(realm.testdir, 'stash')
16
17 # Count the number of principals in the realm.
18 nprincs = len(realm.run([kadminl, 'listprincs']).splitlines())
19
20 # List the currently active mkeys and compare against expected
21 # results.  Each argument must be a sequence of four elements: an
22 # expected kvno, an expected enctype, whether the key is expected to
23 # have an activation time, and whether the key is expected to be
24 # currently active.
25 list_mkeys_re = re.compile(r'^KVNO: (\d+), Enctype: (\S+), '
26                            '(Active on: [^\*]+|No activate time set)( \*)?$')
27 def check_mkey_list(*expected):
28     # Split the output of kdb5_util list_mkeys into lines and ignore the first.
29     outlines = realm.run([kdb5_util, 'list_mkeys']).splitlines()[1:]
30     if len(outlines) != len(expected):
31         fail('Unexpected number of list_mkeys output lines')
32     for line, ex in zip(outlines, expected):
33         m = list_mkeys_re.match(line)
34         if not m:
35             fail('Unrecognized list_mkeys output line')
36         kvno, enctype, act_time, active = m.groups()
37         exp_kvno, exp_enctype, exp_act_time_present, exp_active = ex
38         if kvno != str(exp_kvno):
39             fail('Unexpected master key version')
40         if enctype != exp_enctype:
41             fail('Unexpected master key enctype')
42         if act_time.startswith('Active on: ') != exp_act_time_present:
43             fail('Unexpected presence or absence of mkey activation time')
44         if (active == ' *') != exp_active:
45             fail('Master key unexpectedly active or inactive')
46
47
48 # Get the K/M principal.  Verify that it has the expected mkvno.  Each
49 # remaining argment must be a sequence of two elements: an expected
50 # key version and an expected enctype.
51 keyline_re = re.compile(r'^Key: vno (\d+), (\S+)$')
52 def check_master_dbent(expected_mkvno, *expected_keys):
53     outlines = realm.run([kadminl, 'getprinc', 'K/M']).splitlines()
54     mkeyline = [l for l in outlines if l.startswith('MKey: vno ')]
55     if len(mkeyline) != 1 or mkeyline[0] != ('MKey: vno %d' % expected_mkvno):
56         fail('Unexpected mkvno in K/M DB entry')
57     keylines = [l for l in outlines if l.startswith('Key: vno ')]
58     if len(keylines) != len(expected_keys):
59         fail('Unexpected number of key lines in K/M DB entry')
60     for line, ex in zip(keylines, expected_keys):
61         m = keyline_re.match(line)
62         if not m:
63             fail('Unrecognized key line in K/M DB entry')
64         kvno, enctype = m.groups()
65         exp_kvno, exp_enctype = ex
66         if kvno != str(exp_kvno):
67             fail('Unexpected key version in K/M DB entry')
68         if enctype != exp_enctype:
69             fail('Unexpected enctype in K/M DB entry')
70
71
72 # Check the stash file.  Each argument must be a sequence of two
73 # elements: an expected key version and an expected enctype.
74 klist_re = re.compile(r'^\s*(\d+) K/M@KRBTEST.COM \((\S+)\)')
75 def check_stash(*expected):
76     # Split the output of klist -e -k into lines and ignore the first three.
77     outlines = realm.run([klist, '-e', '-k', stash_file]).splitlines()[3:]
78     if len(outlines) != len(expected):
79         fail('Unexpected number of lines in stash file klist')
80     for line, ex in zip(outlines, expected):
81         m = klist_re.match(line)
82         if not m:
83             fail('Unrecognized stash file klist line')
84         kvno, enctype = m.groups()
85         exp_kvno, exp_enctype = ex
86         if kvno != str(exp_kvno):
87             fail('Unexpected stash file klist kvno')
88         if enctype != exp_enctype:
89             fail('Unexpected stash file klist enctype')
90
91
92 # Verify that the user principal has the expected mkvno.
93 def check_mkvno(princ, expected_mkvno):
94     msg = 'MKey: vno %d\n' % expected_mkvno
95     realm.run([kadminl, 'getprinc', princ], expected_msg=msg)
96
97
98 # Change the password using either kadmin.local or kadmin, then check
99 # the mkvno of the principal against expected_mkvno and verify that
100 # the running KDC can access the new key.
101 def change_password_check_mkvno(local, princ, password, expected_mkvno):
102     cmd = ['cpw', '-pw', password, princ]
103     if local:
104         realm.run([kadminl] + cmd)
105     else:
106         realm.run_kadmin(cmd)
107     check_mkvno(princ, expected_mkvno)
108     realm.kinit(princ, password)
109
110
111 # Add a master key with the specified options and a random password.
112 def add_mkey(options):
113     pw = ''.join(random.choice(string.ascii_uppercase) for x in range(5))
114     realm.run([kdb5_util, 'add_mkey'] + options, input=(pw + '\n' + pw + '\n'))
115
116
117 # Run kdb5_util update_princ_encryption (with the dry-run option if
118 # specified) and verify the output against the expected mkvno, number
119 # of updated principals, and number of already-current principals.
120 mkvno_re = {False: re.compile(r'^Principals whose keys are being re-encrypted '
121                               'to master key vno (\d+) if necessary:$'),
122             True: re.compile(r'^Principals whose keys WOULD BE re-encrypted '
123                              'to master key vno (\d+):$')}
124 count_re = {False: re.compile(r'^(\d+) principals processed: (\d+) updated, '
125                               '(\d+) already current$'),
126             True: re.compile(r'^(\d+) principals processed: (\d+) would be '
127                              'updated, (\d+) already current$')}
128 def update_princ_encryption(dry_run, expected_mkvno, expected_updated,
129                             expected_current):
130     opts = ['-f', '-v']
131     if dry_run:
132         opts += ['-n']
133     out = realm.run([kdb5_util, 'update_princ_encryption'] + opts)
134     lines = out.splitlines()
135     # Parse the first line to get the target mkvno.
136     m = mkvno_re[dry_run].match(lines[0])
137     if not m:
138         fail('Unexpected first line of update_princ_encryption output')
139     if m.group(1) != str(expected_mkvno):
140         fail('Unexpected master key version in update_princ_encryption output')
141     # Parse the last line to get the principal counts.
142     m = count_re[dry_run].match(lines[-1])
143     if not m:
144         fail('Unexpected last line of update_princ_encryption output')
145     total, updated, current = m.groups()
146     if (total != str(expected_updated + expected_current) or
147         updated != str(expected_updated) or current != str(expected_current)):
148         fail('Unexpected counts from update_princ_encryption')
149
150
151 # Check the initial state of the realm.
152 mark('initial state')
153 check_mkey_list((1, defetype, True, True))
154 check_master_dbent(1, (1, defetype))
155 check_stash((1, defetype))
156 check_mkvno(realm.user_princ, 1)
157
158 # Check that stash will fail if a temp stash file is already present.
159 mark('temp stash collision')
160 collisionfile = os.path.join(realm.testdir, 'stash_tmp')
161 f = open(collisionfile, 'w')
162 f.close()
163 realm.run([kdb5_util, 'stash'], expected_code=1,
164           expected_msg='Temporary stash file already exists')
165 os.unlink(collisionfile)
166
167 # Add a new master key with no options.  Verify that:
168 # 1. The new key appears in list_mkeys but has no activation time and
169 #    is not active.
170 # 2. The new key appears in the K/M DB entry and is the current key to
171 #    encrypt that entry.
172 # 3. The stash file is not modified (since we did not pass -s).
173 # 4. The old key is used for password changes.
174 mark('add_mkey (second master key)')
175 add_mkey([])
176 check_mkey_list((2, defetype, False, False), (1, defetype, True, True))
177 check_master_dbent(2, (2, defetype), (1, defetype))
178 change_password_check_mkvno(True, realm.user_princ, 'abcd', 1)
179 change_password_check_mkvno(False, realm.user_princ, 'user', 1)
180
181 # Verify that use_mkey won't make all master keys inactive.
182 mark('use_mkey (no active keys)')
183 realm.run([kdb5_util, 'use_mkey', '1', 'now+1day'], expected_code=1,
184           expected_msg='there must be one master key currently active')
185 check_mkey_list((2, defetype, False, False), (1, defetype, True, True))
186
187 # Make the new master key active.  Verify that:
188 # 1. The new key has an activation time in list_mkeys and is active.
189 # 2. The new key is used for password changes.
190 # 3. The running KDC can access the new key.
191 mark('use_mkey')
192 realm.run([kdb5_util, 'use_mkey', '2', 'now-1day'])
193 check_mkey_list((2, defetype, True, True), (1, defetype, True, False))
194 change_password_check_mkvno(True, realm.user_princ, 'abcd', 2)
195 change_password_check_mkvno(False, realm.user_princ, 'user', 2)
196
197 # Check purge_mkeys behavior with both master keys still in use.
198 mark('purge_mkeys (nothing to purge)')
199 realm.run([kdb5_util, 'purge_mkeys', '-f', '-v'],
200           expected_msg='All keys in use, nothing purged.')
201
202 # Do an update_princ_encryption dry run and for real.  Verify that:
203 # 1. The target master key is 2 (the active mkvno).
204 # 2. nprincs - 2 principals were updated and one principal was
205 #    skipped (K/M is not included in the output and user was updated
206 #    above).
207 # 3. The dry run doesn't change user/admin's mkvno but the real update
208 #    does.
209 # 4. The old stashed master key is sufficient to access the DB (via
210 #    MKEY_AUX tl-data which keeps the current master key encrypted in
211 #    each of the old master keys).
212 mark('update_princ_encryption')
213 update_princ_encryption(True, 2, nprincs - 2, 1)
214 check_mkvno(realm.admin_princ, 1)
215 update_princ_encryption(False, 2, nprincs - 2, 1)
216 check_mkvno(realm.admin_princ, 2)
217 realm.stop_kdc()
218 realm.start_kdc()
219 realm.kinit(realm.user_princ, 'user')
220
221 # Update all principals back to mkvno 1 and to mkvno 2 again, to
222 # verify that update_princ_encryption targets the active master key.
223 mark('update_princ_encryption (back and forth)')
224 realm.run([kdb5_util, 'use_mkey', '2', 'now+1day'])
225 update_princ_encryption(False, 1, nprincs - 1, 0)
226 check_mkvno(realm.user_princ, 1)
227 realm.run([kdb5_util, 'use_mkey', '2', 'now-1day'])
228 update_princ_encryption(False, 2, nprincs - 1, 0)
229 check_mkvno(realm.user_princ, 2)
230
231 # Test the safety check for purging with an outdated stash file.
232 mark('purge_mkeys (outdated stash file)')
233 realm.run([kdb5_util, 'purge_mkeys', '-f'], expected_code=1,
234           expected_msg='stash file needs updating')
235
236 # Update the master stash file and check it.  Save a copy of the old
237 # one for a later test.
238 mark('update stash file')
239 shutil.copy(stash_file, stash_file + '.old')
240 realm.run([kdb5_util, 'stash'])
241 check_stash((2, defetype), (1, defetype))
242
243 # Do a purge_mkeys dry run and for real.  Verify that:
244 # 1. Master key 1 is purged.
245 # 2. The dry run doesn't remove mkvno 1 but the real one does.
246 # 3. The old stash file is no longer sufficient to access the DB.
247 # 4. If the stash file is updated, it no longer contains mkvno 1.
248 # 5. use_mkey now gives an error if we refer to mkvno 1.
249 # 6. A second purge_mkeys gives the right message.
250 mark('purge_mkeys')
251 out = realm.run([kdb5_util, 'purge_mkeys', '-v', '-n', '-f'])
252 if 'KVNO: 1' not in out or '1 key(s) would be purged' not in out:
253     fail('Unexpected output from purge_mkeys dry-run')
254 check_mkey_list((2, defetype, True, True), (1, defetype, True, False))
255 check_master_dbent(2, (2, defetype), (1, defetype))
256 out = realm.run([kdb5_util, 'purge_mkeys', '-v', '-f'])
257 check_mkey_list((2, defetype, True, True))
258 check_master_dbent(2, (2, defetype))
259 os.rename(stash_file, stash_file + '.save')
260 os.rename(stash_file + '.old', stash_file)
261 realm.run([kadminl, 'getprinc', 'user'], expected_code=1,
262           expected_msg='Unable to decrypt latest master key')
263 os.rename(stash_file + '.save', stash_file)
264 realm.run([kdb5_util, 'stash'])
265 check_stash((2, defetype))
266 realm.run([kdb5_util, 'use_mkey', '1'], expected_code=1,
267           expected_msg='1 is an invalid KVNO value')
268 realm.run([kdb5_util, 'purge_mkeys', '-f', '-v'],
269           expected_msg='There is only one master key which can not be purged.')
270
271 # Add a third master key with a specified enctype.  Verify that:
272 # 1. The new master key receives the correct number.
273 # 2. The enctype argument is respected.
274 # 3. The new master key is stashed (by itself, at the moment).
275 # 4. We can roll over to the new master key and use it.
276 mark('add_mkey and update_princ_encryption (third master key)')
277 add_mkey(['-s', '-e', aes128])
278 check_mkey_list((3, aes128, False, False), (2, defetype, True, True))
279 check_master_dbent(3, (3, aes128), (2, defetype))
280 check_stash((3, aes128))
281 realm.run([kdb5_util, 'use_mkey', '3', 'now-1day'])
282 update_princ_encryption(False, 3, nprincs - 1, 0)
283 check_mkey_list((3, aes128, True, True), (2, defetype, True, False))
284 check_mkvno(realm.user_princ, 3)
285
286 # Regression test for #7994 (randkey does not update principal mkvno)
287 # and #7995 (-keepold does not re-encrypt old keys).
288 mark('#7994 and #7995 regression test')
289 add_mkey(['-s'])
290 realm.run([kdb5_util, 'use_mkey', '4', 'now-1day'])
291 realm.run([kadminl, 'cpw', '-randkey', '-keepold', realm.user_princ])
292 # With #7994 unfixed, mkvno of user will still be 3.
293 check_mkvno(realm.user_princ, 4)
294 # With #7995 unfixed, old keys are still encrypted with mkvno 3.
295 update_princ_encryption(False, 4, nprincs - 2, 1)
296 realm.run([kdb5_util, 'purge_mkeys', '-f'])
297 out = realm.run([kadminl, 'xst', '-norandkey', realm.user_princ])
298 if 'Decrypt integrity check failed' in out or 'added to keytab' not in out:
299     fail('Preserved old key data not updated to new master key')
300
301 realm.stop()
302
303 # Load a dump file created with krb5 1.6, before the master key
304 # rollover changes were introduced.  Write out an old-format stash
305 # file consistent with the dump's master password ("footes").  The K/M
306 # entry in this database will not have actkvno tl-data because it was
307 # created prior to master key rollover support.  Verify that:
308 # 1. We can access the database using the old-format stash file.
309 # 2. list_mkeys displays the same list as for a post-1.7 KDB.
310 mark('pre-1.7 stash file')
311 dumpfile = os.path.join(srctop, 'tests', 'dumpfiles', 'dump.16')
312 os.remove(stash_file)
313 f = open(stash_file, 'wb')
314 f.write(struct.pack('=HL24s', 16, 24,
315                     b'\xF8\x3E\xFB\xBA\x6D\x80\xD9\x54\xE5\x5D\xF2\xE0'
316                     b'\x94\xAD\x6D\x86\xB5\x16\x37\xEC\x7C\x8A\xBC\x86'))
317 f.close()
318 realm.run([kdb5_util, 'load', dumpfile])
319 nprincs = len(realm.run([kadminl, 'listprincs']).splitlines())
320 check_mkvno('K/M', 1)
321 check_mkey_list((1, des3, True, True))
322
323 # Create a new master key and verify that, without actkvkno tl-data:
324 # 1. list_mkeys displays the same as for a post-1.7 KDB.
325 # 2. update_princ_encryption still targets mkvno 1.
326 # 3. libkadm5 still uses mkvno 1 for key changes.
327 # 4. use_mkey creates the same list as for a post-1.7 KDB.
328 mark('rollover from pre-1.7 KDB')
329 add_mkey([])
330 check_mkey_list((2, defetype, False, False), (1, des3, True, True))
331 update_princ_encryption(False, 1, 0, nprincs - 1)
332 realm.run([kadminl, 'addprinc', '-randkey', realm.user_princ])
333 check_mkvno(realm.user_princ, 1)
334 realm.run([kdb5_util, 'use_mkey', '2', 'now-1day'])
335 check_mkey_list((2, defetype, True, True), (1, des3, True, False))
336
337 # Regression test for #8395.  Purge the master key and verify that a
338 # master key fetch does not segfault.
339 mark('#8395 regression test')
340 realm.run([kadminl, 'purgekeys', '-all', 'K/M'])
341 realm.run([kadminl, 'getprinc', realm.user_princ], expected_code=1,
342           expected_msg='Cannot find master key record in database')
343
344 success('Master key rollover tests')