ctdb-scripts: Improve update and listing code
[samba4-gss.git] / python / samba / tests / krb5 / kpasswd_tests.py
blob6f0b36a25f631ce30e53d9b04cb7f92155db1035
1 #!/usr/bin/env python3
2 # Unix SMB/CIFS implementation.
3 # Copyright (C) Stefan Metzmacher 2020
4 # Copyright (C) Catalyst.Net Ltd
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 import sys
21 import os
23 sys.path.insert(0, 'bin/python')
24 os.environ['PYTHONUNBUFFERED'] = '1'
26 from functools import partial
28 from samba import generate_random_password
29 from samba.dcerpc import krb5pac
30 from samba.sd_utils import SDUtils
32 from samba.tests.krb5.kdc_base_test import KDCBaseTest
33 from samba.tests.krb5.rfc4120_constants import (
34 KDC_ERR_TGT_REVOKED,
35 KDC_ERR_TKT_EXPIRED,
36 KPASSWD_ACCESSDENIED,
37 KPASSWD_AUTHERROR,
38 KPASSWD_HARDERROR,
39 KPASSWD_INITIAL_FLAG_NEEDED,
40 KPASSWD_MALFORMED,
41 KPASSWD_SOFTERROR,
42 KPASSWD_SUCCESS,
43 NT_PRINCIPAL,
44 NT_SRV_INST,
47 global_asn1_print = False
48 global_hexdump = False
51 # Note: these tests do not pass on Windows, which returns different error codes
52 # to the ones we have chosen, and does not always return additional error data.
53 class KpasswdTests(KDCBaseTest):
55 def setUp(self):
56 super().setUp()
57 self.do_asn1_print = global_asn1_print
58 self.do_hexdump = global_hexdump
60 samdb = self.get_samdb()
62 # Get the old 'dSHeuristics' if it was set
63 dsheuristics = samdb.get_dsheuristics()
65 # Reset the 'dSHeuristics' as they were before
66 self.addCleanup(samdb.set_dsheuristics, dsheuristics)
68 # Set the 'dSHeuristics' to activate the correct 'userPassword'
69 # behaviour
70 samdb.set_dsheuristics('000000001')
72 # Get the old 'minPwdAge'
73 minPwdAge = samdb.get_minPwdAge()
75 # Reset the 'minPwdAge' as it was before
76 self.addCleanup(samdb.set_minPwdAge, minPwdAge)
78 # Set it temporarily to '0'
79 samdb.set_minPwdAge('0')
81 def _get_creds(self, expired=False):
82 opts = {
83 'expired_password': expired
86 # Create the account.
87 creds = self.get_cached_creds(account_type=self.AccountType.USER,
88 opts=opts,
89 use_cache=False)
91 return creds
93 def get_ticket_lifetime(self, ticket):
94 enc_part = ticket.ticket_private
96 authtime = enc_part['authtime']
97 starttime = enc_part.get('starttime', authtime)
98 endtime = enc_part['endtime']
100 starttime = self.get_EpochFromKerberosTime(starttime)
101 endtime = self.get_EpochFromKerberosTime(endtime)
103 return endtime - starttime
105 # Test setting a password with kpasswd.
106 def test_kpasswd_set(self):
107 # Create an account for testing.
108 creds = self._get_creds()
110 # Get an initial ticket to kpasswd.
111 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
112 kdc_options='0')
114 expected_code = KPASSWD_SUCCESS
115 expected_msg = b'Password changed'
117 # Set the password.
118 new_password = generate_random_password(32, 32)
119 self.kpasswd_exchange(ticket,
120 new_password,
121 expected_code,
122 expected_msg,
123 mode=self.KpasswdMode.SET)
125 # Test the newly set password.
126 creds.update_password(new_password)
127 self.get_tgt(creds, fresh=True)
129 # Test changing a password with kpasswd.
130 def test_kpasswd_change(self):
131 # Create an account for testing.
132 creds = self._get_creds()
134 # Get an initial ticket to kpasswd.
135 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
136 kdc_options='0')
138 expected_code = KPASSWD_SUCCESS
139 expected_msg = b'Password changed'
141 # Change the password.
142 new_password = generate_random_password(32, 32)
143 self.kpasswd_exchange(ticket,
144 new_password,
145 expected_code,
146 expected_msg,
147 mode=self.KpasswdMode.CHANGE)
149 # Test the newly set password.
150 creds.update_password(new_password)
151 self.get_tgt(creds, fresh=True)
153 # Test kpasswd without setting the canonicalize option.
154 def test_kpasswd_no_canonicalize(self):
155 # Create an account for testing.
156 creds = self._get_creds()
158 sname = self.get_kpasswd_sname()
160 # Get an initial ticket to kpasswd.
161 ticket = self.get_tgt(creds, sname=sname,
162 kdc_options='0')
164 expected_code = KPASSWD_SUCCESS
165 expected_msg = b'Password changed'
167 # Set the password.
168 new_password = generate_random_password(32, 32)
169 self.kpasswd_exchange(ticket,
170 new_password,
171 expected_code,
172 expected_msg,
173 mode=self.KpasswdMode.SET)
175 creds.update_password(new_password)
177 # Get an initial ticket to kpasswd.
178 ticket = self.get_tgt(creds, sname=sname,
179 kdc_options='0')
181 # Change the password.
182 new_password = generate_random_password(32, 32)
183 self.kpasswd_exchange(ticket,
184 new_password,
185 expected_code,
186 expected_msg,
187 mode=self.KpasswdMode.CHANGE)
189 # Test kpasswd with the canonicalize option reset and a non-canonical
190 # (by conversion to title case) realm.
191 def test_kpasswd_no_canonicalize_realm_case(self):
192 # Create an account for testing.
193 creds = self._get_creds()
195 sname = self.get_kpasswd_sname()
196 realm = creds.get_realm().capitalize() # We use a title-cased realm.
198 # Get an initial ticket to kpasswd.
199 ticket = self.get_tgt(creds, sname=sname,
200 realm=realm,
201 kdc_options='0')
203 expected_code = KPASSWD_SUCCESS
204 expected_msg = b'Password changed'
206 # Set the password.
207 new_password = generate_random_password(32, 32)
208 self.kpasswd_exchange(ticket,
209 new_password,
210 expected_code,
211 expected_msg,
212 mode=self.KpasswdMode.SET)
214 creds.update_password(new_password)
216 # Get an initial ticket to kpasswd.
217 ticket = self.get_tgt(creds, sname=sname,
218 realm=realm,
219 kdc_options='0')
221 # Change the password.
222 new_password = generate_random_password(32, 32)
223 self.kpasswd_exchange(ticket,
224 new_password,
225 expected_code,
226 expected_msg,
227 mode=self.KpasswdMode.CHANGE)
229 # Test kpasswd with the canonicalize option set.
230 def test_kpasswd_canonicalize(self):
231 # Create an account for testing.
232 creds = self._get_creds()
234 # Get an initial ticket to kpasswd. We set the canonicalize flag here.
235 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
236 kdc_options='canonicalize')
238 expected_code = KPASSWD_SUCCESS
239 expected_msg = b'Password changed'
241 # Set the password.
242 new_password = generate_random_password(32, 32)
243 self.kpasswd_exchange(ticket,
244 new_password,
245 expected_code,
246 expected_msg,
247 mode=self.KpasswdMode.SET)
249 creds.update_password(new_password)
251 # Get an initial ticket to kpasswd. We set the canonicalize flag here.
252 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
253 kdc_options='canonicalize')
255 # Change the password.
256 new_password = generate_random_password(32, 32)
257 self.kpasswd_exchange(ticket,
258 new_password,
259 expected_code,
260 expected_msg,
261 mode=self.KpasswdMode.CHANGE)
263 # Test kpasswd with the canonicalize option set and a non-canonical (by
264 # conversion to title case) realm.
265 def test_kpasswd_canonicalize_realm_case(self):
266 # Create an account for testing.
267 creds = self._get_creds()
269 sname = self.get_kpasswd_sname()
270 realm = creds.get_realm().capitalize() # We use a title-cased realm.
272 # Get an initial ticket to kpasswd. We set the canonicalize flag here.
273 ticket = self.get_tgt(creds, sname=sname,
274 realm=realm,
275 kdc_options='canonicalize')
277 expected_code = KPASSWD_SUCCESS
278 expected_msg = b'Password changed'
280 # Set the password.
281 new_password = generate_random_password(32, 32)
282 self.kpasswd_exchange(ticket,
283 new_password,
284 expected_code,
285 expected_msg,
286 mode=self.KpasswdMode.SET)
288 creds.update_password(new_password)
290 # Get an initial ticket to kpasswd. We set the canonicalize flag here.
291 ticket = self.get_tgt(creds, sname=sname,
292 realm=realm,
293 kdc_options='canonicalize')
295 # Change the password.
296 new_password = generate_random_password(32, 32)
297 self.kpasswd_exchange(ticket,
298 new_password,
299 expected_code,
300 expected_msg,
301 mode=self.KpasswdMode.CHANGE)
303 # Test kpasswd rejects a password that does not meet complexity
304 # requirements.
305 def test_kpasswd_too_weak(self):
306 # Create an account for testing.
307 creds = self._get_creds()
309 # Get an initial ticket to kpasswd.
310 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
311 kdc_options='0')
313 expected_code = KPASSWD_SOFTERROR
314 expected_msg = b'Password does not meet complexity requirements'
316 # Set the password.
317 new_password = 'password'
318 self.kpasswd_exchange(ticket,
319 new_password,
320 expected_code,
321 expected_msg,
322 mode=self.KpasswdMode.SET)
324 # Change the password.
325 self.kpasswd_exchange(ticket,
326 new_password,
327 expected_code,
328 expected_msg,
329 mode=self.KpasswdMode.CHANGE)
331 # Test kpasswd rejects an empty new password.
332 def test_kpasswd_empty(self):
333 # Create an account for testing.
334 creds = self._get_creds()
336 # Get an initial ticket to kpasswd.
337 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
338 kdc_options='0')
340 expected_code = KPASSWD_SOFTERROR, KPASSWD_HARDERROR
341 expected_msg = (b'Password too short, password must be at least 7 '
342 b'characters long.',
343 b'String conversion failed!')
345 # Set the password.
346 new_password = ''
347 self.kpasswd_exchange(ticket,
348 new_password,
349 expected_code,
350 expected_msg,
351 mode=self.KpasswdMode.SET)
353 expected_code = KPASSWD_HARDERROR
354 expected_msg = b'String conversion failed!'
356 # Change the password.
357 self.kpasswd_exchange(ticket,
358 new_password,
359 expected_code,
360 expected_msg,
361 mode=self.KpasswdMode.CHANGE)
363 # Test kpasswd rejects a request that does not include a random sequence
364 # number.
365 def test_kpasswd_no_seq_number(self):
366 # Create an account for testing.
367 creds = self._get_creds()
369 # Get an initial ticket to kpasswd.
370 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
371 kdc_options='0')
373 expected_code = KPASSWD_HARDERROR
374 expected_msg = b'gensec_unwrap failed - NT_STATUS_ACCESS_DENIED\n'
376 # Set the password.
377 new_password = generate_random_password(32, 32)
378 self.kpasswd_exchange(ticket,
379 new_password,
380 expected_code,
381 expected_msg,
382 mode=self.KpasswdMode.SET,
383 send_seq_number=False)
385 # Change the password.
386 self.kpasswd_exchange(ticket,
387 new_password,
388 expected_code,
389 expected_msg,
390 mode=self.KpasswdMode.CHANGE,
391 send_seq_number=False)
393 # Test kpasswd rejects a ticket issued by an RODC.
394 def test_kpasswd_from_rodc(self):
395 # Create an account for testing.
396 creds = self._get_creds()
398 # Get an initial ticket to kpasswd.
399 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
400 kdc_options='0')
402 # Have the ticket be issued by the RODC.
403 ticket = self.issued_by_rodc(ticket)
405 expected_code = KPASSWD_HARDERROR
406 expected_msg = b'gensec_update failed - NT_STATUS_LOGON_FAILURE\n'
408 # Set the password.
409 new_password = generate_random_password(32, 32)
410 self.kpasswd_exchange(ticket,
411 new_password,
412 expected_code,
413 expected_msg,
414 mode=self.KpasswdMode.SET)
416 # Change the password.
417 self.kpasswd_exchange(ticket,
418 new_password,
419 expected_code,
420 expected_msg,
421 mode=self.KpasswdMode.CHANGE)
423 # Test setting a password, specifying the principal of the target user.
424 def test_kpasswd_set_target_princ_only(self):
425 # Create an account for testing.
426 creds = self._get_creds()
427 username = creds.get_username()
429 cname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
430 names=username.split('/'))
432 # Get an initial ticket to kpasswd.
433 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
434 kdc_options='0')
436 expected_code = KPASSWD_MALFORMED
437 expected_msg = (b'Realm and principal must be both present, or '
438 b'neither present',
439 b'Failed to decode packet')
441 # Change the password.
442 new_password = generate_random_password(32, 32)
443 self.kpasswd_exchange(ticket,
444 new_password,
445 expected_code,
446 expected_msg,
447 mode=self.KpasswdMode.SET,
448 target_princ=cname)
450 # Test that kpasswd rejects a password set specifying only the realm of the
451 # target user.
452 def test_kpasswd_set_target_realm_only(self):
453 # Create an account for testing.
454 creds = self._get_creds()
456 # Get an initial ticket to kpasswd.
457 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
458 kdc_options='0')
460 expected_code = KPASSWD_MALFORMED, KPASSWD_ACCESSDENIED
461 expected_msg = (b'Realm and principal must be both present, or '
462 b'neither present',
463 b'Failed to decode packet',
464 b'No such user when changing password')
466 # Change the password.
467 new_password = generate_random_password(32, 32)
468 self.kpasswd_exchange(ticket,
469 new_password,
470 expected_code,
471 expected_msg,
472 mode=self.KpasswdMode.SET,
473 target_realm=creds.get_realm())
475 # Show that a user cannot set a password, specifying both principal and
476 # realm of the target user, without having control access.
477 def test_kpasswd_set_target_princ_and_realm_no_access(self):
478 # Create an account for testing.
479 creds = self._get_creds()
480 username = creds.get_username()
482 cname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
483 names=username.split('/'))
485 # Get an initial ticket to kpasswd.
486 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
487 kdc_options='0')
489 expected_code = KPASSWD_ACCESSDENIED
490 expected_msg = b'Not permitted to change password'
492 # Change the password.
493 new_password = generate_random_password(32, 32)
494 self.kpasswd_exchange(ticket,
495 new_password,
496 expected_code,
497 expected_msg,
498 mode=self.KpasswdMode.SET,
499 target_princ=cname,
500 target_realm=creds.get_realm())
502 # Test setting a password, specifying both principal and realm of the
503 # target user, when the user has control access on their account.
504 def test_kpasswd_set_target_princ_and_realm_access(self):
505 # Create an account for testing.
506 creds = self._get_creds()
507 username = creds.get_username()
508 tgt = self.get_tgt(creds)
510 cname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
511 names=username.split('/'))
513 samdb = self.get_samdb()
514 sd_utils = SDUtils(samdb)
516 user_dn = creds.get_dn()
517 user_sid = creds.get_sid()
519 # Give the user control access on their account.
520 ace = f'(A;;CR;;;{user_sid})'
521 sd_utils.dacl_add_ace(user_dn, ace)
523 # Get a non-initial ticket to kpasswd. Since we have the right to
524 # change the account's password, we don't need an initial ticket.
525 krbtgt_creds = self.get_krbtgt_creds()
526 ticket = self.get_service_ticket(tgt,
527 krbtgt_creds,
528 service='kadmin',
529 target_name='changepw',
530 kdc_options='0')
532 expected_code = KPASSWD_SUCCESS
533 expected_msg = b'Password changed'
535 # Change the password.
536 new_password = generate_random_password(32, 32)
537 self.kpasswd_exchange(ticket,
538 new_password,
539 expected_code,
540 expected_msg,
541 mode=self.KpasswdMode.SET,
542 target_princ=cname,
543 target_realm=creds.get_realm())
545 # Test setting a password when the existing password has expired.
546 def test_kpasswd_set_expired_password(self):
547 # Create an account for testing, with an expired password.
548 creds = self._get_creds(expired=True)
550 # Get an initial ticket to kpasswd.
551 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
552 kdc_options='0')
554 expected_code = KPASSWD_SUCCESS
555 expected_msg = b'Password changed'
557 # Set the password.
558 new_password = generate_random_password(32, 32)
559 self.kpasswd_exchange(ticket,
560 new_password,
561 expected_code,
562 expected_msg,
563 mode=self.KpasswdMode.SET)
565 # Test changing a password when the existing password has expired.
566 def test_kpasswd_change_expired_password(self):
567 # Create an account for testing, with an expired password.
568 creds = self._get_creds(expired=True)
570 # Get an initial ticket to kpasswd.
571 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
572 kdc_options='0')
574 expected_code = KPASSWD_SUCCESS
575 expected_msg = b'Password changed'
577 # Change the password.
578 new_password = generate_random_password(32, 32)
579 self.kpasswd_exchange(ticket,
580 new_password,
581 expected_code,
582 expected_msg,
583 mode=self.KpasswdMode.CHANGE)
585 # Check the lifetime of a kpasswd ticket is not more than two minutes.
586 def test_kpasswd_ticket_lifetime(self):
587 # Create an account for testing.
588 creds = self._get_creds()
590 # Get an initial ticket to kpasswd.
591 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
592 kdc_options='0')
594 # Check the lifetime of the ticket is equal to two minutes.
595 lifetime = self.get_ticket_lifetime(ticket)
596 self.assertEqual(2 * 60, lifetime)
598 # Ensure we cannot perform a TGS-REQ with a kpasswd ticket.
599 def test_kpasswd_ticket_tgs(self):
600 creds = self.get_client_creds()
602 # Get an initial ticket to kpasswd.
603 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
604 kdc_options='0')
606 # Change the sname of the ticket to match that of a TGT.
607 realm = creds.get_realm()
608 krbtgt_sname = self.PrincipalName_create(name_type=NT_SRV_INST,
609 names=['krbtgt', realm])
610 ticket.set_sname(krbtgt_sname)
612 # Try to use that ticket to get a service ticket.
613 service_creds = self.get_service_creds()
615 # This fails due to missing REQUESTER_SID buffer.
616 self._make_tgs_request(creds, service_creds, ticket,
617 expect_error=(KDC_ERR_TGT_REVOKED,
618 KDC_ERR_TKT_EXPIRED))
620 # Ensure we cannot perform a TGS-REQ with a kpasswd ticket containing a
621 # requester SID and having a remaining lifetime of two minutes.
622 def test_kpasswd_ticket_requester_sid_tgs(self):
623 creds = self.get_client_creds()
625 # Get an initial ticket to kpasswd.
626 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
627 kdc_options='0')
629 # Change the sname of the ticket to match that of a TGT.
630 realm = creds.get_realm()
631 krbtgt_sname = self.PrincipalName_create(name_type=NT_SRV_INST,
632 names=['krbtgt', realm])
633 ticket.set_sname(krbtgt_sname)
635 # Modify the ticket to add a requester SID and give it two minutes to
636 # live.
637 ticket = self.modify_lifetime(ticket,
638 lifetime=2 * 60,
639 requester_sid=creds.get_sid())
641 # Try to use that ticket to get a service ticket.
642 service_creds = self.get_service_creds()
644 # This fails due to the lifetime being too short.
645 self._make_tgs_request(creds, service_creds, ticket,
646 expect_error=KDC_ERR_TKT_EXPIRED)
648 # Show we can perform a TGS-REQ with a kpasswd ticket containing a
649 # requester SID if the remaining lifetime exceeds two minutes.
650 def test_kpasswd_ticket_requester_sid_lifetime_tgs(self):
651 creds = self.get_client_creds()
653 # Get an initial ticket to kpasswd.
654 ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(),
655 kdc_options='0')
657 # Change the sname of the ticket to match that of a TGT.
658 realm = creds.get_realm()
659 krbtgt_sname = self.PrincipalName_create(name_type=NT_SRV_INST,
660 names=['krbtgt', realm])
661 ticket.set_sname(krbtgt_sname)
663 # Modify the ticket to add a requester SID and give it two minutes and
664 # ten seconds to live.
665 ticket = self.modify_lifetime(ticket,
666 lifetime=2 * 60 + 10,
667 requester_sid=creds.get_sid())
669 # Try to use that ticket to get a service ticket.
670 service_creds = self.get_service_creds()
672 # This succeeds.
673 self._make_tgs_request(creds, service_creds, ticket,
674 expect_error=False)
676 # Show that we cannot provide a TGT to kpasswd to change the password.
677 def test_kpasswd_tgt(self):
678 # Create an account for testing, and get a TGT.
679 creds = self._get_creds()
680 tgt = self.get_tgt(creds)
682 # Change the sname of the ticket to match that of kadmin/changepw.
683 tgt.set_sname(self.get_kpasswd_sname())
685 expected_code = KPASSWD_AUTHERROR
686 expected_msg = b'A TGT may not be used as a ticket to kpasswd'
688 # Set the password.
689 new_password = generate_random_password(32, 32)
690 self.kpasswd_exchange(tgt,
691 new_password,
692 expected_code,
693 expected_msg,
694 mode=self.KpasswdMode.SET)
696 # Change the password.
697 self.kpasswd_exchange(tgt,
698 new_password,
699 expected_code,
700 expected_msg,
701 mode=self.KpasswdMode.CHANGE)
703 # Show that we cannot provide a TGT to kpasswd that was obtained with a
704 # single‐component principal.
705 def test_kpasswd_tgt_single_component_krbtgt(self):
706 # Create an account for testing.
707 creds = self._get_creds()
709 # Create a single‐component principal of the form ‘krbtgt@REALM’.
710 sname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
711 names=['krbtgt'])
713 # Don’t request canonicalization.
714 kdc_options = 'forwardable,renewable,renewable-ok'
716 # Get a TGT.
717 tgt = self.get_tgt(creds, sname=sname, kdc_options=kdc_options)
719 # Change the sname of the ticket to match that of kadmin/changepw.
720 tgt.set_sname(self.get_kpasswd_sname())
722 expected_code = KPASSWD_AUTHERROR
723 expected_msg = b'A TGT may not be used as a ticket to kpasswd'
725 # Set the password.
726 new_password = generate_random_password(32, 32)
727 self.kpasswd_exchange(tgt,
728 new_password,
729 expected_code,
730 expected_msg,
731 mode=self.KpasswdMode.SET)
733 # Change the password.
734 self.kpasswd_exchange(tgt,
735 new_password,
736 expected_code,
737 expected_msg,
738 mode=self.KpasswdMode.CHANGE)
740 # Test that kpasswd rejects requests with a service ticket.
741 def test_kpasswd_non_initial(self):
742 # Create an account for testing, and get a TGT.
743 creds = self._get_creds()
744 tgt = self.get_tgt(creds)
746 # Get a non-initial ticket to kpasswd.
747 krbtgt_creds = self.get_krbtgt_creds()
748 ticket = self.get_service_ticket(tgt,
749 krbtgt_creds,
750 service='kadmin',
751 target_name='changepw',
752 kdc_options='0')
754 expected_code = KPASSWD_INITIAL_FLAG_NEEDED
755 expected_msg = b'Expected an initial ticket'
757 # Set the password.
758 new_password = generate_random_password(32, 32)
759 self.kpasswd_exchange(ticket,
760 new_password,
761 expected_code,
762 expected_msg,
763 mode=self.KpasswdMode.SET)
765 # Change the password.
766 self.kpasswd_exchange(ticket,
767 new_password,
768 expected_code,
769 expected_msg,
770 mode=self.KpasswdMode.CHANGE)
772 # Show that kpasswd accepts requests with a service ticket modified to set
773 # the 'initial' flag.
774 def test_kpasswd_initial(self):
775 # Create an account for testing, and get a TGT.
776 creds = self._get_creds()
778 krbtgt_creds = self.get_krbtgt_creds()
780 # Get a service ticket, and modify it to set the 'initial' flag.
781 def get_ticket():
782 tgt = self.get_tgt(creds, fresh=True)
784 # Get a non-initial ticket to kpasswd.
785 ticket = self.get_service_ticket(tgt,
786 krbtgt_creds,
787 service='kadmin',
788 target_name='changepw',
789 kdc_options='0',
790 fresh=True)
792 set_initial_flag = partial(self.modify_ticket_flag, flag='initial',
793 value=True)
795 checksum_keys = self.get_krbtgt_checksum_key()
796 return self.modified_ticket(ticket,
797 modify_fn=set_initial_flag,
798 checksum_keys=checksum_keys)
800 expected_code = KPASSWD_SUCCESS
801 expected_msg = b'Password changed'
803 ticket = get_ticket()
805 # Set the password.
806 new_password = generate_random_password(32, 32)
807 self.kpasswd_exchange(ticket,
808 new_password,
809 expected_code,
810 expected_msg,
811 mode=self.KpasswdMode.SET)
813 creds.update_password(new_password)
814 ticket = get_ticket()
816 # Change the password.
817 new_password = generate_random_password(32, 32)
818 self.kpasswd_exchange(ticket,
819 new_password,
820 expected_code,
821 expected_msg,
822 mode=self.KpasswdMode.CHANGE)
824 # Test that kpasswd rejects requests where the ticket is encrypted with a
825 # key other than the krbtgt's.
826 def test_kpasswd_wrong_key(self):
827 # Create an account for testing.
828 creds = self._get_creds()
830 sname = self.get_kpasswd_sname()
832 # Get an initial ticket to kpasswd.
833 ticket = self.get_tgt(creds, sname=sname,
834 kdc_options='0')
836 # Get a key belonging to the Administrator account.
837 admin_creds = self.get_admin_creds()
838 admin_key = self.TicketDecryptionKey_from_creds(admin_creds)
839 self.assertIsNotNone(admin_key.kvno,
840 'a kvno is required to tell the DB '
841 'which key to look up.')
842 checksum_keys = {
843 krb5pac.PAC_TYPE_KDC_CHECKSUM: admin_key,
846 # Re-encrypt the ticket using the Administrator's key.
847 ticket = self.modified_ticket(ticket,
848 new_ticket_key=admin_key,
849 checksum_keys=checksum_keys)
851 # Set the sname of the ticket to that of the Administrator account.
852 admin_sname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
853 names=['Administrator'])
854 ticket.set_sname(admin_sname)
856 expected_code = KPASSWD_HARDERROR
857 expected_msg = b'gensec_update failed - NT_STATUS_LOGON_FAILURE\n'
859 # Set the password.
860 new_password = generate_random_password(32, 32)
861 self.kpasswd_exchange(ticket,
862 new_password,
863 expected_code,
864 expected_msg,
865 mode=self.KpasswdMode.SET)
867 # Change the password.
868 self.kpasswd_exchange(ticket,
869 new_password,
870 expected_code,
871 expected_msg,
872 mode=self.KpasswdMode.CHANGE)
874 def test_kpasswd_wrong_key_service(self):
875 # Create an account for testing.
876 creds = self.get_cached_creds(account_type=self.AccountType.COMPUTER,
877 use_cache=False)
879 sname = self.get_kpasswd_sname()
881 # Get an initial ticket to kpasswd.
882 ticket = self.get_tgt(creds, sname=sname,
883 kdc_options='0')
885 # Get a key belonging to our account.
886 our_key = self.TicketDecryptionKey_from_creds(creds)
887 self.assertIsNotNone(our_key.kvno,
888 'a kvno is required to tell the DB '
889 'which key to look up.')
890 checksum_keys = {
891 krb5pac.PAC_TYPE_KDC_CHECKSUM: our_key,
894 # Re-encrypt the ticket using our key.
895 ticket = self.modified_ticket(ticket,
896 new_ticket_key=our_key,
897 checksum_keys=checksum_keys)
899 # Set the sname of the ticket to that of our account.
900 username = creds.get_username()
901 sname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
902 names=username.split('/'))
903 ticket.set_sname(sname)
905 expected_code = KPASSWD_HARDERROR
906 expected_msg = b'gensec_update failed - NT_STATUS_LOGON_FAILURE\n'
908 # Set the password.
909 new_password = generate_random_password(32, 32)
910 self.kpasswd_exchange(ticket,
911 new_password,
912 expected_code,
913 expected_msg,
914 mode=self.KpasswdMode.SET)
916 # Change the password.
917 self.kpasswd_exchange(ticket,
918 new_password,
919 expected_code,
920 expected_msg,
921 mode=self.KpasswdMode.CHANGE)
923 # Test that kpasswd rejects requests where the ticket is encrypted with a
924 # key belonging to a server account other than the krbtgt.
925 def test_kpasswd_wrong_key_server(self):
926 # Create an account for testing.
927 creds = self._get_creds()
929 sname = self.get_kpasswd_sname()
931 # Get an initial ticket to kpasswd.
932 ticket = self.get_tgt(creds, sname=sname,
933 kdc_options='0')
935 # Get a key belonging to the DC's account.
936 dc_creds = self.get_dc_creds()
937 dc_key = self.TicketDecryptionKey_from_creds(dc_creds)
938 self.assertIsNotNone(dc_key.kvno,
939 'a kvno is required to tell the DB '
940 'which key to look up.')
941 checksum_keys = {
942 krb5pac.PAC_TYPE_KDC_CHECKSUM: dc_key,
945 # Re-encrypt the ticket using the DC's key.
946 ticket = self.modified_ticket(ticket,
947 new_ticket_key=dc_key,
948 checksum_keys=checksum_keys)
950 # Set the sname of the ticket to that of the DC's account.
951 dc_username = dc_creds.get_username()
952 dc_sname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
953 names=dc_username.split('/'))
954 ticket.set_sname(dc_sname)
956 expected_code = KPASSWD_HARDERROR
957 expected_msg = b'gensec_update failed - NT_STATUS_LOGON_FAILURE\n'
959 # Set the password.
960 new_password = generate_random_password(32, 32)
961 self.kpasswd_exchange(ticket,
962 new_password,
963 expected_code,
964 expected_msg,
965 mode=self.KpasswdMode.SET)
967 # Change the password.
968 self.kpasswd_exchange(ticket,
969 new_password,
970 expected_code,
971 expected_msg,
972 mode=self.KpasswdMode.CHANGE)
975 if __name__ == '__main__':
976 global_asn1_print = False
977 global_hexdump = False
978 import unittest
979 unittest.main()