ctdb-scripts: Improve update and listing code
[samba4-gss.git] / python / samba / tests / krb5 / claims_tests.py
blob074147e5afe69a2f8a1bbb6df41ba9dce9287ea1
1 #!/usr/bin/env python3
2 # Unix SMB/CIFS implementation.
3 # Copyright (C) Stefan Metzmacher 2020
4 # Copyright (C) Catalyst.Net Ltd 2022
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 import re
27 import ldb
29 from samba.dcerpc import claims, krb5pac, security
30 from samba.ndr import ndr_pack
32 from samba.tests import DynamicTestCase, env_get_var_value
33 from samba.tests.krb5 import kcrypto
34 from samba.tests.krb5.kcrypto import Enctype
35 from samba.tests.krb5.kdc_base_test import GroupType, KDCBaseTest, Principal
36 from samba.tests.krb5.raw_testcase import Krb5EncryptionKey, RawKerberosTest
37 from samba.tests.krb5.rfc4120_constants import (
38 AES256_CTS_HMAC_SHA1_96,
39 ARCFOUR_HMAC_MD5,
40 KRB_TGS_REP,
41 NT_PRINCIPAL,
43 import samba.tests.krb5.rfc4120_pyasn1 as krb5_asn1
45 SidType = RawKerberosTest.SidType
47 global_asn1_print = False
48 global_hexdump = False
51 class UnorderedList(tuple):
52 def __eq__(self, other):
53 if not isinstance(other, UnorderedList):
54 raise AssertionError('unexpected comparison attempt')
55 return sorted(self) == sorted(other)
57 def __hash__(self):
58 return hash(tuple(sorted(self)))
61 @DynamicTestCase
62 class ClaimsTests(KDCBaseTest):
63 # Placeholder objects that represent accounts undergoing testing.
64 user = object()
65 mach = object()
67 # Constants for group SID attributes.
68 default_attrs = security.SE_GROUP_DEFAULT_FLAGS
69 resource_attrs = default_attrs | security.SE_GROUP_RESOURCE
71 asserted_identity = security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY
72 compounded_auth = security.SID_COMPOUNDED_AUTHENTICATION
74 @classmethod
75 def setUpClass(cls):
76 super().setUpClass()
78 cls._search_iterator = None
80 def setUp(self):
81 super().setUp()
82 self.do_asn1_print = global_asn1_print
83 self.do_hexdump = global_hexdump
85 def get_sample_dn(self):
86 if self._search_iterator is None:
87 samdb = self.get_samdb()
88 type(self)._search_iterator = samdb.search_iterator()
90 return str(next(self._search_iterator).dn)
92 def get_binary_dn(self):
93 return 'B:8:01010101:' + self.get_sample_dn()
95 def setup_claims(self, all_claims):
96 expected_claims = {}
97 unexpected_claims = set()
99 details = {}
100 mod_msg = ldb.Message()
101 security_desc = None
103 for claim in all_claims:
104 # Make a copy to avoid modifying the original.
105 claim = dict(claim)
107 claim_id = self.get_new_username()
109 expected = claim.pop('expected', False)
110 expected_values = claim.pop('expected_values', None)
111 if not expected:
112 self.assertIsNone(expected_values,
113 'claim not expected, '
114 'but expected values provided')
116 values = claim.pop('values', None)
117 if values is not None:
118 def get_placeholder(val):
119 if val is self.sample_dn:
120 return self.get_sample_dn()
121 elif val is self.binary_dn:
122 return self.get_binary_dn()
123 else:
124 return val
126 def ldb_transform(val):
127 if val is True:
128 return 'TRUE'
129 elif val is False:
130 return 'FALSE'
131 elif isinstance(val, int):
132 return str(val)
133 else:
134 return val
136 values_type = type(values)
137 values = values_type(map(get_placeholder, values))
138 transformed_values = values_type(map(ldb_transform, values))
140 attribute = claim['attribute']
141 if attribute in details:
142 self.assertEqual(details[attribute], transformed_values,
143 'conflicting values set for attribute')
144 details[attribute] = transformed_values
146 readable = claim.pop('readable', True)
147 if not readable:
148 if security_desc is None:
149 security_desc = security.descriptor()
151 # Deny all read property access to the attribute.
152 ace = security.ace()
153 ace.type = security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
154 ace.access_mask = security.SEC_ADS_READ_PROP
155 ace.trustee = security.dom_sid(security.SID_WORLD)
156 ace.object.flags |= security.SEC_ACE_OBJECT_TYPE_PRESENT
157 ace.object.type = self.get_schema_id_guid_from_attribute(
158 attribute)
160 security_desc.dacl_add(ace)
162 if expected_values is None:
163 expected_values = values
165 mod_values = claim.pop('mod_values', None)
166 if mod_values is not None:
167 flag = (ldb.FLAG_MOD_REPLACE
168 if values is not None else ldb.FLAG_MOD_ADD)
169 mod_msg[attribute] = ldb.MessageElement(mod_values,
170 flag,
171 attribute)
173 if expected:
174 self.assertIsNotNone(expected_values,
175 'expected claim, but no value(s) set')
176 value_type = claim['value_type']
178 expected_claims[claim_id] = {
179 'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
180 'type': value_type,
181 'values': expected_values,
183 else:
184 unexpected_claims.add(claim_id)
186 self.create_claim(claim_id, **claim)
188 if security_desc is not None:
189 self.assertNotIn('nTSecurityDescriptor', details)
190 details['nTSecurityDescriptor'] = ndr_pack(security_desc)
192 return details, mod_msg, expected_claims, unexpected_claims
194 def modify_pac_remove_client_claims(self, pac):
195 pac_buffers = pac.buffers
196 for pac_buffer in pac_buffers:
197 if pac_buffer.type == krb5pac.PAC_TYPE_CLIENT_CLAIMS_INFO:
198 pac.num_buffers -= 1
199 pac_buffers.remove(pac_buffer)
201 break
202 else:
203 self.fail('expected client claims in PAC')
205 pac.buffers = pac_buffers
207 return pac
209 def remove_client_claims(self, ticket):
210 return self.modified_ticket(
211 ticket,
212 modify_pac_fn=self.modify_pac_remove_client_claims,
213 checksum_keys=self.get_krbtgt_checksum_key())
215 def remove_client_claims_tgt_from_rodc(self, ticket):
216 rodc_krbtgt_creds = self.get_mock_rodc_krbtgt_creds()
217 rodc_krbtgt_key = self.TicketDecryptionKey_from_creds(
218 rodc_krbtgt_creds)
220 checksum_keys = {
221 krb5pac.PAC_TYPE_KDC_CHECKSUM: rodc_krbtgt_key
224 return self.modified_ticket(
225 ticket,
226 new_ticket_key=rodc_krbtgt_key,
227 modify_pac_fn=self.modify_pac_remove_client_claims,
228 checksum_keys=checksum_keys)
230 def test_tgs_claims(self):
231 self.run_tgs_test(remove_claims=False, to_krbtgt=False)
233 def test_tgs_claims_remove_claims(self):
234 self.run_tgs_test(remove_claims=True, to_krbtgt=False)
236 def test_tgs_claims_to_krbtgt(self):
237 self.run_tgs_test(remove_claims=False, to_krbtgt=True)
239 def test_tgs_claims_remove_claims_to_krbtgt(self):
240 self.run_tgs_test(remove_claims=True, to_krbtgt=True)
242 def test_delegation_claims(self):
243 self.run_delegation_test(remove_claims=False)
245 def test_delegation_claims_remove_claims(self):
246 self.run_delegation_test(remove_claims=True)
248 def test_rodc_issued_claims_modify(self):
249 self.run_rodc_tgs_test(remove_claims=False, delete_claim=False)
251 def test_rodc_issued_claims_delete(self):
252 self.run_rodc_tgs_test(remove_claims=False, delete_claim=True)
254 def test_rodc_issued_claims_remove_claims_modify(self):
255 self.run_rodc_tgs_test(remove_claims=True, delete_claim=False)
257 def test_rodc_issued_claims_remove_claims_delete(self):
258 self.run_rodc_tgs_test(remove_claims=True, delete_claim=True)
260 def test_rodc_issued_device_claims_modify(self):
261 self.run_device_rodc_tgs_test(remove_claims=False, delete_claim=False)
263 def test_rodc_issued_device_claims_delete(self):
264 self.run_device_rodc_tgs_test(remove_claims=False, delete_claim=True)
266 def test_rodc_issued_device_claims_remove_claims_modify(self):
267 self.run_device_rodc_tgs_test(remove_claims=True, delete_claim=False)
269 def test_rodc_issued_device_claims_remove_claims_delete(self):
270 self.run_device_rodc_tgs_test(remove_claims=True, delete_claim=True)
272 # Create a user account with an applicable claim for the 'middleName'
273 # attribute. After obtaining a TGT, from which we optionally remove the
274 # claims, change the middleName attribute values for the account in the
275 # database to a different value. By which we may observe, when examining
276 # the reply to our following Kerberos TGS request, whether the claims
277 # contained therein are taken directly from the ticket, or obtained fresh
278 # from the database.
279 def run_tgs_test(self, remove_claims, to_krbtgt):
280 samdb = self.get_samdb()
281 user_creds, user_dn = self.create_account(samdb,
282 self.get_new_username(),
283 additional_details={
284 'middleName': 'foo',
287 claim_id = self.get_new_username()
288 self.create_claim(claim_id,
289 enabled=True,
290 attribute='middleName',
291 single_valued=True,
292 source_type='AD',
293 for_classes=['user'],
294 value_type=claims.CLAIM_TYPE_STRING)
296 expected_claims = {
297 claim_id: {
298 'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
299 'type': claims.CLAIM_TYPE_STRING,
300 'values': ('foo',),
304 # Get a TGT for the user.
305 tgt = self.get_tgt(user_creds, expect_pac=True,
306 expect_client_claims=True,
307 expected_client_claims=expected_claims)
309 if remove_claims:
310 tgt = self.remove_client_claims(tgt)
312 # Change the value of the attribute used for the claim.
313 msg = ldb.Message(ldb.Dn(samdb, user_dn))
314 msg['middleName'] = ldb.MessageElement('bar',
315 ldb.FLAG_MOD_REPLACE,
316 'middleName')
317 samdb.modify(msg)
319 if to_krbtgt:
320 target_creds = self.get_krbtgt_creds()
321 sname = self.get_krbtgt_sname()
322 else:
323 target_creds = self.get_service_creds()
324 sname = None
326 # Get a service ticket for the user. The claim value should not have
327 # changed, indicating that the client claims are propagated straight
328 # through.
329 self.get_service_ticket(
330 tgt, target_creds,
331 sname=sname,
332 expect_pac=True,
333 expect_client_claims=not remove_claims,
334 expected_client_claims=(expected_claims
335 if not remove_claims else None))
337 # Perform a test similar to that preceding. This time, create both a user
338 # and a computer account, each having an applicable claim. After obtaining
339 # tickets, from which the claims are optionally removed, change the claim
340 # attribute of each account to a different value. Then perform constrained
341 # delegation with the user's service ticket, verifying that the user's
342 # claims are carried into the resulting ticket.
343 def run_delegation_test(self, remove_claims):
344 service_creds = self.get_service_creds()
345 service_spn = service_creds.get_spn()
347 user_name = self.get_new_username()
348 mach_name = self.get_new_username()
350 samdb = self.get_samdb()
351 user_creds, user_dn = self.create_account(
352 samdb,
353 user_name,
354 self.AccountType.USER,
355 additional_details={
356 'middleName': 'user_old',
358 mach_creds, mach_dn = self.create_account(
359 samdb,
360 mach_name,
361 self.AccountType.COMPUTER,
362 spn=f'host/{mach_name}',
363 additional_details={
364 'middleName': 'mach_old',
365 'msDS-AllowedToDelegateTo': service_spn,
368 claim_id = self.get_new_username()
369 self.create_claim(claim_id,
370 enabled=True,
371 attribute='middleName',
372 single_valued=True,
373 source_type='AD',
374 for_classes=['user', 'computer'],
375 value_type=claims.CLAIM_TYPE_STRING)
377 options = 'forwardable'
378 expected_flags = krb5_asn1.TicketFlags(options)
380 expected_claims_user = {
381 claim_id: {
382 'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
383 'type': claims.CLAIM_TYPE_STRING,
384 'values': ('user_old',),
387 expected_claims_mach = {
388 claim_id: {
389 'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
390 'type': claims.CLAIM_TYPE_STRING,
391 'values': ('mach_old',),
395 user_tgt = self.get_tgt(user_creds,
396 kdc_options=options,
397 expect_pac=True,
398 expected_flags=expected_flags,
399 expect_client_claims=True,
400 expected_client_claims=expected_claims_user)
401 user_ticket = self.get_service_ticket(
402 user_tgt,
403 mach_creds,
404 kdc_options=options,
405 expect_pac=True,
406 expected_flags=expected_flags,
407 expect_client_claims=True,
408 expected_client_claims=expected_claims_user)
410 mach_tgt = self.get_tgt(mach_creds,
411 expect_pac=True,
412 expect_client_claims=True,
413 expected_client_claims=expected_claims_mach)
415 if remove_claims:
416 user_ticket = self.remove_client_claims(user_ticket)
417 mach_tgt = self.remove_client_claims(mach_tgt)
419 # Change the value of the attribute used for the user claim.
420 msg = ldb.Message(ldb.Dn(samdb, user_dn))
421 msg['middleName'] = ldb.MessageElement('user_new',
422 ldb.FLAG_MOD_REPLACE,
423 'middleName')
424 samdb.modify(msg)
426 # Change the value of the attribute used for the machine claim.
427 msg = ldb.Message(ldb.Dn(samdb, mach_dn))
428 msg['middleName'] = ldb.MessageElement('mach_new',
429 ldb.FLAG_MOD_REPLACE,
430 'middleName')
431 samdb.modify(msg)
433 additional_tickets = [user_ticket.ticket]
434 options = str(krb5_asn1.KDCOptions('cname-in-addl-tkt'))
436 user_realm = user_creds.get_realm()
437 user_cname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
438 names=[user_name])
440 user_sid = user_creds.get_sid()
442 mach_realm = mach_creds.get_realm()
444 service_name = service_creds.get_username()[:-1]
445 service_realm = service_creds.get_realm()
446 service_sname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
447 names=['host', service_name])
448 service_decryption_key = self.TicketDecryptionKey_from_creds(
449 service_creds)
450 service_etypes = service_creds.tgs_supported_enctypes
452 expected_proxy_target = service_creds.get_spn()
453 expected_transited_services = [f'host/{mach_name}@{mach_realm}']
455 authenticator_subkey = self.RandomKey(Enctype.AES256)
457 etypes = (AES256_CTS_HMAC_SHA1_96, ARCFOUR_HMAC_MD5)
459 # The user's claims are propagated into the new ticket, while the
460 # machine's claims are dispensed with.
461 expected_claims = expected_claims_user if not remove_claims else None
463 # Perform constrained delegation.
464 kdc_exchange_dict = self.tgs_exchange_dict(
465 creds=user_creds,
466 expected_crealm=user_realm,
467 expected_cname=user_cname,
468 expected_srealm=service_realm,
469 expected_sname=service_sname,
470 expected_account_name=user_name,
471 expected_sid=user_sid,
472 expected_supported_etypes=service_etypes,
473 ticket_decryption_key=service_decryption_key,
474 check_rep_fn=self.generic_check_kdc_rep,
475 check_kdc_private_fn=self.generic_check_kdc_private,
476 tgt=mach_tgt,
477 authenticator_subkey=authenticator_subkey,
478 kdc_options=options,
479 expected_proxy_target=expected_proxy_target,
480 expected_transited_services=expected_transited_services,
481 expect_client_claims=not remove_claims,
482 expected_client_claims=expected_claims,
483 expect_device_claims=False,
484 expect_pac=True)
486 rep = self._generic_kdc_exchange(kdc_exchange_dict,
487 cname=None,
488 realm=service_realm,
489 sname=service_sname,
490 etypes=etypes,
491 additional_tickets=additional_tickets)
492 self.check_reply(rep, KRB_TGS_REP)
494 def run_rodc_tgs_test(self, remove_claims, delete_claim):
495 samdb = self.get_samdb()
496 # Create a user account permitted to replicate to the RODC.
497 user_creds = self.get_cached_creds(
498 account_type=self.AccountType.USER,
499 opts={
500 # Set the value of the claim attribute.
501 'additional_details': (('middleName', 'foo'),),
502 'allowed_replication_mock': True,
503 'revealed_to_mock_rodc': True,
505 use_cache=False)
506 user_dn = user_creds.get_dn()
508 # Create a claim that applies to the user.
509 claim_id = self.get_new_username()
510 self.create_claim(claim_id,
511 enabled=True,
512 attribute='middleName',
513 single_valued=True,
514 source_type='AD',
515 for_classes=['user'],
516 value_type=claims.CLAIM_TYPE_STRING)
518 expected_claims = {
519 claim_id: {
520 'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
521 'type': claims.CLAIM_TYPE_STRING,
522 'values': ('foo',),
526 # Get a TGT for the user.
527 tgt = self.get_tgt(user_creds, expect_pac=True,
528 expect_client_claims=True,
529 expected_client_claims=expected_claims)
531 # Modify the TGT to be issued by an RODC. Optionally remove the client
532 # claims.
533 if remove_claims:
534 tgt = self.remove_client_claims_tgt_from_rodc(tgt)
535 else:
536 tgt = self.issued_by_rodc(tgt)
538 # Modify or delete the value of the attribute used for the claim. Modify
539 # our test expectations accordingly.
540 msg = ldb.Message(user_dn)
541 if delete_claim:
542 msg['middleName'] = ldb.MessageElement([],
543 ldb.FLAG_MOD_DELETE,
544 'middleName')
545 expected_claims = None
546 unexpected_claims = {claim_id}
547 else:
548 msg['middleName'] = ldb.MessageElement('bar',
549 ldb.FLAG_MOD_REPLACE,
550 'middleName')
551 expected_claims = {
552 claim_id: {
553 'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
554 'type': claims.CLAIM_TYPE_STRING,
555 'values': ('bar',),
558 unexpected_claims = None
559 samdb.modify(msg)
561 target_creds = self.get_service_creds()
563 # Get a service ticket for the user. The claim value should have
564 # changed, indicating that the client claims have been regenerated or
565 # removed, depending on whether the corresponding attribute is still
566 # present on the account.
567 self.get_service_ticket(
568 tgt, target_creds,
569 expect_pac=True,
570 # Expect the CLIENT_CLAIMS_INFO PAC buffer. It may be empty.
571 expect_client_claims=True,
572 expected_client_claims=expected_claims,
573 unexpected_client_claims=unexpected_claims)
575 def run_device_rodc_tgs_test(self, remove_claims, delete_claim):
576 samdb = self.get_samdb()
578 # Create the user account.
579 user_creds = self.get_cached_creds(
580 account_type=self.AccountType.USER)
581 user_name = user_creds.get_username()
583 # Create a machine account permitted to replicate to the RODC.
584 mach_creds = self.get_cached_creds(
585 account_type=self.AccountType.COMPUTER,
586 opts={
587 # Set the value of the claim attribute.
588 'additional_details': (('middleName', 'foo'),),
589 'allowed_replication_mock': True,
590 'revealed_to_mock_rodc': True,
592 use_cache=False)
593 mach_dn = mach_creds.get_dn()
595 # Create a claim that applies to the computer.
596 claim_id = self.get_new_username()
597 self.create_claim(claim_id,
598 enabled=True,
599 attribute='middleName',
600 single_valued=True,
601 source_type='AD',
602 for_classes=['computer'],
603 value_type=claims.CLAIM_TYPE_STRING)
605 expected_claims = {
606 claim_id: {
607 'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
608 'type': claims.CLAIM_TYPE_STRING,
609 'values': ('foo',),
613 # Get a TGT for the user.
614 user_tgt = self.get_tgt(user_creds)
616 # Get a TGT for the computer.
617 mach_tgt = self.get_tgt(mach_creds, expect_pac=True,
618 expect_client_claims=True,
619 expected_client_claims=expected_claims)
621 # Modify the computer's TGT to be issued by an RODC. Optionally remove
622 # the client claims.
623 if remove_claims:
624 mach_tgt = self.remove_client_claims_tgt_from_rodc(mach_tgt)
625 else:
626 mach_tgt = self.issued_by_rodc(mach_tgt)
628 # Modify or delete the value of the attribute used for the claim. Modify
629 # our test expectations accordingly.
630 msg = ldb.Message(mach_dn)
631 if delete_claim:
632 msg['middleName'] = ldb.MessageElement([],
633 ldb.FLAG_MOD_DELETE,
634 'middleName')
635 expected_claims = None
636 unexpected_claims = {claim_id}
637 else:
638 msg['middleName'] = ldb.MessageElement('bar',
639 ldb.FLAG_MOD_REPLACE,
640 'middleName')
641 expected_claims = {
642 claim_id: {
643 'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
644 'type': claims.CLAIM_TYPE_STRING,
645 'values': ('bar',),
648 unexpected_claims = None
649 samdb.modify(msg)
651 subkey = self.RandomKey(user_tgt.session_key.etype)
653 armor_subkey = self.RandomKey(subkey.etype)
654 explicit_armor_key = self.generate_armor_key(armor_subkey,
655 mach_tgt.session_key)
656 armor_key = kcrypto.cf2(explicit_armor_key.key,
657 subkey.key,
658 b'explicitarmor',
659 b'tgsarmor')
660 armor_key = Krb5EncryptionKey(armor_key, None)
662 target_creds = self.get_service_creds()
663 target_name = target_creds.get_username()
664 if target_name[-1] == '$':
665 target_name = target_name[:-1]
667 sname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
668 names=['host', target_name])
669 srealm = target_creds.get_realm()
671 decryption_key = self.TicketDecryptionKey_from_creds(
672 target_creds)
674 target_supported_etypes = target_creds.tgs_supported_enctypes
676 etypes = (AES256_CTS_HMAC_SHA1_96, ARCFOUR_HMAC_MD5)
678 kdc_options = '0'
679 pac_options = '1' # claims support
681 # Perform a TGS-REQ for the user. The device claim value should have
682 # changed, indicating that the computer's client claims have been
683 # regenerated or removed, depending on whether the corresponding
684 # attribute is still present on the account.
686 kdc_exchange_dict = self.tgs_exchange_dict(
687 creds=user_creds,
688 expected_crealm=user_tgt.crealm,
689 expected_cname=user_tgt.cname,
690 expected_srealm=srealm,
691 expected_sname=sname,
692 expected_account_name=user_name,
693 ticket_decryption_key=decryption_key,
694 generate_fast_fn=self.generate_simple_fast,
695 generate_fast_armor_fn=self.generate_ap_req,
696 check_rep_fn=self.generic_check_kdc_rep,
697 check_kdc_private_fn=self.generic_check_kdc_private,
698 tgt=user_tgt,
699 armor_key=armor_key,
700 armor_tgt=mach_tgt,
701 armor_subkey=armor_subkey,
702 pac_options=pac_options,
703 authenticator_subkey=subkey,
704 kdc_options=kdc_options,
705 expect_pac=True,
706 expected_supported_etypes=target_supported_etypes,
707 # Expect the DEVICE_CLAIMS_INFO PAC buffer. It may be empty.
708 expect_device_claims=True,
709 expected_device_claims=expected_claims,
710 unexpected_device_claims=unexpected_claims)
712 rep = self._generic_kdc_exchange(kdc_exchange_dict,
713 cname=None,
714 realm=srealm,
715 sname=sname,
716 etypes=etypes)
717 self.check_reply(rep, KRB_TGS_REP)
719 @classmethod
720 def setUpDynamicTestCases(cls):
721 FILTER = env_get_var_value('FILTER', allow_missing=True)
722 for case in cls.cases:
723 name = case.pop('name')
724 name = re.sub(r'\W+', '_', name)
725 if FILTER and not re.search(FILTER, name):
726 continue
728 # Run tests making requests both to the krbtgt and to our own
729 # account.
730 cls.generate_dynamic_test('test_claims', name,
731 dict(case), False)
732 cls.generate_dynamic_test('test_claims', name + '_to_self',
733 dict(case), True)
735 for case in cls.device_claims_cases:
736 name = case.pop('test')
737 name = re.sub(r'\W+', '_', name)
738 if FILTER and not re.search(FILTER, name):
739 continue
741 cls.generate_dynamic_test('test_device_claims', name,
742 dict(case))
744 def _test_claims_with_args(self, case, to_self):
745 account_class = case.pop('class')
746 if account_class == 'user':
747 account_type = self.AccountType.USER
748 elif account_class == 'computer':
749 account_type = self.AccountType.COMPUTER
750 else:
751 self.fail(f'Unknown class "{account_class}"')
753 all_claims = case.pop('claims')
754 (details, mod_msg,
755 expected_claims,
756 unexpected_claims) = self.setup_claims(all_claims)
757 self.assertFalse(mod_msg,
758 'mid-test modifications not supported in this test')
759 creds = self.get_cached_creds(
760 account_type=account_type,
761 opts={
762 'additional_details': self.freeze(details),
765 # Whether to specify claims support in PA-PAC-OPTIONS.
766 pac_options_claims = case.pop('pac-options:claims-support', None)
768 self.assertFalse(case, 'unexpected parameters in testcase')
770 if pac_options_claims is None:
771 pac_options_claims = True
773 if to_self:
774 service_creds = self.get_service_creds()
775 sname = self.PrincipalName_create(
776 name_type=NT_PRINCIPAL,
777 names=[service_creds.get_username()])
778 ticket_etype = Enctype.RC4
779 else:
780 service_creds = None
781 sname = None
782 ticket_etype = None
784 if pac_options_claims:
785 pac_options = '1' # claims support
786 else:
787 pac_options = '0' # no claims support
789 self.get_tgt(creds,
790 sname=sname,
791 target_creds=service_creds,
792 ticket_etype=ticket_etype,
793 pac_options=pac_options,
794 expect_pac=True,
795 expect_client_claims=True,
796 expected_client_claims=expected_claims or None,
797 unexpected_client_claims=unexpected_claims or None)
799 sample_dn = object()
800 binary_dn = object()
801 security_descriptor = (b'\x01\x00\x04\x95\x14\x00\x00\x00\x00\x00\x00\x00'
802 b'\x00\x00\x00\x00$\x00\x00\x00\x01\x02\x00\x00\x00'
803 b'\x00\x00\x05 \x00\x00\x00 \x02\x00\x00\x04\x00'
804 b'\x1c\x00\x01\x00\x00\x00\x00\x1f\x14\x00\xff\x01'
805 b'\x0f\xf0\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00'
806 b'\x00\x00')
808 cases = [
810 'name': 'no claims',
811 'claims': [],
812 'class': 'user',
815 'name': 'simple AD-sourced claim',
816 'claims': [
818 # 2.5.5.12
819 'enabled': True,
820 'attribute': 'carLicense',
821 'single_valued': True,
822 'source_type': 'AD',
823 'for_classes': ['user'],
824 'value_type': claims.CLAIM_TYPE_STRING,
825 'values': ('foo',),
826 'expected': True,
829 'class': 'user',
832 'name': 'no claims support in pac options',
833 'claims': [
835 # 2.5.5.12
836 'enabled': True,
837 'attribute': 'carLicense',
838 'single_valued': True,
839 'source_type': 'AD',
840 'for_classes': ['user'],
841 'value_type': claims.CLAIM_TYPE_STRING,
842 'values': ('foo',),
843 # We still get claims in the PAC even if we don't specify
844 # claims support in PA-PAC-OPTIONS.
845 'expected': True,
848 'class': 'user',
849 'pac-options:claims-support': False,
852 'name': 'deny RP',
853 'claims': [
855 # 2.5.5.12
856 'enabled': True,
857 'attribute': 'carLicense',
858 'single_valued': True,
859 'source_type': 'AD',
860 'for_classes': ['user'],
861 'value_type': claims.CLAIM_TYPE_STRING,
862 'values': ('foo',),
863 # Deny read access to the attribute. It still shows up in
864 # the claim.
865 'readable': False,
866 'expected': True,
869 'class': 'user',
872 # Note: The order of these DNs may differ on Windows.
873 'name': 'dn string syntax',
874 'claims': [
876 # 2.5.5.1
877 'enabled': True,
878 'attribute': 'msDS-AuthenticatedAtDC',
879 'single_valued': True,
880 'source_type': 'AD',
881 'for_classes': ['user'],
882 'value_type': claims.CLAIM_TYPE_STRING,
883 'values': UnorderedList([sample_dn, sample_dn, sample_dn]),
884 'expected': True,
887 'class': 'user',
890 'name': 'dn string syntax, wrong value type',
891 'claims': [
893 # 2.5.5.1
894 'enabled': True,
895 'attribute': 'msDS-AuthenticatedAtDC',
896 'single_valued': True,
897 'source_type': 'AD',
898 'for_classes': ['user'],
899 'value_type': claims.CLAIM_TYPE_BOOLEAN,
900 'values': UnorderedList([sample_dn, sample_dn, sample_dn]),
903 'class': 'user',
906 'name': 'oid syntax',
907 'claims': [
909 # 2.5.5.2
910 'enabled': True,
911 'attribute': 'objectClass',
912 'single_valued': True,
913 'source_type': 'AD',
914 'for_classes': ['user'],
915 'value_type': claims.CLAIM_TYPE_UINT64,
916 'expected_values': [655369, 65543, 65542, 65536],
917 'expected': True,
920 'class': 'user',
923 'name': 'oid syntax 2',
924 'claims': [
926 # 2.5.5.2
927 'enabled': True,
928 'attribute': 'objectClass',
929 'single_valued': True,
930 'source_type': 'AD',
931 'for_classes': ['computer'],
932 'value_type': claims.CLAIM_TYPE_UINT64,
933 'expected_values': [196638, 655369, 65543, 65542, 65536],
934 'expected': True,
937 'class': 'computer',
940 'name': 'oid syntax, wrong value type',
941 'claims': [
943 # 2.5.5.2
944 'enabled': True,
945 'attribute': 'objectClass',
946 'single_valued': True,
947 'source_type': 'AD',
948 'for_classes': ['user'],
949 'value_type': claims.CLAIM_TYPE_INT64,
952 'class': 'user',
955 'name': 'boolean syntax, true',
956 'claims': [
958 # 2.5.5.8
959 'enabled': True,
960 'attribute': 'msTSAllowLogon',
961 'single_valued': True,
962 'source_type': 'AD',
963 'for_classes': ['user'],
964 'value_type': claims.CLAIM_TYPE_BOOLEAN,
965 'values': (True,),
966 'expected': True,
969 'class': 'user',
972 'name': 'boolean syntax, false',
973 'claims': [
975 # 2.5.5.8
976 'enabled': True,
977 'attribute': 'msTSAllowLogon',
978 'single_valued': True,
979 'source_type': 'AD',
980 'for_classes': ['user'],
981 'value_type': claims.CLAIM_TYPE_BOOLEAN,
982 'values': (False,),
983 'expected': True,
986 'class': 'user',
989 'name': 'boolean syntax, wrong value type',
990 'claims': [
992 # 2.5.5.8
993 'enabled': True,
994 'attribute': 'msTSAllowLogon',
995 'single_valued': True,
996 'source_type': 'AD',
997 'for_classes': ['user'],
998 'value_type': claims.CLAIM_TYPE_STRING,
999 'values': (True,),
1002 'class': 'user',
1005 'name': 'integer syntax',
1006 'claims': [
1008 # 2.5.5.9
1009 'enabled': True,
1010 'attribute': 'localeID',
1011 'single_valued': True,
1012 'source_type': 'AD',
1013 'for_classes': ['user'],
1014 'value_type': claims.CLAIM_TYPE_INT64,
1015 'values': (3, 42, -999, 1000, 20000),
1016 'expected': True,
1019 'class': 'user',
1022 'name': 'integer syntax, duplicate claim',
1023 'claims': [
1025 # 2.5.5.9
1026 'enabled': True,
1027 'attribute': 'localeID',
1028 'single_valued': True,
1029 'source_type': 'AD',
1030 'for_classes': ['user'],
1031 'value_type': claims.CLAIM_TYPE_INT64,
1032 'values': (3, 42, -999, 1000, 20000),
1033 'expected': True,
1035 ] * 2, # Create two integer syntax claims.
1036 'class': 'user',
1039 'name': 'integer syntax, wrong value type',
1040 'claims': [
1042 # 2.5.5.9
1043 'enabled': True,
1044 'attribute': 'localeID',
1045 'single_valued': True,
1046 'source_type': 'AD',
1047 'for_classes': ['user'],
1048 'value_type': claims.CLAIM_TYPE_UINT64,
1049 'values': (3, 42, -999, 1000),
1052 'class': 'user',
1055 'name': 'security descriptor syntax',
1056 'claims': [
1058 # 2.5.5.15
1059 'enabled': True,
1060 'attribute': 'msDS-AllowedToActOnBehalfOfOtherIdentity',
1061 'single_valued': True,
1062 'source_type': 'AD',
1063 'for_classes': ['computer'],
1064 'value_type': claims.CLAIM_TYPE_STRING,
1065 'values': (security_descriptor,),
1066 'expected_values': (
1067 'O:BAD:PARAI(A;OICINPIOID;CCDCLCSWRPWPDTLOCRSDRCWDWOGAGXGWGR;;;S-1-0-0)',
1069 'expected': True,
1072 'class': 'computer',
1075 'name': 'security descriptor syntax, wrong value type',
1076 'claims': [
1078 # 2.5.5.15
1079 'enabled': True,
1080 'attribute': 'msDS-AllowedToActOnBehalfOfOtherIdentity',
1081 'single_valued': True,
1082 'source_type': 'AD',
1083 'for_classes': ['computer'],
1084 'value_type': claims.CLAIM_TYPE_UINT64,
1085 'values': (security_descriptor,),
1088 'class': 'computer',
1091 'name': 'case insensitive string syntax (invalid)',
1092 'claims': [
1094 # 2.5.5.4
1095 'enabled': True,
1096 'attribute': 'networkAddress',
1097 'single_valued': True,
1098 'source_type': 'AD',
1099 'for_classes': ['user'],
1100 'value_type': claims.CLAIM_TYPE_STRING,
1101 'values': ('foo', 'bar'),
1104 'class': 'user',
1107 'name': 'printable string syntax (invalid)',
1108 'claims': [
1110 # 2.5.5.5
1111 'enabled': True,
1112 'attribute': 'displayNamePrintable',
1113 'single_valued': True,
1114 'source_type': 'AD',
1115 'for_classes': ['user'],
1116 'value_type': claims.CLAIM_TYPE_STRING,
1117 'values': ('foo',),
1120 'class': 'user',
1123 'name': 'numeric string syntax (invalid)',
1124 'claims': [
1126 # 2.5.5.6
1127 'enabled': True,
1128 'attribute': 'internationalISDNNumber',
1129 'single_valued': True,
1130 'source_type': 'AD',
1131 'for_classes': ['user'],
1132 'value_type': claims.CLAIM_TYPE_STRING,
1133 'values': ('foo', 'bar'),
1136 'class': 'user',
1139 'name': 'dn binary syntax (invalid)',
1140 'claims': [
1142 # 2.5.5.7
1143 'enabled': True,
1144 'attribute': 'msDS-RevealedUsers',
1145 'single_valued': True,
1146 'source_type': 'AD',
1147 'for_classes': ['user'],
1148 'value_type': claims.CLAIM_TYPE_STRING,
1149 'values': (binary_dn, binary_dn, binary_dn),
1152 'class': 'computer',
1155 'name': 'octet string syntax (invalid)',
1156 'claims': [
1158 # 2.5.5.10
1159 'enabled': True,
1160 'attribute': 'jpegPhoto',
1161 'single_valued': True,
1162 'source_type': 'AD',
1163 'for_classes': ['user'],
1164 'value_type': claims.CLAIM_TYPE_STRING,
1165 'values': ('foo', 'bar'),
1168 'class': 'user',
1171 'name': 'utc time syntax (invalid)',
1172 'claims': [
1174 # 2.5.5.11
1175 'enabled': True,
1176 'attribute': 'msTSExpireDate2',
1177 'single_valued': True,
1178 'source_type': 'AD',
1179 'for_classes': ['user'],
1180 'value_type': claims.CLAIM_TYPE_STRING,
1181 'values': ('19700101000000.0Z',),
1184 'class': 'user',
1187 'name': 'access point syntax (invalid)',
1188 'claims': [
1190 # 2.5.5.17
1191 'enabled': True,
1192 'attribute': 'mS-DS-CreatorSID',
1193 'single_valued': True,
1194 'source_type': 'AD',
1195 'for_classes': ['user'],
1196 'value_type': claims.CLAIM_TYPE_STRING,
1199 'class': 'user',
1202 'name': 'no value set',
1203 'claims': [
1205 # 2.5.5.12
1206 'enabled': True,
1207 'attribute': 'carLicense',
1208 'single_valued': True,
1209 'source_type': 'AD',
1210 'for_classes': ['user'],
1211 'value_type': claims.CLAIM_TYPE_STRING,
1214 'class': 'user',
1217 'name': 'multi-valued claim',
1218 'claims': [
1220 # 2.5.5.12
1221 'enabled': True,
1222 'attribute': 'carLicense',
1223 'single_valued': True,
1224 'source_type': 'AD',
1225 'for_classes': ['user'],
1226 'value_type': claims.CLAIM_TYPE_STRING,
1227 'values': ('foo', 'bar', 'baz'),
1228 'expected': True,
1231 'class': 'user',
1234 'name': 'missing attribute',
1235 'claims': [
1237 'enabled': True,
1238 'single_valued': True,
1239 'source_type': 'AD',
1240 'for_classes': ['user'],
1241 'value_type': claims.CLAIM_TYPE_STRING,
1244 'class': 'user',
1247 'name': 'invalid attribute',
1248 'claims': [
1250 # 2.5.5.10
1251 'enabled': True,
1252 'attribute': 'unicodePwd',
1253 'single_valued': True,
1254 'source_type': 'AD',
1255 'for_classes': ['user'],
1256 'value_type': claims.CLAIM_TYPE_STRING,
1259 'class': 'user',
1262 'name': 'incorrect value type',
1263 'claims': [
1265 # 2.5.5.12
1266 'enabled': True,
1267 'attribute': 'carLicense',
1268 'single_valued': True,
1269 'source_type': 'AD',
1270 'for_classes': ['user'],
1271 'value_type': claims.CLAIM_TYPE_INT64,
1272 'values': ('foo',),
1275 'class': 'user',
1278 'name': 'invalid value type',
1279 'claims': [
1281 # 2.5.5.12
1282 'enabled': True,
1283 'attribute': 'carLicense',
1284 'single_valued': True,
1285 'source_type': 'AD',
1286 'for_classes': ['user'],
1287 'value_type': 0,
1288 'values': ('foo',),
1291 'class': 'user',
1294 'name': 'missing value type',
1295 'claims': [
1297 # 2.5.5.12
1298 'enabled': True,
1299 'attribute': 'carLicense',
1300 'single_valued': True,
1301 'source_type': 'AD',
1302 'for_classes': ['user'],
1303 'values': ('foo',),
1306 'class': 'user',
1309 'name': 'string syntax, duplicate claim',
1310 'claims': [
1312 # 2.5.5.12
1313 'enabled': True,
1314 'attribute': 'carLicense',
1315 'single_valued': True,
1316 'source_type': 'AD',
1317 'for_classes': ['user'],
1318 'value_type': claims.CLAIM_TYPE_STRING,
1319 'values': ('foo',),
1320 'expected': True,
1322 ] * 2, # Create two string syntax claims.
1323 'class': 'user',
1326 'name': 'multiple claims',
1327 'claims': [
1329 # 2.5.5.12
1330 'enabled': True,
1331 'attribute': 'carLicense',
1332 'single_valued': True,
1333 'source_type': 'AD',
1334 'for_classes': ['user'],
1335 'value_type': claims.CLAIM_TYPE_STRING,
1336 'values': ('foo', 'bar', 'baz'),
1337 'expected': True,
1340 # 2.5.5.8
1341 'enabled': True,
1342 'attribute': 'msTSAllowLogon',
1343 'single_valued': True,
1344 'source_type': 'AD',
1345 'for_classes': ['user'],
1346 'value_type': claims.CLAIM_TYPE_BOOLEAN,
1347 'values': (True,),
1348 'expected': True,
1351 'class': 'user',
1354 'name': 'case difference for source type',
1355 'claims': [
1357 # 2.5.5.12
1358 'enabled': True,
1359 'attribute': 'carLicense',
1360 'single_valued': True,
1361 'source_type': 'ad',
1362 'for_classes': ['user'],
1363 'value_type': claims.CLAIM_TYPE_STRING,
1364 'values': ('foo',),
1365 'expected': True,
1368 'class': 'user',
1371 'name': 'unhandled source type',
1372 'claims': [
1374 # 2.5.5.12
1375 'enabled': True,
1376 'attribute': 'carLicense',
1377 'single_valued': True,
1378 'source_type': '<unknown>',
1379 'for_classes': ['user'],
1380 'value_type': claims.CLAIM_TYPE_STRING,
1381 'values': ('foo',),
1384 'class': 'user',
1387 'name': 'disabled claim',
1388 'claims': [
1390 # 2.5.5.12
1391 'enabled': False,
1392 'attribute': 'carLicense',
1393 'single_valued': True,
1394 'source_type': 'AD',
1395 'for_classes': ['user'],
1396 'value_type': claims.CLAIM_TYPE_STRING,
1397 'values': ('foo',),
1400 'class': 'user',
1403 'name': 'not enabled claim',
1404 'claims': [
1406 # 2.5.5.12
1407 'attribute': 'carLicense',
1408 'single_valued': True,
1409 'source_type': 'AD',
1410 'for_classes': ['user'],
1411 'value_type': claims.CLAIM_TYPE_STRING,
1412 'values': ('foo',),
1415 'class': 'user',
1418 'name': 'not applicable to any class',
1419 'claims': [
1421 # 2.5.5.12
1422 'enabled': True,
1423 'attribute': 'carLicense',
1424 'single_valued': True,
1425 'source_type': 'AD',
1426 'value_type': claims.CLAIM_TYPE_STRING,
1427 'values': ('foo',),
1430 'class': 'user',
1433 'name': 'not applicable to class',
1434 'claims': [
1436 # 2.5.5.12
1437 'enabled': True,
1438 'attribute': 'carLicense',
1439 'single_valued': True,
1440 'source_type': 'AD',
1441 'for_classes': ['user'],
1442 'value_type': claims.CLAIM_TYPE_STRING,
1443 'values': ('foo',),
1446 'class': 'computer',
1449 'name': 'applicable to class',
1450 'claims': [
1452 # 2.5.5.12
1453 'enabled': True,
1454 'attribute': 'carLicense',
1455 'single_valued': True,
1456 'source_type': 'AD',
1457 'for_classes': ['user', 'computer'],
1458 'value_type': claims.CLAIM_TYPE_STRING,
1459 'values': ('foo',),
1460 'expected': True,
1463 'class': 'computer',
1466 'name': 'applicable to base class',
1467 'claims': [
1469 # 2.5.5.12
1470 'enabled': True,
1471 'attribute': 'carLicense',
1472 'single_valued': True,
1473 'source_type': 'AD',
1474 'for_classes': ['top'],
1475 'value_type': claims.CLAIM_TYPE_STRING,
1476 'values': ('foo',),
1479 'class': 'user',
1482 'name': 'applicable to base class 2',
1483 'claims': [
1485 # 2.5.5.12
1486 'enabled': True,
1487 'attribute': 'carLicense',
1488 'single_valued': True,
1489 'source_type': 'AD',
1490 'for_classes': ['organizationalPerson'],
1491 'value_type': claims.CLAIM_TYPE_STRING,
1492 'values': ('foo',),
1495 'class': 'user',
1498 'name': 'large compressed claim',
1499 'claims': [
1501 # 2.5.5.12
1502 'enabled': True,
1503 'attribute': 'carLicense',
1504 'single_valued': True,
1505 'source_type': 'AD',
1506 'for_classes': ['user'],
1507 'value_type': claims.CLAIM_TYPE_STRING,
1508 # a large value that should cause the claim to be
1509 # compressed.
1510 'values': ('a' * 10000,),
1511 'expected': True,
1514 'class': 'user',
1518 def _test_device_claims_with_args(self, case):
1519 # The group arrangement for the test.
1520 group_setup = case.pop('groups')
1522 # Groups that should be the primary group for the user and machine
1523 # respectively.
1524 primary_group = case.pop('primary_group', None)
1525 mach_primary_group = case.pop('mach:primary_group', None)
1527 # Whether the TGS-REQ should be directed to the krbtgt.
1528 tgs_to_krbtgt = case.pop('tgs:to_krbtgt', None)
1530 # Whether the target server of the TGS-REQ should support compound
1531 # identity or resource SID compression.
1532 tgs_compound_id = case.pop('tgs:compound_id', None)
1533 tgs_compression = case.pop('tgs:compression', None)
1535 # Optional SIDs to replace those in the machine account PAC prior to a
1536 # TGS-REQ.
1537 tgs_mach_sids = case.pop('tgs:mach:sids', None)
1539 # Optional machine SID to replace that in the PAC prior to a TGS-REQ.
1540 tgs_mach_sid = case.pop('tgs:mach_sid', None)
1542 # User flags that may be set or reset in the PAC prior to a TGS-REQ.
1543 tgs_mach_set_user_flags = case.pop('tgs:mach:set_user_flags', None)
1544 tgs_mach_reset_user_flags = case.pop('tgs:mach:reset_user_flags', None)
1546 # The SIDs we expect to see in the PAC after a AS-REQ or a TGS-REQ.
1547 as_expected = case.pop('as:expected', None)
1548 as_mach_expected = case.pop('as:mach:expected', None)
1549 tgs_expected = case.pop('tgs:expected', None)
1550 tgs_device_expected = case.pop('tgs:device:expected', None)
1552 # Whether to specify claims support in PA-PAC-OPTIONS.
1553 pac_options_claims = case.pop('pac-options:claims-support', None)
1555 all_claims = case.pop('claims')
1557 # There should be no parameters remaining in the testcase.
1558 self.assertFalse(case, 'unexpected parameters in testcase')
1560 if as_expected is None:
1561 self.assertIsNotNone(tgs_expected,
1562 'no set of expected SIDs is provided')
1564 if as_mach_expected is None:
1565 self.assertIsNotNone(tgs_expected,
1566 'no set of expected machine SIDs is provided')
1568 if tgs_to_krbtgt is None:
1569 tgs_to_krbtgt = False
1571 if tgs_compound_id is None and not tgs_to_krbtgt:
1572 # Assume the service supports compound identity by default.
1573 tgs_compound_id = True
1575 if tgs_to_krbtgt:
1576 self.assertIsNone(tgs_device_expected,
1577 'device SIDs are not added for a krbtgt request')
1579 self.assertIsNotNone(tgs_expected,
1580 'no set of expected TGS SIDs is provided')
1582 if tgs_mach_sid is not None:
1583 self.assertIsNotNone(tgs_mach_sids,
1584 'specified TGS-REQ mach SID, but no '
1585 'accompanying machine SIDs provided')
1587 if tgs_mach_set_user_flags is None:
1588 tgs_mach_set_user_flags = 0
1589 else:
1590 self.assertIsNotNone(tgs_mach_sids,
1591 'specified TGS-REQ set user flags, but no '
1592 'accompanying machine SIDs provided')
1594 if tgs_mach_reset_user_flags is None:
1595 tgs_mach_reset_user_flags = 0
1596 else:
1597 self.assertIsNotNone(tgs_mach_sids,
1598 'specified TGS-REQ reset user flags, but no '
1599 'accompanying machine SIDs provided')
1601 if pac_options_claims is None:
1602 pac_options_claims = True
1604 (details, mod_msg,
1605 expected_claims,
1606 unexpected_claims) = self.setup_claims(all_claims)
1608 samdb = self.get_samdb()
1610 domain_sid = samdb.get_domain_sid()
1612 user_creds = self.get_cached_creds(
1613 account_type=self.AccountType.USER)
1614 user_dn = user_creds.get_dn()
1615 user_sid = user_creds.get_sid()
1617 mach_name = self.get_new_username()
1618 mach_creds, mach_dn_str = self.create_account(
1619 samdb,
1620 mach_name,
1621 account_type=self.AccountType.COMPUTER,
1622 additional_details=details)
1623 mach_dn = ldb.Dn(samdb, mach_dn_str)
1624 mach_sid = mach_creds.get_sid()
1626 user_principal = Principal(user_dn, user_sid)
1627 mach_principal = Principal(mach_dn, mach_sid)
1628 preexisting_groups = {
1629 self.user: user_principal,
1630 self.mach: mach_principal,
1632 primary_groups = {}
1633 if primary_group is not None:
1634 primary_groups[user_principal] = primary_group
1635 if mach_primary_group is not None:
1636 primary_groups[mach_principal] = mach_primary_group
1637 groups = self.setup_groups(samdb,
1638 preexisting_groups,
1639 group_setup,
1640 primary_groups)
1641 del group_setup
1643 tgs_user_sid = user_sid
1644 tgs_user_domain_sid, tgs_user_rid = tgs_user_sid.rsplit('-', 1)
1646 if tgs_mach_sid is None:
1647 tgs_mach_sid = mach_sid
1648 elif tgs_mach_sid in groups:
1649 tgs_mach_sid = groups[tgs_mach_sid].sid
1651 tgs_mach_domain_sid, tgs_mach_rid = tgs_mach_sid.rsplit('-', 1)
1653 expected_groups = self.map_sids(as_expected, groups,
1654 domain_sid)
1655 mach_expected_groups = self.map_sids(as_mach_expected, groups,
1656 domain_sid)
1657 tgs_mach_sids_mapped = self.map_sids(tgs_mach_sids, groups,
1658 tgs_mach_domain_sid)
1659 tgs_expected_mapped = self.map_sids(tgs_expected, groups,
1660 tgs_user_domain_sid)
1661 tgs_device_expected_mapped = self.map_sids(tgs_device_expected, groups,
1662 tgs_mach_domain_sid)
1664 user_tgt = self.get_tgt(user_creds, expected_groups=expected_groups)
1666 # Get a TGT for the computer.
1667 mach_tgt = self.get_tgt(mach_creds, expect_pac=True,
1668 expected_groups=mach_expected_groups,
1669 expect_client_claims=True,
1670 expected_client_claims=expected_claims,
1671 unexpected_client_claims=unexpected_claims)
1673 if tgs_mach_sids is not None:
1674 # Replace the SIDs in the PAC with the ones provided by the test.
1675 mach_tgt = self.ticket_with_sids(mach_tgt,
1676 tgs_mach_sids_mapped,
1677 tgs_mach_domain_sid,
1678 tgs_mach_rid,
1679 set_user_flags=tgs_mach_set_user_flags,
1680 reset_user_flags=tgs_mach_reset_user_flags)
1682 if mod_msg:
1683 self.assertFalse(tgs_to_krbtgt,
1684 'device claims are omitted for a krbtgt request, '
1685 'so specifying mod_values is probably a mistake!')
1687 # Change the value of attributes used for claims.
1688 mod_msg.dn = mach_dn
1689 samdb.modify(mod_msg)
1691 domain_sid = samdb.get_domain_sid()
1693 subkey = self.RandomKey(user_tgt.session_key.etype)
1695 armor_subkey = self.RandomKey(subkey.etype)
1696 explicit_armor_key = self.generate_armor_key(armor_subkey,
1697 mach_tgt.session_key)
1698 armor_key = kcrypto.cf2(explicit_armor_key.key,
1699 subkey.key,
1700 b'explicitarmor',
1701 b'tgsarmor')
1702 armor_key = Krb5EncryptionKey(armor_key, None)
1704 target_creds, sname = self.get_target(
1705 to_krbtgt=tgs_to_krbtgt,
1706 compound_id=tgs_compound_id,
1707 compression=tgs_compression)
1708 srealm = target_creds.get_realm()
1710 decryption_key = self.TicketDecryptionKey_from_creds(
1711 target_creds)
1713 etypes = (AES256_CTS_HMAC_SHA1_96, ARCFOUR_HMAC_MD5)
1715 kdc_options = '0'
1716 if pac_options_claims:
1717 pac_options = '1' # claims support
1718 else:
1719 pac_options = '0' # no claims support
1721 requester_sid = None
1722 if tgs_to_krbtgt:
1723 requester_sid = user_sid
1725 if not tgs_compound_id:
1726 expected_claims = None
1727 unexpected_claims = None
1729 # Get a service ticket for the user, using the computer's TGT as an
1730 # armor TGT. The claim value should not have changed.
1732 kdc_exchange_dict = self.tgs_exchange_dict(
1733 creds=user_creds,
1734 expected_crealm=user_tgt.crealm,
1735 expected_cname=user_tgt.cname,
1736 expected_srealm=srealm,
1737 expected_sname=sname,
1738 ticket_decryption_key=decryption_key,
1739 generate_fast_fn=self.generate_simple_fast,
1740 generate_fast_armor_fn=self.generate_ap_req,
1741 check_rep_fn=self.generic_check_kdc_rep,
1742 check_kdc_private_fn=self.generic_check_kdc_private,
1743 tgt=user_tgt,
1744 armor_key=armor_key,
1745 armor_tgt=mach_tgt,
1746 armor_subkey=armor_subkey,
1747 pac_options=pac_options,
1748 authenticator_subkey=subkey,
1749 kdc_options=kdc_options,
1750 expect_pac=True,
1751 expect_pac_attrs=tgs_to_krbtgt,
1752 expect_pac_attrs_pac_request=tgs_to_krbtgt,
1753 expected_sid=tgs_user_sid,
1754 expected_requester_sid=requester_sid,
1755 expected_domain_sid=tgs_user_domain_sid,
1756 expected_device_domain_sid=tgs_mach_domain_sid,
1757 expected_groups=tgs_expected_mapped,
1758 unexpected_groups=None,
1759 expect_client_claims=True,
1760 expected_client_claims=None,
1761 expect_device_info=bool(tgs_compound_id),
1762 expected_device_groups=tgs_device_expected_mapped,
1763 expect_device_claims=bool(tgs_compound_id),
1764 expected_device_claims=expected_claims,
1765 unexpected_device_claims=unexpected_claims)
1767 rep = self._generic_kdc_exchange(kdc_exchange_dict,
1768 cname=None,
1769 realm=srealm,
1770 sname=sname,
1771 etypes=etypes)
1772 self.check_reply(rep, KRB_TGS_REP)
1774 device_claims_cases = [
1776 # Make a TGS request containing claims, but omit the Claims Valid
1777 # SID.
1778 'test': 'device to service no claims valid sid',
1779 'groups': {
1780 # Some groups to test how the device info is generated.
1781 'foo': (GroupType.DOMAIN_LOCAL, {mach}),
1782 'bar': (GroupType.DOMAIN_LOCAL, {mach}),
1784 'claims': [
1786 # 2.5.5.10
1787 'enabled': True,
1788 'attribute': 'middleName',
1789 'single_valued': True,
1790 'source_type': 'AD',
1791 'for_classes': ['computer'],
1792 'value_type': claims.CLAIM_TYPE_STRING,
1793 'values': ('foo',),
1794 'expected': True,
1795 'mod_values': ['bar'],
1798 'as:expected': {
1799 (asserted_identity, SidType.EXTRA_SID, default_attrs),
1800 (security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
1801 (security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
1802 (security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
1804 'as:mach:expected': {
1805 (asserted_identity, SidType.EXTRA_SID, default_attrs),
1806 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
1807 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
1808 (security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
1810 'tgs:mach:sids': {
1811 (asserted_identity, SidType.EXTRA_SID, default_attrs),
1812 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
1813 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
1814 # Omit the Claims Valid SID, and verify that this doesn't
1815 # affect the propagation of claims into the final ticket.
1817 # Some extra SIDs to show how they are propagated into the
1818 # final ticket.
1819 ('S-1-5-22-1-2-3-4', SidType.EXTRA_SID, default_attrs),
1820 ('S-1-5-22-1-2-3-5', SidType.EXTRA_SID, default_attrs),
1822 'tgs:to_krbtgt': False,
1823 'tgs:expected': {
1824 (security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY, SidType.EXTRA_SID, default_attrs),
1825 (security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
1826 (security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, default_attrs),
1827 (security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
1828 (security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
1830 'tgs:device:expected': {
1831 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
1832 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
1833 (asserted_identity, SidType.EXTRA_SID, default_attrs),
1834 ('S-1-5-22-1-2-3-4', SidType.EXTRA_SID, default_attrs),
1835 ('S-1-5-22-1-2-3-5', SidType.EXTRA_SID, default_attrs),
1836 frozenset([
1837 ('foo', SidType.RESOURCE_SID, resource_attrs),
1838 ('bar', SidType.RESOURCE_SID, resource_attrs),
1843 # Make a TGS request containing claims to a service that lacks
1844 # support for compound identity. The claims are not propagated to
1845 # the final ticket.
1846 'test': 'device to service no compound id',
1847 'groups': {
1848 'foo': (GroupType.DOMAIN_LOCAL, {mach}),
1849 'bar': (GroupType.DOMAIN_LOCAL, {mach}),
1851 'claims': [
1853 # 2.5.5.10
1854 'enabled': True,
1855 'attribute': 'middleName',
1856 'single_valued': True,
1857 'source_type': 'AD',
1858 'for_classes': ['computer'],
1859 'value_type': claims.CLAIM_TYPE_STRING,
1860 'values': ('foo',),
1861 'expected': True,
1862 'mod_values': ['bar'],
1865 'as:expected': {
1866 (asserted_identity, SidType.EXTRA_SID, default_attrs),
1867 (security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
1868 (security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
1869 (security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
1871 'as:mach:expected': {
1872 (asserted_identity, SidType.EXTRA_SID, default_attrs),
1873 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
1874 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
1875 (security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
1877 'tgs:to_krbtgt': False,
1878 # Compound identity is unsupported.
1879 'tgs:compound_id': False,
1880 'tgs:expected': {
1881 (security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY, SidType.EXTRA_SID, default_attrs),
1882 (security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
1883 # The Compounded Authentication SID should not be present.
1884 (security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
1885 (security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
1889 # Make a TGS request containing claims to a service, but don't
1890 # specify support for claims in PA-PAC-OPTIONS. We still expect the
1891 # final PAC to contain claims.
1892 'test': 'device to service no claims support in pac options',
1893 'groups': {
1894 'foo': (GroupType.DOMAIN_LOCAL, {mach}),
1895 'bar': (GroupType.DOMAIN_LOCAL, {mach}),
1897 'claims': [
1899 # 2.5.5.10
1900 'enabled': True,
1901 'attribute': 'middleName',
1902 'single_valued': True,
1903 'source_type': 'AD',
1904 'for_classes': ['computer'],
1905 'value_type': claims.CLAIM_TYPE_STRING,
1906 'values': ('foo',),
1907 'expected': True,
1908 'mod_values': ['bar'],
1911 'as:expected': {
1912 (asserted_identity, SidType.EXTRA_SID, default_attrs),
1913 (security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
1914 (security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
1915 (security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
1917 'as:mach:expected': {
1918 (asserted_identity, SidType.EXTRA_SID, default_attrs),
1919 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
1920 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
1921 (security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
1923 'tgs:to_krbtgt': False,
1924 # Claims are unsupported.
1925 'pac-options:claims-support': False,
1926 'tgs:expected': {
1927 (security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY, SidType.EXTRA_SID, default_attrs),
1928 (security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
1929 (security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, default_attrs),
1930 (security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
1931 (security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
1933 'tgs:device:expected': {
1934 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
1935 (security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
1936 frozenset([
1937 ('foo', SidType.RESOURCE_SID, resource_attrs),
1938 ('bar', SidType.RESOURCE_SID, resource_attrs),
1940 (asserted_identity, SidType.EXTRA_SID, default_attrs),
1941 frozenset([(security.SID_CLAIMS_VALID, SidType.RESOURCE_SID, default_attrs)]),
1946 def test_auth_silo_claim(self):
1947 self.run_auth_silo_claim_test()
1949 def test_auth_silo_claim_unenforced(self):
1950 # The claim is not present if the silo is unenforced.
1951 self.run_auth_silo_claim_test(enforced=False,
1952 expect_claim=False)
1954 def test_auth_silo_claim_not_a_member(self):
1955 # The claim is not present if the user is not a member of the silo.
1956 self.run_auth_silo_claim_test(add_to_silo=False,
1957 expect_claim=False)
1959 def test_auth_silo_claim_unassigned(self):
1960 # The claim is not present if the user is not assigned to the silo.
1961 self.run_auth_silo_claim_test(assigned=False,
1962 expect_claim=False)
1964 def test_auth_silo_claim_assigned_to_wrong_dn(self):
1965 samdb = self.get_samdb()
1967 # The claim is not present if the user is assigned to some other DN.
1968 self.run_auth_silo_claim_test(assigned=self.get_server_dn(samdb),
1969 expect_claim=False)
1971 def run_auth_silo_claim_test(self, *,
1972 enforced=True,
1973 add_to_silo=True,
1974 assigned=True,
1975 expect_claim=True):
1976 # Create a new authentication silo.
1977 silo = self.create_authn_silo(enforced=enforced)
1979 account_options = None
1980 if assigned is not False:
1981 if assigned is True:
1982 assigned = silo.dn
1984 account_options = {
1985 'additional_details': self.freeze({
1986 # The user is assigned to the authentication silo we just
1987 # created, or to some DN specified by a test.
1988 'msDS-AssignedAuthNPolicySilo': str(assigned),
1992 # Create the user account.
1993 creds = self.get_cached_creds(
1994 account_type=self.AccountType.USER,
1995 opts=account_options)
1997 if add_to_silo:
1998 # Add the account to the silo.
1999 self.add_to_group(str(creds.get_dn()),
2000 silo.dn,
2001 'msDS-AuthNPolicySiloMembers',
2002 expect_attr=False)
2004 claim_id = self.create_authn_silo_claim_id()
2006 if expect_claim:
2007 expected_claims = {
2008 claim_id: {
2009 'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
2010 'type': claims.CLAIM_TYPE_STRING,
2011 # Expect a claim containing the name of the silo.
2012 'values': (silo.name,),
2015 unexpected_claims = None
2016 else:
2017 expected_claims = None
2018 unexpected_claims = {claim_id}
2020 # Get a TGT and check whether the claim is present or missing.
2021 self.get_tgt(creds,
2022 expect_pac=True,
2023 expect_client_claims=True,
2024 expected_client_claims=expected_claims,
2025 unexpected_client_claims=unexpected_claims)
2028 if __name__ == '__main__':
2029 global_asn1_print = False
2030 global_hexdump = False
2031 import unittest
2032 unittest.main()