xref: /freebsd/crypto/krb5/src/tests/t_pkinit.py (revision b670c9bafc0e31c7609969bf374b2e80bdc00211)
1from k5test import *
2
3# Skip this test if pkinit wasn't built.
4if not pkinit_enabled:
5    skip_rest('PKINIT tests', 'PKINIT module not built')
6
7soft_pkcs11 = os.path.join(buildtop, 'tests', 'softpkcs11', 'softpkcs11.so')
8
9# Construct a krb5.conf fragment configuring pkinit.
10user_pem = os.path.join(pkinit_certs, 'user.pem')
11privkey_pem = os.path.join(pkinit_certs, 'privkey.pem')
12privkey_enc_pem = os.path.join(pkinit_certs, 'privkey-enc.pem')
13user_p12 = os.path.join(pkinit_certs, 'user.p12')
14user_enc_p12 = os.path.join(pkinit_certs, 'user-enc.p12')
15user_upn_p12 = os.path.join(pkinit_certs, 'user-upn.p12')
16user_upn2_p12 = os.path.join(pkinit_certs, 'user-upn2.p12')
17user_upn3_p12 = os.path.join(pkinit_certs, 'user-upn3.p12')
18generic_p12 = os.path.join(pkinit_certs, 'generic.p12')
19path = os.path.join(os.getcwd(), 'testdir', 'tmp-pkinit-certs')
20path_enc = os.path.join(os.getcwd(), 'testdir', 'tmp-pkinit-certs-enc')
21
22pkinit_kdc_conf = {'realms': {'$realm': {
23            'default_principal_flags': '+preauth',
24            'pkinit_eku_checking': 'none',
25            'pkinit_indicator': ['indpkinit1', 'indpkinit2']}}}
26restrictive_kdc_conf = {'realms': {'$realm': {
27            'restrict_anonymous_to_tgt': 'true' }}}
28freshness_kdc_conf = {'realms': {'$realm': {
29            'pkinit_require_freshness': 'true'}}}
30
31testprincs = {'krbtgt/KRBTEST.COM': {'keys': 'aes128-cts'},
32              'user': {'keys': 'aes128-cts', 'flags': '+preauth'},
33              'user2': {'keys': 'aes128-cts', 'flags': '+preauth'}}
34alias_kdc_conf = {'realms': {'$realm': {
35            'default_principal_flags': '+preauth',
36            'pkinit_eku_checking': 'none',
37            'pkinit_allow_upn': 'true',
38            'database_module': 'test'}},
39                  'dbmodules': {'test': {
40                      'db_library': 'test',
41                      'alias': {'user@krbtest.com': 'user'},
42                      'princs': testprincs}}}
43
44file_identity = 'FILE:%s,%s' % (user_pem, privkey_pem)
45file_enc_identity = 'FILE:%s,%s' % (user_pem, privkey_enc_pem)
46dir_identity = 'DIR:%s' % path
47dir_enc_identity = 'DIR:%s' % path_enc
48dir_file_identity = 'FILE:%s,%s' % (os.path.join(path, 'user.crt'),
49                                    os.path.join(path, 'user.key'))
50dir_file_enc_identity = 'FILE:%s,%s' % (os.path.join(path_enc, 'user.crt'),
51                                        os.path.join(path_enc, 'user.key'))
52p12_identity = 'PKCS12:%s' % user_p12
53p12_upn_identity = 'PKCS12:%s' % user_upn_p12
54p12_upn2_identity = 'PKCS12:%s' % user_upn2_p12
55p12_upn3_identity = 'PKCS12:%s' % user_upn3_p12
56p12_generic_identity = 'PKCS12:%s' % generic_p12
57p12_enc_identity = 'PKCS12:%s' % user_enc_p12
58p11_identity = 'PKCS11:' + soft_pkcs11
59p11_token_identity = ('PKCS11:module_name=' + soft_pkcs11 +
60                      ':slotid=1:token=SoftToken (token)')
61
62# Start a realm with the test kdb module for the following UPN SAN tests.
63realm = K5Realm(kdc_conf=alias_kdc_conf, create_kdb=False, pkinit=True)
64realm.start_kdc()
65
66mark('UPN SANs')
67
68# Compatibility check: cert contains UPN "user", which matches the
69# request principal user@KRBTEST.COM if parsed as a normal principal.
70realm.kinit(realm.user_princ,
71            flags=['-X', 'X509_user_identity=%s' % p12_upn2_identity])
72
73# Compatibility check: cert contains UPN "user@KRBTEST.COM", which matches
74# the request principal user@KRBTEST.COM if parsed as a normal principal.
75realm.kinit(realm.user_princ,
76            flags=['-X', 'X509_user_identity=%s' % p12_upn3_identity])
77
78# Cert contains UPN "user@krbtest.com" which is aliased to the request
79# principal.
80realm.kinit(realm.user_princ,
81            flags=['-X', 'X509_user_identity=%s' % p12_upn_identity])
82
83# Test an id-pkinit-san match to a post-canonical principal.
84realm.kinit('user@krbtest.com',
85            flags=['-E', '-X', 'X509_user_identity=%s' % p12_identity])
86
87# Test a UPN match to a post-canonical principal.  (This only works
88# for the cert with the UPN containing just "user", as we don't allow
89# UPN reparsing when comparing to the canonicalized client principal.)
90realm.kinit('user@krbtest.com',
91            flags=['-E', '-X', 'X509_user_identity=%s' % p12_upn2_identity])
92
93# Test a mismatch.
94msg = 'kinit: Client name mismatch while getting initial credentials'
95realm.run([kinit, '-X', 'X509_user_identity=%s' % p12_upn2_identity, 'user2'],
96          expected_code=1, expected_msg=msg)
97realm.stop()
98
99realm = K5Realm(kdc_conf=pkinit_kdc_conf, get_creds=False, pkinit=True)
100
101# Sanity check - password-based preauth should still work.
102mark('password preauth sanity check')
103realm.run(['./responder', '-r', 'password=%s' % password('user'),
104           realm.user_princ])
105realm.kinit(realm.user_princ, password=password('user'))
106realm.klist(realm.user_princ)
107realm.run([kvno, realm.host_princ])
108
109# Having tested password preauth, remove the keys for better error
110# reporting.
111realm.run([kadminl, 'purgekeys', '-all', realm.user_princ])
112
113# Test anonymous PKINIT.
114mark('anonymous')
115realm.kinit('@%s' % realm.realm, flags=['-n'], expected_code=1,
116            expected_msg='not found in Kerberos database')
117realm.addprinc('WELLKNOWN/ANONYMOUS')
118realm.kinit('@%s' % realm.realm, flags=['-n'])
119realm.klist('WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS')
120realm.run([kvno, realm.host_princ])
121out = realm.run(['./adata', realm.host_princ])
122if '97:' in out:
123    fail('auth indicators seen in anonymous PKINIT ticket')
124# Verify start_realm setting and test referrals TGS request.
125realm.run([klist, '-C'], expected_msg='start_realm = KRBTEST.COM')
126realm.run([kvno, '-S', 'host', hostname])
127
128# Test anonymous kadmin.
129mark('anonymous kadmin')
130f = open(os.path.join(realm.testdir, 'acl'), 'a')
131f.write('WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS a *')
132f.close()
133realm.start_kadmind()
134realm.run([kadmin, '-n', 'addprinc', '-pw', 'test', 'testadd'])
135realm.run([kadmin, '-n', 'getprinc', 'testadd'], expected_code=1,
136          expected_msg="Operation requires ``get'' privilege")
137realm.stop_kadmind()
138
139# Test with anonymous restricted; FAST should work but kvno should fail.
140mark('anonymous restricted')
141r_env = realm.special_env('restrict', True, kdc_conf=restrictive_kdc_conf)
142realm.stop_kdc()
143realm.start_kdc(env=r_env)
144realm.kinit('@%s' % realm.realm, flags=['-n'])
145realm.kinit('@%s' % realm.realm, flags=['-n', '-T', realm.ccache])
146realm.run([kvno, realm.host_princ], expected_code=1,
147          expected_msg='KDC policy rejects request')
148
149# Regression test for #8458: S4U2Self requests crash the KDC if
150# anonymous is restricted.
151mark('#8458 regression test')
152realm.kinit(realm.host_princ, flags=['-k'])
153realm.run([kvno, '-U', 'user', realm.host_princ])
154
155# Go back to the normal KDC environment.
156realm.stop_kdc()
157realm.start_kdc()
158
159# Run the basic test - PKINIT with FILE: identity, with no password on the key.
160mark('FILE identity, no password')
161msgs = ('Sending unauthenticated request',
162        '/Additional pre-authentication required',
163        'Preauthenticating using KDC method data',
164        'PKINIT client received freshness token from KDC',
165        'PKINIT loading CA certs and CRLs from FILE',
166        'PKINIT client making DH request',
167        ' preauth for next request: PA-FX-COOKIE (133), PA-PK-AS-REQ (16)',
168        'PKINIT client verified DH reply',
169        'PKINIT client found id-pkinit-san in KDC cert',
170        'PKINIT client matched KDC principal krbtgt/')
171realm.pkinit(realm.user_princ, expected_trace=msgs)
172realm.klist(realm.user_princ)
173realm.run([kvno, realm.host_princ])
174
175# Try using multiple configured pkinit_identities, to make sure we
176# fall back to the second one when the first one cannot be read.
177id_conf = {'realms': {'$realm': {'pkinit_identities': [file_identity + 'X',
178                                                       file_identity]}}}
179id_env = realm.special_env('idconf', False, krb5_conf=id_conf)
180realm.kinit(realm.user_princ, expected_trace=msgs, env=id_env)
181
182# Try again using RSA instead of DH.
183mark('FILE identity, no password, RSA')
184realm.pkinit(realm.user_princ, flags=['-X', 'flag_RSA_PROTOCOL=yes'],
185             expected_trace=('PKINIT client making RSA request',
186                             'PKINIT client verified RSA reply'))
187realm.klist(realm.user_princ)
188
189# Test a DH parameter renegotiation by temporarily setting a 4096-bit
190# minimum on the KDC.  (Preauth type 16 is PKINIT PA_PK_AS_REQ;
191# 109 is PKINIT TD_DH_PARAMETERS; 133 is FAST PA-FX-COOKIE.)
192mark('DH parameter renegotiation')
193minbits_kdc_conf = {'realms': {'$realm': {'pkinit_dh_min_bits': '4096'}}}
194minbits_env = realm.special_env('restrict', True, kdc_conf=minbits_kdc_conf)
195realm.stop_kdc()
196realm.start_kdc(env=minbits_env)
197msgs = ('Sending unauthenticated request',
198        '/Additional pre-authentication required',
199        'Preauthenticating using KDC method data',
200        'Preauth module pkinit (16) (real) returned: 0/Success',
201        ' preauth for next request: PA-FX-COOKIE (133), PA-PK-AS-REQ (16)',
202        '/Key parameters not accepted',
203        'Preauth tryagain input types (16): 109, PA-FX-COOKIE (133)',
204        'trying again with KDC-provided parameters',
205        'Preauth module pkinit (16) tryagain returned: 0/Success',
206        ' preauth for next request: PA-PK-AS-REQ (16), PA-FX-COOKIE (133)')
207realm.pkinit(realm.user_princ, expected_trace=msgs)
208
209# Test enforcement of required freshness tokens.  (We can leave
210# freshness tokens required after this test.)
211mark('freshness token enforcement')
212realm.pkinit(realm.user_princ, flags=['-X', 'disable_freshness=yes'])
213f_env = realm.special_env('freshness', True, kdc_conf=freshness_kdc_conf)
214realm.stop_kdc()
215realm.start_kdc(env=f_env)
216realm.pkinit(realm.user_princ)
217realm.pkinit(realm.user_princ, flags=['-X', 'disable_freshness=yes'],
218             expected_code=1, expected_msg='Preauthentication failed')
219# Anonymous should never require a freshness token.
220realm.kinit('@%s' % realm.realm, flags=['-n', '-X', 'disable_freshness=yes'])
221
222# Run the basic test - PKINIT with FILE: identity, with a password on the key,
223# supplied by the prompter.
224# Expect failure if the responder does nothing, and we have no prompter.
225mark('FILE identity, password on key (prompter)')
226realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % file_enc_identity,
227          '-X', 'X509_user_identity=%s' % file_enc_identity, realm.user_princ],
228          expected_code=2)
229realm.kinit(realm.user_princ,
230            flags=['-X', 'X509_user_identity=%s' % file_enc_identity],
231            password='encrypted')
232realm.klist(realm.user_princ)
233realm.run([kvno, realm.host_princ])
234realm.run(['./adata', realm.host_princ],
235          expected_msg='+97: [indpkinit1, indpkinit2]')
236
237# Run the basic test - PKINIT with FILE: identity, with a password on the key,
238# supplied by the responder.
239# Supply the response in raw form.
240mark('FILE identity, password on key (responder)')
241out = realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % file_enc_identity,
242                 '-r', 'pkinit={"%s": "encrypted"}' % file_enc_identity,
243                 '-X', 'X509_user_identity=%s' % file_enc_identity,
244                 realm.user_princ])
245# Regression test for #8885 (password question asked twice).
246if out.count('OK: ') != 1:
247    fail('Wrong number of responder calls')
248# Supply the response through the convenience API.
249realm.run(['./responder', '-X', 'X509_user_identity=%s' % file_enc_identity,
250           '-p', '%s=%s' % (file_enc_identity, 'encrypted'), realm.user_princ])
251realm.klist(realm.user_princ)
252realm.run([kvno, realm.host_princ])
253
254# PKINIT with DIR: identity, with no password on the key.
255mark('DIR identity, no password')
256os.mkdir(path)
257os.mkdir(path_enc)
258shutil.copy(privkey_pem, os.path.join(path, 'user.key'))
259shutil.copy(privkey_enc_pem, os.path.join(path_enc, 'user.key'))
260shutil.copy(user_pem, os.path.join(path, 'user.crt'))
261shutil.copy(user_pem, os.path.join(path_enc, 'user.crt'))
262realm.kinit(realm.user_princ,
263            flags=['-X', 'X509_user_identity=%s' % dir_identity])
264realm.klist(realm.user_princ)
265realm.run([kvno, realm.host_princ])
266
267# PKINIT with DIR: identity, with a password on the key, supplied by the
268# prompter.
269# Expect failure if the responder does nothing, and we have no prompter.
270mark('DIR identity, password on key (prompter)')
271realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % dir_file_enc_identity,
272           '-X', 'X509_user_identity=%s' % dir_enc_identity, realm.user_princ],
273           expected_code=2)
274realm.kinit(realm.user_princ,
275            flags=['-X', 'X509_user_identity=%s' % dir_enc_identity],
276            password='encrypted')
277realm.klist(realm.user_princ)
278realm.run([kvno, realm.host_princ])
279
280# PKINIT with DIR: identity, with a password on the key, supplied by the
281# responder.
282# Supply the response in raw form.
283mark('DIR identity, password on key (responder)')
284realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % dir_file_enc_identity,
285           '-r', 'pkinit={"%s": "encrypted"}' % dir_file_enc_identity,
286           '-X', 'X509_user_identity=%s' % dir_enc_identity, realm.user_princ])
287# Supply the response through the convenience API.
288realm.run(['./responder', '-X', 'X509_user_identity=%s' % dir_enc_identity,
289           '-p', '%s=%s' % (dir_file_enc_identity, 'encrypted'),
290           realm.user_princ])
291realm.klist(realm.user_princ)
292realm.run([kvno, realm.host_princ])
293
294# PKINIT with PKCS12: identity, with no password on the bundle.
295mark('PKCS12 identity, no password')
296realm.kinit(realm.user_princ,
297            flags=['-X', 'X509_user_identity=%s' % p12_identity])
298realm.klist(realm.user_princ)
299realm.run([kvno, realm.host_princ])
300
301# PKINIT with PKCS12: identity, with a password on the bundle, supplied by the
302# prompter.
303# Expect failure if the responder does nothing, and we have no prompter.
304mark('PKCS12 identity, password on bundle (prompter)')
305realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % p12_enc_identity,
306           '-X', 'X509_user_identity=%s' % p12_enc_identity, realm.user_princ],
307           expected_code=2)
308realm.kinit(realm.user_princ,
309            flags=['-X', 'X509_user_identity=%s' % p12_enc_identity],
310            password='encrypted')
311realm.klist(realm.user_princ)
312realm.run([kvno, realm.host_princ])
313
314# PKINIT with PKCS12: identity, with a password on the bundle, supplied by the
315# responder.
316# Supply the response in raw form.
317mark('PKCS12 identity, password on bundle (responder)')
318realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % p12_enc_identity,
319           '-r', 'pkinit={"%s": "encrypted"}' % p12_enc_identity,
320           '-X', 'X509_user_identity=%s' % p12_enc_identity, realm.user_princ])
321# Supply the response through the convenience API.
322realm.run(['./responder', '-X', 'X509_user_identity=%s' % p12_enc_identity,
323           '-p', '%s=%s' % (p12_enc_identity, 'encrypted'),
324           realm.user_princ])
325realm.klist(realm.user_princ)
326realm.run([kvno, realm.host_princ])
327
328mark('pkinit_cert_match rules')
329
330# Match a single rule.
331rule = '<SAN>^user@KRBTEST.COM$'
332realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
333realm.kinit(realm.user_princ,
334            flags=['-X', 'X509_user_identity=%s' % p12_identity])
335realm.klist(realm.user_princ)
336
337# Regression test for #8670: match a UPN SAN with a single rule.
338rule = '<SAN>^user@krbtest.com$'
339realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
340realm.kinit(realm.user_princ,
341            flags=['-X', 'X509_user_identity=%s' % p12_upn_identity])
342realm.klist(realm.user_princ)
343
344# Match a combined rule (default prefix is &&).
345rule = '<SUBJECT>CN=user$<KU>digitalSignature,keyEncipherment'
346realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
347realm.kinit(realm.user_princ,
348            flags=['-X', 'X509_user_identity=%s' % p12_identity])
349realm.klist(realm.user_princ)
350
351# Fail an && rule.
352rule = '&&<SUBJECT>O=OTHER.COM<SAN>^user@KRBTEST.COM$'
353realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
354msg = 'kinit: Certificate mismatch while getting initial credentials'
355realm.kinit(realm.user_princ,
356            flags=['-X', 'X509_user_identity=%s' % p12_identity],
357            expected_code=1, expected_msg=msg)
358
359# Pass an || rule.
360rule = '||<SUBJECT>O=KRBTEST.COM<SAN>^otheruser@KRBTEST.COM$'
361realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
362realm.kinit(realm.user_princ,
363            flags=['-X', 'X509_user_identity=%s' % p12_identity])
364realm.klist(realm.user_princ)
365
366# Fail an || rule.
367rule = '||<SUBJECT>O=OTHER.COM<SAN>^otheruser@KRBTEST.COM$'
368realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
369msg = 'kinit: Certificate mismatch while getting initial credentials'
370realm.kinit(realm.user_princ,
371            flags=['-X', 'X509_user_identity=%s' % p12_identity],
372            expected_code=1, expected_msg=msg)
373
374# Authorize a client cert with no PKINIT extensions using subject and
375# issuer.  (Relies on EKU checking being turned off.)
376rule = '&&<SUBJECT>CN=user$<ISSUER>O=MIT,'
377realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
378realm.kinit(realm.user_princ,
379            flags=['-X', 'X509_user_identity=%s' % p12_generic_identity])
380realm.klist(realm.user_princ)
381
382# Regression test for #8726: null deref when parsing a FILE residual
383# beginning with a comma.
384realm.kinit(realm.user_princ, flags=['-X', 'X509_user_identity=,'],
385            expected_code=1, expected_msg='Preauthentication failed while')
386
387softpkcs11rc = os.path.join(os.getcwd(), 'testdir', 'soft-pkcs11.rc')
388realm.env['SOFTPKCS11RC'] = softpkcs11rc
389
390# PKINIT with PKCS11: identity, with no need for a PIN.
391mark('PKCS11 identity, no PIN')
392conf = open(softpkcs11rc, 'w')
393conf.write("%s\t%s\t%s\t%s\n" % ('user', 'user token', user_pem, privkey_pem))
394conf.close()
395# Expect to succeed without having to supply any more information.
396realm.kinit(realm.user_princ,
397            flags=['-X', 'X509_user_identity=%s' % p11_identity])
398realm.klist(realm.user_princ)
399realm.run([kvno, realm.host_princ])
400
401# PKINIT with PKCS11: identity, with a PIN supplied by the prompter.
402mark('PKCS11 identity, with PIN (prompter)')
403os.remove(softpkcs11rc)
404conf = open(softpkcs11rc, 'w')
405conf.write("%s\t%s\t%s\t%s\n" % ('user', 'user token', user_pem,
406                                 privkey_enc_pem))
407conf.close()
408# Expect failure if the responder does nothing, and there's no prompter
409realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % p11_token_identity,
410           '-X', 'X509_user_identity=%s' % p11_identity, realm.user_princ],
411          expected_code=2)
412realm.kinit(realm.user_princ,
413            flags=['-X', 'X509_user_identity=%s' % p11_identity],
414            password='encrypted')
415realm.klist(realm.user_princ)
416realm.run([kvno, realm.host_princ])
417
418# Supply the wrong PIN.
419mark('PKCS11 identity, wrong PIN')
420expected_trace = ('PKINIT client has no configured identity; giving up',)
421realm.kinit(realm.user_princ,
422            flags=['-X', 'X509_user_identity=%s' % p11_identity],
423            password='wrong', expected_code=1, expected_trace=expected_trace)
424
425# PKINIT with PKCS11: identity, with a PIN supplied by the responder.
426# Supply the response in raw form.
427mark('PKCS11 identity, with PIN (responder)')
428realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % p11_token_identity,
429           '-r', 'pkinit={"%s": "encrypted"}' % p11_token_identity,
430           '-X', 'X509_user_identity=%s' % p11_identity, realm.user_princ])
431# Supply the response through the convenience API.
432realm.run(['./responder', '-X', 'X509_user_identity=%s' % p11_identity,
433           '-p', '%s=%s' % (p11_token_identity, 'encrypted'),
434           realm.user_princ])
435realm.klist(realm.user_princ)
436realm.run([kvno, realm.host_princ])
437
438success('PKINIT tests')
439