xref: /freebsd/crypto/krb5/src/tests/t_keytab.py (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1from k5test import *
2
3for realm in multipass_realms(create_user=False):
4    # Test kinit with a keytab.
5    realm.kinit(realm.host_princ, flags=['-k'])
6
7realm = K5Realm(get_creds=False, start_kadmind=True)
8
9# Test kinit with a partial keytab.
10mark('partial keytab')
11pkeytab = realm.keytab + '.partial'
12realm.run([ktutil], input=('rkt %s\ndelent 1\nwkt %s\n' %
13                           (realm.keytab, pkeytab)))
14realm.kinit(realm.host_princ, flags=['-k', '-t', pkeytab])
15
16# Test kinit with no keys for client in keytab.
17mark('no keys for client')
18realm.kinit(realm.user_princ, flags=['-k'], expected_code=1,
19            expected_msg='no suitable keys')
20
21# Test kinit and klist with client keytab defaults.
22mark('client keytab')
23realm.extract_keytab(realm.user_princ, realm.client_keytab);
24realm.run([kinit, '-k', '-i'])
25realm.klist(realm.user_princ)
26realm.run([kdestroy])
27realm.kinit(realm.user_princ, flags=['-k', '-i'])
28realm.klist(realm.user_princ)
29out = realm.run([klist, '-k', '-i'])
30if realm.client_keytab not in out or realm.user_princ not in out:
31    fail('Expected output not seen from klist -k -i')
32
33# Test implicit request for keytab (-i or -t without -k)
34mark('implicit -k')
35realm.run([kdestroy])
36realm.kinit(realm.host_princ, flags=['-t', realm.keytab],
37            expected_msg='keytab specified, forcing -k')
38realm.klist(realm.host_princ)
39realm.run([kdestroy])
40realm.kinit(realm.user_princ, flags=['-i'],
41            expected_msg='keytab specified, forcing -k')
42realm.klist(realm.user_princ)
43
44# Test default principal for -k.  This operation requires
45# canonicalization against the keytab in krb5_get_init_creds_keytab()
46# as the krb5_sname_to_principal() result won't have a realm.  Try
47# with and without without fallback processing since the code paths
48# are different.
49mark('default principal for -k')
50realm.run([kinit, '-k'])
51realm.klist(realm.host_princ)
52no_canon_conf = {'libdefaults': {'dns_canonicalize_hostname': 'false'}}
53no_canon = realm.special_env('no_canon', False, krb5_conf=no_canon_conf)
54realm.run([kinit, '-k'], env=no_canon)
55realm.klist(realm.host_princ)
56
57# Test extracting keys with multiple key versions present.
58mark('multi-kvno extract')
59os.remove(realm.keytab)
60realm.run([kadminl, 'cpw', '-randkey', '-keepold', realm.host_princ])
61out = realm.run([kadminl, 'ktadd', '-norandkey', realm.host_princ])
62if 'with kvno 1,' not in out or 'with kvno 2,' not in out:
63    fail('Expected output not seen from kadmin.local ktadd -norandkey')
64out = realm.run([klist, '-k', '-e'])
65if ' 1 host/' not in out or ' 2 host/' not in out:
66    fail('Expected output not seen from klist -k -e')
67
68# Test again using kadmin over the network.
69mark('multi-kvno extract (via kadmin)')
70realm.prep_kadmin()
71os.remove(realm.keytab)
72out = realm.run_kadmin(['ktadd', '-norandkey', realm.host_princ])
73if 'with kvno 1,' not in out or 'with kvno 2,' not in out:
74    fail('Expected output not seen from kadmin.local ktadd -norandkey')
75out = realm.run([klist, '-k', '-e'])
76if ' 1 host/' not in out or ' 2 host/' not in out:
77    fail('Expected output not seen from klist -k -e')
78
79# Test handling of kvno values beyond 255.  Use kadmin over the
80# network since we used to have an 8-bit limit on kvno marshalling.
81
82# Test one key rotation, verifying that the expected new kvno appears
83# in the keytab and in the principal entry.
84def test_key_rotate(realm, princ, expected_kvno):
85    realm.run_kadmin(['ktadd', '-k', realm.keytab, princ])
86    realm.run([kadminl, 'ktrem', princ, 'old'])
87    realm.kinit(princ, flags=['-k'])
88    msg = '%d %s' % (expected_kvno, princ)
89    out = realm.run([klist, '-k'], expected_msg=msg)
90    msg = 'Key: vno %d,' % expected_kvno
91    out = realm.run_kadmin(['getprinc', princ], expected_msg=msg)
92
93mark('key rotation across boundaries')
94princ = 'foo/bar@%s' % realm.realm
95realm.addprinc(princ)
96os.remove(realm.keytab)
97realm.run([kadminl, 'modprinc', '-kvno', '253', princ])
98test_key_rotate(realm, princ, 254)
99test_key_rotate(realm, princ, 255)
100test_key_rotate(realm, princ, 256)
101test_key_rotate(realm, princ, 257)
102realm.run([kadminl, 'modprinc', '-kvno', '32766', princ])
103test_key_rotate(realm, princ, 32767)
104test_key_rotate(realm, princ, 32768)
105test_key_rotate(realm, princ, 32769)
106realm.run([kadminl, 'modprinc', '-kvno', '65534', princ])
107test_key_rotate(realm, princ, 65535)
108test_key_rotate(realm, princ, 1)
109test_key_rotate(realm, princ, 2)
110
111mark('32-bit kvno')
112
113# Test that klist -k can read a keytab entry without a 32-bit kvno and
114# reports the 8-bit key version.
115record = b'\x00\x01'             # principal component count
116record += b'\x00\x0bKRBTEST.COM' # realm
117record += b'\x00\x04user'        # principal component
118record += b'\x00\x00\x00\x01'    # name type (NT-PRINCIPAL)
119record += b'\x54\xf7\x4d\x35'    # timestamp
120record += b'\x02'                # key version
121record += b'\x00\x12'            # enctype
122record += b'\x00\x20'            # key length
123record += b'\x00' * 32           # key bytes
124f = open(realm.keytab, 'wb')
125f.write(b'\x05\x02\x00\x00\x00' + bytes([len(record)]))
126f.write(record)
127f.close()
128msg = '   2 %s' % realm.user_princ
129out = realm.run([klist, '-k'], expected_msg=msg)
130
131# Make sure zero-fill isn't treated as a 32-bit kvno.
132f = open(realm.keytab, 'wb')
133f.write(b'\x05\x02\x00\x00\x00' + bytes([len(record) + 4]))
134f.write(record)
135f.write(b'\x00\x00\x00\x00')
136f.close()
137msg = '   2 %s' % realm.user_princ
138out = realm.run([klist, '-k'], expected_msg=msg)
139
140# Make sure a hand-crafted 32-bit kvno is recognized.
141f = open(realm.keytab, 'wb')
142f.write(b'\x05\x02\x00\x00\x00' + bytes([len(record) + 4]))
143f.write(record)
144f.write(b'\x00\x00\x00\x03')
145f.close()
146msg = '   3 %s' % realm.user_princ
147out = realm.run([klist, '-k'], expected_msg=msg)
148
149# Test parameter expansion in profile variables
150mark('parameter expansion')
151realm.stop()
152conf = {'libdefaults': {
153        'default_keytab_name': 'testdir/%{null}abc%{uid}',
154        'default_client_keytab_name': 'testdir/%{null}xyz%{uid}'}}
155realm = K5Realm(krb5_conf=conf, create_kdb=False)
156del realm.env['KRB5_KTNAME']
157del realm.env['KRB5_CLIENT_KTNAME']
158uidstr = str(os.getuid())
159msg = 'FILE:testdir/abc%s' % uidstr
160out = realm.run([klist, '-k'], expected_code=1, expected_msg=msg)
161msg = 'FILE:testdir/xyz%s' % uidstr
162out = realm.run([klist, '-ki'], expected_code=1, expected_msg=msg)
163
164conf = {'libdefaults': {'allow_weak_crypto': 'true'}}
165realm = K5Realm(create_user=False, create_host=False, krb5_conf=conf)
166
167realm.run([kadminl, 'ank', '-pw', 'pw', 'default'])
168realm.run([kadminl, 'ank', '-e', 'aes256-cts:special', '-pw', 'pw', 'exp'])
169realm.run([kadminl, 'ank', '-e', 'aes256-cts:special', '-pw', 'pw', '+preauth',
170           'pexp'])
171
172# Extract one of the explicit salt values from the database.
173out = realm.run([kdb5_util, 'tabdump', 'keyinfo'])
174salt_dict = {f[0]: f[5] for f in [l.split('\t') for l in out.splitlines()]}
175exp_salt = bytes.fromhex(salt_dict['exp@KRBTEST.COM']).decode('ascii')
176
177# Create a keytab using ktutil addent with the specified options and
178# password "pw".  Test that we can use it to get initial tickets.
179# Remove the keytab afterwards.
180def test_addent(realm, princ, opts):
181    realm.run([ktutil], input=('addent -password -p %s -k 1 %s\npw\nwkt %s\n' %
182                               (princ, opts, realm.keytab)))
183    realm.kinit(princ, flags=['-k'])
184    os.remove(realm.keytab)
185
186mark('ktutil addent')
187
188# Test with default salt.
189test_addent(realm, 'default', '-e aes128-cts')
190test_addent(realm, 'default', '-e aes256-cts')
191
192# Test with a salt specified to ktutil addent.
193test_addent(realm, 'exp', '-e aes256-cts -s %s' % exp_salt)
194
195# Test etype-info fetching.
196test_addent(realm, 'default', '-f')
197test_addent(realm, 'default', '-f -e aes128-cts')
198test_addent(realm, 'exp', '-f')
199test_addent(realm, 'pexp', '-f')
200
201# Regression test for #8914: INT32_MIN length can cause backwards seek
202mark('invalid record length')
203f = open(realm.keytab, 'wb')
204f.write(b'\x05\x02\x80\x00\x00\x00')
205f.close()
206msg = 'Bad format in keytab while scanning keytab'
207realm.run([klist, '-k'], expected_code=1, expected_msg=msg)
208
209success('Keytab-related tests')
210