mirror of
https://github.com/samba-team/samba.git
synced 2025-10-14 03:33:17 +03:00
These are useful inside the test infrastructure. Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2052 lines
77 KiB
Python
Executable File
2052 lines
77 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Unix SMB/CIFS implementation.
|
|
# Copyright (C) Stefan Metzmacher 2020
|
|
# Copyright (C) Catalyst.Net Ltd 2022
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
import sys
|
|
import os
|
|
|
|
sys.path.insert(0, 'bin/python')
|
|
os.environ['PYTHONUNBUFFERED'] = '1'
|
|
|
|
import re
|
|
import ldb
|
|
|
|
from samba.dcerpc import claims, krb5pac, security
|
|
from samba.ndr import ndr_pack
|
|
|
|
from samba.tests import DynamicTestCase, env_get_var_value
|
|
from samba.tests.krb5 import kcrypto
|
|
from samba.tests.krb5.kcrypto import Enctype
|
|
from samba.tests.krb5.kdc_base_test import GroupType, KDCBaseTest, Principal
|
|
from samba.tests.krb5.raw_testcase import Krb5EncryptionKey, RawKerberosTest
|
|
from samba.tests.krb5.rfc4120_constants import (
|
|
AES256_CTS_HMAC_SHA1_96,
|
|
ARCFOUR_HMAC_MD5,
|
|
KRB_TGS_REP,
|
|
NT_PRINCIPAL,
|
|
)
|
|
import samba.tests.krb5.rfc4120_pyasn1 as krb5_asn1
|
|
|
|
SidType = RawKerberosTest.SidType
|
|
|
|
global_asn1_print = False
|
|
global_hexdump = False
|
|
|
|
|
|
class UnorderedList(tuple):
|
|
def __eq__(self, other):
|
|
if not isinstance(other, UnorderedList):
|
|
raise AssertionError('unexpected comparison attempt')
|
|
return sorted(self) == sorted(other)
|
|
|
|
def __hash__(self):
|
|
return hash(tuple(sorted(self)))
|
|
|
|
|
|
@DynamicTestCase
|
|
class ClaimsTests(KDCBaseTest):
|
|
# Placeholder objects that represent accounts undergoing testing.
|
|
user = object()
|
|
mach = object()
|
|
|
|
# Constants for group SID attributes.
|
|
default_attrs = security.SE_GROUP_DEFAULT_FLAGS
|
|
resource_attrs = default_attrs | security.SE_GROUP_RESOURCE
|
|
|
|
asserted_identity = security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY
|
|
compounded_auth = security.SID_COMPOUNDED_AUTHENTICATION
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
cls._search_iterator = None
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.do_asn1_print = global_asn1_print
|
|
self.do_hexdump = global_hexdump
|
|
|
|
def get_sample_dn(self):
|
|
if self._search_iterator is None:
|
|
samdb = self.get_samdb()
|
|
type(self)._search_iterator = samdb.search_iterator()
|
|
|
|
return str(next(self._search_iterator).dn)
|
|
|
|
def get_binary_dn(self):
|
|
return 'B:8:01010101:' + self.get_sample_dn()
|
|
|
|
def setup_claims(self, all_claims):
|
|
expected_claims = {}
|
|
unexpected_claims = set()
|
|
|
|
details = {}
|
|
mod_msg = ldb.Message()
|
|
security_desc = None
|
|
|
|
for claim in all_claims:
|
|
# Make a copy to avoid modifying the original.
|
|
claim = dict(claim)
|
|
|
|
claim_id = self.get_new_username()
|
|
|
|
expected = claim.pop('expected', False)
|
|
expected_values = claim.pop('expected_values', None)
|
|
if not expected:
|
|
self.assertIsNone(expected_values,
|
|
'claim not expected, '
|
|
'but expected values provided')
|
|
|
|
values = claim.pop('values', None)
|
|
if values is not None:
|
|
def get_placeholder(val):
|
|
if val is self.sample_dn:
|
|
return self.get_sample_dn()
|
|
elif val is self.binary_dn:
|
|
return self.get_binary_dn()
|
|
else:
|
|
return val
|
|
|
|
def ldb_transform(val):
|
|
if val is True:
|
|
return 'TRUE'
|
|
elif val is False:
|
|
return 'FALSE'
|
|
elif isinstance(val, int):
|
|
return str(val)
|
|
else:
|
|
return val
|
|
|
|
values_type = type(values)
|
|
values = values_type(map(get_placeholder, values))
|
|
transformed_values = values_type(map(ldb_transform, values))
|
|
|
|
attribute = claim['attribute']
|
|
if attribute in details:
|
|
self.assertEqual(details[attribute], transformed_values,
|
|
'conflicting values set for attribute')
|
|
details[attribute] = transformed_values
|
|
|
|
readable = claim.pop('readable', True)
|
|
if not readable:
|
|
if security_desc is None:
|
|
security_desc = security.descriptor()
|
|
|
|
# Deny all read property access to the attribute.
|
|
ace = security.ace()
|
|
ace.type = security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
|
|
ace.access_mask = security.SEC_ADS_READ_PROP
|
|
ace.trustee = security.dom_sid(security.SID_WORLD)
|
|
ace.object.flags |= security.SEC_ACE_OBJECT_TYPE_PRESENT
|
|
ace.object.type = self.get_schema_id_guid_from_attribute(
|
|
attribute)
|
|
|
|
security_desc.dacl_add(ace)
|
|
|
|
if expected_values is None:
|
|
expected_values = values
|
|
|
|
mod_values = claim.pop('mod_values', None)
|
|
if mod_values is not None:
|
|
flag = (ldb.FLAG_MOD_REPLACE
|
|
if values is not None else ldb.FLAG_MOD_ADD)
|
|
mod_msg[attribute] = ldb.MessageElement(mod_values,
|
|
flag,
|
|
attribute)
|
|
|
|
if expected:
|
|
self.assertIsNotNone(expected_values,
|
|
'expected claim, but no value(s) set')
|
|
value_type = claim['value_type']
|
|
|
|
expected_claims[claim_id] = {
|
|
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
|
'type': value_type,
|
|
'values': expected_values,
|
|
}
|
|
else:
|
|
unexpected_claims.add(claim_id)
|
|
|
|
self.create_claim(claim_id, **claim)
|
|
|
|
if security_desc is not None:
|
|
self.assertNotIn('nTSecurityDescriptor', details)
|
|
details['nTSecurityDescriptor'] = ndr_pack(security_desc)
|
|
|
|
return details, mod_msg, expected_claims, unexpected_claims
|
|
|
|
def modify_pac_remove_client_claims(self, pac):
|
|
pac_buffers = pac.buffers
|
|
for pac_buffer in pac_buffers:
|
|
if pac_buffer.type == krb5pac.PAC_TYPE_CLIENT_CLAIMS_INFO:
|
|
pac.num_buffers -= 1
|
|
pac_buffers.remove(pac_buffer)
|
|
|
|
break
|
|
else:
|
|
self.fail('expected client claims in PAC')
|
|
|
|
pac.buffers = pac_buffers
|
|
|
|
return pac
|
|
|
|
def remove_client_claims(self, ticket):
|
|
return self.modified_ticket(
|
|
ticket,
|
|
modify_pac_fn=self.modify_pac_remove_client_claims,
|
|
checksum_keys=self.get_krbtgt_checksum_key())
|
|
|
|
def remove_client_claims_tgt_from_rodc(self, ticket):
|
|
rodc_krbtgt_creds = self.get_mock_rodc_krbtgt_creds()
|
|
rodc_krbtgt_key = self.TicketDecryptionKey_from_creds(
|
|
rodc_krbtgt_creds)
|
|
|
|
checksum_keys = {
|
|
krb5pac.PAC_TYPE_KDC_CHECKSUM: rodc_krbtgt_key
|
|
}
|
|
|
|
return self.modified_ticket(
|
|
ticket,
|
|
new_ticket_key=rodc_krbtgt_key,
|
|
modify_pac_fn=self.modify_pac_remove_client_claims,
|
|
checksum_keys=checksum_keys)
|
|
|
|
def test_tgs_claims(self):
|
|
self.run_tgs_test(remove_claims=False, to_krbtgt=False)
|
|
|
|
def test_tgs_claims_remove_claims(self):
|
|
self.run_tgs_test(remove_claims=True, to_krbtgt=False)
|
|
|
|
def test_tgs_claims_to_krbtgt(self):
|
|
self.run_tgs_test(remove_claims=False, to_krbtgt=True)
|
|
|
|
def test_tgs_claims_remove_claims_to_krbtgt(self):
|
|
self.run_tgs_test(remove_claims=True, to_krbtgt=True)
|
|
|
|
def test_delegation_claims(self):
|
|
self.run_delegation_test(remove_claims=False)
|
|
|
|
def test_delegation_claims_remove_claims(self):
|
|
self.run_delegation_test(remove_claims=True)
|
|
|
|
def test_rodc_issued_claims_modify(self):
|
|
self.run_rodc_tgs_test(remove_claims=False, delete_claim=False)
|
|
|
|
def test_rodc_issued_claims_delete(self):
|
|
self.run_rodc_tgs_test(remove_claims=False, delete_claim=True)
|
|
|
|
def test_rodc_issued_claims_remove_claims_modify(self):
|
|
self.run_rodc_tgs_test(remove_claims=True, delete_claim=False)
|
|
|
|
def test_rodc_issued_claims_remove_claims_delete(self):
|
|
self.run_rodc_tgs_test(remove_claims=True, delete_claim=True)
|
|
|
|
def test_rodc_issued_device_claims_modify(self):
|
|
self.run_device_rodc_tgs_test(remove_claims=False, delete_claim=False)
|
|
|
|
def test_rodc_issued_device_claims_delete(self):
|
|
self.run_device_rodc_tgs_test(remove_claims=False, delete_claim=True)
|
|
|
|
def test_rodc_issued_device_claims_remove_claims_modify(self):
|
|
self.run_device_rodc_tgs_test(remove_claims=True, delete_claim=False)
|
|
|
|
def test_rodc_issued_device_claims_remove_claims_delete(self):
|
|
self.run_device_rodc_tgs_test(remove_claims=True, delete_claim=True)
|
|
|
|
# Create a user account with an applicable claim for the 'middleName'
|
|
# attribute. After obtaining a TGT, from which we optionally remove the
|
|
# claims, change the middleName attribute values for the account in the
|
|
# database to a different value. By which we may observe, when examining
|
|
# the reply to our following Kerberos TGS request, whether the claims
|
|
# contained therein are taken directly from the ticket, or obtained fresh
|
|
# from the database.
|
|
def run_tgs_test(self, remove_claims, to_krbtgt):
|
|
samdb = self.get_samdb()
|
|
user_creds, user_dn = self.create_account(samdb,
|
|
self.get_new_username(),
|
|
additional_details={
|
|
'middleName': 'foo',
|
|
})
|
|
|
|
claim_id = self.get_new_username()
|
|
self.create_claim(claim_id,
|
|
enabled=True,
|
|
attribute='middleName',
|
|
single_valued=True,
|
|
source_type='AD',
|
|
for_classes=['user'],
|
|
value_type=claims.CLAIM_TYPE_STRING)
|
|
|
|
expected_claims = {
|
|
claim_id: {
|
|
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
|
'type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
},
|
|
}
|
|
|
|
# Get a TGT for the user.
|
|
tgt = self.get_tgt(user_creds, expect_pac=True,
|
|
expect_client_claims=True,
|
|
expected_client_claims=expected_claims)
|
|
|
|
if remove_claims:
|
|
tgt = self.remove_client_claims(tgt)
|
|
|
|
# Change the value of the attribute used for the claim.
|
|
msg = ldb.Message(ldb.Dn(samdb, user_dn))
|
|
msg['middleName'] = ldb.MessageElement('bar',
|
|
ldb.FLAG_MOD_REPLACE,
|
|
'middleName')
|
|
samdb.modify(msg)
|
|
|
|
if to_krbtgt:
|
|
target_creds = self.get_krbtgt_creds()
|
|
sname = self.get_krbtgt_sname()
|
|
else:
|
|
target_creds = self.get_service_creds()
|
|
sname = None
|
|
|
|
# Get a service ticket for the user. The claim value should not have
|
|
# changed, indicating that the client claims are propagated straight
|
|
# through.
|
|
self.get_service_ticket(
|
|
tgt, target_creds,
|
|
sname=sname,
|
|
expect_pac=True,
|
|
expect_client_claims=not remove_claims,
|
|
expected_client_claims=(expected_claims
|
|
if not remove_claims else None))
|
|
|
|
# Perform a test similar to that preceeding. This time, create both a user
|
|
# and a computer account, each having an applicable claim. After obtaining
|
|
# tickets, from which the claims are optionally removed, change the claim
|
|
# attribute of each account to a different value. Then perform constrained
|
|
# delegation with the user's service ticket, verifying that the user's
|
|
# claims are carried into the resulting ticket.
|
|
def run_delegation_test(self, remove_claims):
|
|
service_creds = self.get_service_creds()
|
|
service_spn = service_creds.get_spn()
|
|
|
|
user_name = self.get_new_username()
|
|
mach_name = self.get_new_username()
|
|
|
|
samdb = self.get_samdb()
|
|
user_creds, user_dn = self.create_account(
|
|
samdb,
|
|
user_name,
|
|
self.AccountType.USER,
|
|
additional_details={
|
|
'middleName': 'user_old',
|
|
})
|
|
mach_creds, mach_dn = self.create_account(
|
|
samdb,
|
|
mach_name,
|
|
self.AccountType.COMPUTER,
|
|
spn=f'host/{mach_name}',
|
|
additional_details={
|
|
'middleName': 'mach_old',
|
|
'msDS-AllowedToDelegateTo': service_spn,
|
|
})
|
|
|
|
claim_id = self.get_new_username()
|
|
self.create_claim(claim_id,
|
|
enabled=True,
|
|
attribute='middleName',
|
|
single_valued=True,
|
|
source_type='AD',
|
|
for_classes=['user', 'computer'],
|
|
value_type=claims.CLAIM_TYPE_STRING)
|
|
|
|
options = 'forwardable'
|
|
expected_flags = krb5_asn1.TicketFlags(options)
|
|
|
|
expected_claims_user = {
|
|
claim_id: {
|
|
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
|
'type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('user_old',),
|
|
},
|
|
}
|
|
expected_claims_mach = {
|
|
claim_id: {
|
|
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
|
'type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('mach_old',),
|
|
},
|
|
}
|
|
|
|
user_tgt = self.get_tgt(user_creds,
|
|
kdc_options=options,
|
|
expect_pac=True,
|
|
expected_flags=expected_flags,
|
|
expect_client_claims=True,
|
|
expected_client_claims=expected_claims_user)
|
|
user_ticket = self.get_service_ticket(
|
|
user_tgt,
|
|
mach_creds,
|
|
kdc_options=options,
|
|
expect_pac=True,
|
|
expected_flags=expected_flags,
|
|
expect_client_claims=True,
|
|
expected_client_claims=expected_claims_user)
|
|
|
|
mach_tgt = self.get_tgt(mach_creds,
|
|
expect_pac=True,
|
|
expect_client_claims=True,
|
|
expected_client_claims=expected_claims_mach)
|
|
|
|
if remove_claims:
|
|
user_ticket = self.remove_client_claims(user_ticket)
|
|
mach_tgt = self.remove_client_claims(mach_tgt)
|
|
|
|
# Change the value of the attribute used for the user claim.
|
|
msg = ldb.Message(ldb.Dn(samdb, user_dn))
|
|
msg['middleName'] = ldb.MessageElement('user_new',
|
|
ldb.FLAG_MOD_REPLACE,
|
|
'middleName')
|
|
samdb.modify(msg)
|
|
|
|
# Change the value of the attribute used for the machine claim.
|
|
msg = ldb.Message(ldb.Dn(samdb, mach_dn))
|
|
msg['middleName'] = ldb.MessageElement('mach_new',
|
|
ldb.FLAG_MOD_REPLACE,
|
|
'middleName')
|
|
samdb.modify(msg)
|
|
|
|
additional_tickets = [user_ticket.ticket]
|
|
options = str(krb5_asn1.KDCOptions('cname-in-addl-tkt'))
|
|
|
|
user_realm = user_creds.get_realm()
|
|
user_cname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
|
|
names=[user_name])
|
|
|
|
user_sid = self.get_objectSid(samdb, user_dn)
|
|
|
|
mach_realm = mach_creds.get_realm()
|
|
|
|
service_name = service_creds.get_username()[:-1]
|
|
service_realm = service_creds.get_realm()
|
|
service_sname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
|
|
names=['host', service_name])
|
|
service_decryption_key = self.TicketDecryptionKey_from_creds(
|
|
service_creds)
|
|
service_etypes = service_creds.tgs_supported_enctypes
|
|
|
|
expected_proxy_target = service_creds.get_spn()
|
|
expected_transited_services = [f'host/{mach_name}@{mach_realm}']
|
|
|
|
authenticator_subkey = self.RandomKey(Enctype.AES256)
|
|
|
|
etypes = (AES256_CTS_HMAC_SHA1_96, ARCFOUR_HMAC_MD5)
|
|
|
|
# The user's claims are propagated into the new ticket, while the
|
|
# machine's claims are dispensed with.
|
|
expected_claims = expected_claims_user if not remove_claims else None
|
|
|
|
# Perform constrained delegation.
|
|
kdc_exchange_dict = self.tgs_exchange_dict(
|
|
creds=user_creds,
|
|
expected_crealm=user_realm,
|
|
expected_cname=user_cname,
|
|
expected_srealm=service_realm,
|
|
expected_sname=service_sname,
|
|
expected_account_name=user_name,
|
|
expected_sid=user_sid,
|
|
expected_supported_etypes=service_etypes,
|
|
ticket_decryption_key=service_decryption_key,
|
|
check_rep_fn=self.generic_check_kdc_rep,
|
|
check_kdc_private_fn=self.generic_check_kdc_private,
|
|
tgt=mach_tgt,
|
|
authenticator_subkey=authenticator_subkey,
|
|
kdc_options=options,
|
|
expected_proxy_target=expected_proxy_target,
|
|
expected_transited_services=expected_transited_services,
|
|
expect_client_claims=not remove_claims,
|
|
expected_client_claims=expected_claims,
|
|
expect_device_claims=False,
|
|
expect_pac=True)
|
|
|
|
rep = self._generic_kdc_exchange(kdc_exchange_dict,
|
|
cname=None,
|
|
realm=service_realm,
|
|
sname=service_sname,
|
|
etypes=etypes,
|
|
additional_tickets=additional_tickets)
|
|
self.check_reply(rep, KRB_TGS_REP)
|
|
|
|
def run_rodc_tgs_test(self, remove_claims, delete_claim):
|
|
samdb = self.get_samdb()
|
|
# Create a user account permitted to replicate to the RODC.
|
|
user_creds = self.get_cached_creds(
|
|
account_type=self.AccountType.USER,
|
|
opts={
|
|
# Set the value of the claim attribute.
|
|
'additional_details': (('middleName', 'foo'),),
|
|
'allowed_replication_mock': True,
|
|
'revealed_to_mock_rodc': True,
|
|
},
|
|
use_cache=False)
|
|
user_dn = user_creds.get_dn()
|
|
|
|
# Create a claim that applies to the user.
|
|
claim_id = self.get_new_username()
|
|
self.create_claim(claim_id,
|
|
enabled=True,
|
|
attribute='middleName',
|
|
single_valued=True,
|
|
source_type='AD',
|
|
for_classes=['user'],
|
|
value_type=claims.CLAIM_TYPE_STRING)
|
|
|
|
expected_claims = {
|
|
claim_id: {
|
|
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
|
'type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
},
|
|
}
|
|
|
|
# Get a TGT for the user.
|
|
tgt = self.get_tgt(user_creds, expect_pac=True,
|
|
expect_client_claims=True,
|
|
expected_client_claims=expected_claims)
|
|
|
|
# Modify the TGT to be issued by an RODC. Optionally remove the client
|
|
# claims.
|
|
if remove_claims:
|
|
tgt = self.remove_client_claims_tgt_from_rodc(tgt)
|
|
else:
|
|
tgt = self.issued_by_rodc(tgt)
|
|
|
|
# Modify or delete the value of the attribute used for the claim. Modify
|
|
# our test expectations accordingly.
|
|
msg = ldb.Message(user_dn)
|
|
if delete_claim:
|
|
msg['middleName'] = ldb.MessageElement([],
|
|
ldb.FLAG_MOD_DELETE,
|
|
'middleName')
|
|
expected_claims = None
|
|
unexpected_claims = {claim_id}
|
|
else:
|
|
msg['middleName'] = ldb.MessageElement('bar',
|
|
ldb.FLAG_MOD_REPLACE,
|
|
'middleName')
|
|
expected_claims = {
|
|
claim_id: {
|
|
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
|
'type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('bar',),
|
|
},
|
|
}
|
|
unexpected_claims = None
|
|
samdb.modify(msg)
|
|
|
|
target_creds = self.get_service_creds()
|
|
|
|
# Get a service ticket for the user. The claim value should have
|
|
# changed, indicating that the client claims have been regenerated or
|
|
# removed, depending on whether the corresponding attribute is still
|
|
# present on the account.
|
|
self.get_service_ticket(
|
|
tgt, target_creds,
|
|
expect_pac=True,
|
|
# Expect the CLIENT_CLAIMS_INFO PAC buffer. It may be empty.
|
|
expect_client_claims=True,
|
|
expected_client_claims=expected_claims,
|
|
unexpected_client_claims=unexpected_claims)
|
|
|
|
def run_device_rodc_tgs_test(self, remove_claims, delete_claim):
|
|
samdb = self.get_samdb()
|
|
|
|
# Create the user account.
|
|
user_creds = self.get_cached_creds(
|
|
account_type=self.AccountType.USER)
|
|
user_name = user_creds.get_username()
|
|
|
|
# Create a machine account permitted to replicate to the RODC.
|
|
mach_creds = self.get_cached_creds(
|
|
account_type=self.AccountType.COMPUTER,
|
|
opts={
|
|
# Set the value of the claim attribute.
|
|
'additional_details': (('middleName', 'foo'),),
|
|
'allowed_replication_mock': True,
|
|
'revealed_to_mock_rodc': True,
|
|
},
|
|
use_cache=False)
|
|
mach_dn = mach_creds.get_dn()
|
|
|
|
# Create a claim that applies to the computer.
|
|
claim_id = self.get_new_username()
|
|
self.create_claim(claim_id,
|
|
enabled=True,
|
|
attribute='middleName',
|
|
single_valued=True,
|
|
source_type='AD',
|
|
for_classes=['computer'],
|
|
value_type=claims.CLAIM_TYPE_STRING)
|
|
|
|
expected_claims = {
|
|
claim_id: {
|
|
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
|
'type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
},
|
|
}
|
|
|
|
# Get a TGT for the user.
|
|
user_tgt = self.get_tgt(user_creds)
|
|
|
|
# Get a TGT for the computer.
|
|
mach_tgt = self.get_tgt(mach_creds, expect_pac=True,
|
|
expect_client_claims=True,
|
|
expected_client_claims=expected_claims)
|
|
|
|
# Modify the computer's TGT to be issued by an RODC. Optionally remove
|
|
# the client claims.
|
|
if remove_claims:
|
|
mach_tgt = self.remove_client_claims_tgt_from_rodc(mach_tgt)
|
|
else:
|
|
mach_tgt = self.issued_by_rodc(mach_tgt)
|
|
|
|
# Modify or delete the value of the attribute used for the claim. Modify
|
|
# our test expectations accordingly.
|
|
msg = ldb.Message(mach_dn)
|
|
if delete_claim:
|
|
msg['middleName'] = ldb.MessageElement([],
|
|
ldb.FLAG_MOD_DELETE,
|
|
'middleName')
|
|
expected_claims = None
|
|
unexpected_claims = {claim_id}
|
|
else:
|
|
msg['middleName'] = ldb.MessageElement('bar',
|
|
ldb.FLAG_MOD_REPLACE,
|
|
'middleName')
|
|
expected_claims = {
|
|
claim_id: {
|
|
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
|
'type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('bar',),
|
|
},
|
|
}
|
|
unexpected_claims = None
|
|
samdb.modify(msg)
|
|
|
|
subkey = self.RandomKey(user_tgt.session_key.etype)
|
|
|
|
armor_subkey = self.RandomKey(subkey.etype)
|
|
explicit_armor_key = self.generate_armor_key(armor_subkey,
|
|
mach_tgt.session_key)
|
|
armor_key = kcrypto.cf2(explicit_armor_key.key,
|
|
subkey.key,
|
|
b'explicitarmor',
|
|
b'tgsarmor')
|
|
armor_key = Krb5EncryptionKey(armor_key, None)
|
|
|
|
target_creds = self.get_service_creds()
|
|
target_name = target_creds.get_username()
|
|
if target_name[-1] == '$':
|
|
target_name = target_name[:-1]
|
|
|
|
sname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
|
|
names=['host', target_name])
|
|
srealm = target_creds.get_realm()
|
|
|
|
decryption_key = self.TicketDecryptionKey_from_creds(
|
|
target_creds)
|
|
|
|
target_supported_etypes = target_creds.tgs_supported_enctypes
|
|
|
|
etypes = (AES256_CTS_HMAC_SHA1_96, ARCFOUR_HMAC_MD5)
|
|
|
|
kdc_options = '0'
|
|
pac_options = '1' # claims support
|
|
|
|
# Perform a TGS-REQ for the user. The device claim value should have
|
|
# changed, indicating that the computer's client claims have been
|
|
# regenerated or removed, depending on whether the corresponding
|
|
# attribute is still present on the account.
|
|
|
|
kdc_exchange_dict = self.tgs_exchange_dict(
|
|
creds=user_creds,
|
|
expected_crealm=user_tgt.crealm,
|
|
expected_cname=user_tgt.cname,
|
|
expected_srealm=srealm,
|
|
expected_sname=sname,
|
|
expected_account_name=user_name,
|
|
ticket_decryption_key=decryption_key,
|
|
generate_fast_fn=self.generate_simple_fast,
|
|
generate_fast_armor_fn=self.generate_ap_req,
|
|
check_rep_fn=self.generic_check_kdc_rep,
|
|
check_kdc_private_fn=self.generic_check_kdc_private,
|
|
tgt=user_tgt,
|
|
armor_key=armor_key,
|
|
armor_tgt=mach_tgt,
|
|
armor_subkey=armor_subkey,
|
|
pac_options=pac_options,
|
|
authenticator_subkey=subkey,
|
|
kdc_options=kdc_options,
|
|
expect_pac=True,
|
|
expected_supported_etypes=target_supported_etypes,
|
|
# Expect the DEVICE_CLAIMS_INFO PAC buffer. It may be empty.
|
|
expect_device_claims=True,
|
|
expected_device_claims=expected_claims,
|
|
unexpected_device_claims=unexpected_claims)
|
|
|
|
rep = self._generic_kdc_exchange(kdc_exchange_dict,
|
|
cname=None,
|
|
realm=srealm,
|
|
sname=sname,
|
|
etypes=etypes)
|
|
self.check_reply(rep, KRB_TGS_REP)
|
|
|
|
@staticmethod
|
|
def freeze(m):
|
|
return frozenset((k, v) for k, v in m.items())
|
|
|
|
@classmethod
|
|
def setUpDynamicTestCases(cls):
|
|
FILTER = env_get_var_value('FILTER', allow_missing=True)
|
|
for case in cls.cases:
|
|
name = case.pop('name')
|
|
if FILTER and not re.search(FILTER, name):
|
|
continue
|
|
name = re.sub(r'\W+', '_', name)
|
|
|
|
# Run tests making requests both to the krbtgt and to our own
|
|
# account.
|
|
cls.generate_dynamic_test('test_claims', name,
|
|
dict(case), False)
|
|
cls.generate_dynamic_test('test_claims', name + '_to_self',
|
|
dict(case), True)
|
|
|
|
for case in cls.device_claims_cases:
|
|
name = case.pop('test')
|
|
if FILTER and not re.search(FILTER, name):
|
|
continue
|
|
name = re.sub(r'\W+', '_', name)
|
|
|
|
cls.generate_dynamic_test('test_device_claims', name,
|
|
dict(case))
|
|
|
|
def _test_claims_with_args(self, case, to_self):
|
|
account_class = case.pop('class')
|
|
if account_class == 'user':
|
|
account_type = self.AccountType.USER
|
|
elif account_class == 'computer':
|
|
account_type = self.AccountType.COMPUTER
|
|
else:
|
|
self.fail(f'Unknown class "{account_class}"')
|
|
|
|
all_claims = case.pop('claims')
|
|
(details, mod_msg,
|
|
expected_claims,
|
|
unexpected_claims) = self.setup_claims(all_claims)
|
|
self.assertFalse(mod_msg,
|
|
'mid-test modifications not supported in this test')
|
|
creds = self.get_cached_creds(
|
|
account_type=account_type,
|
|
opts={
|
|
'additional_details': self.freeze(details),
|
|
})
|
|
|
|
# Whether to specify claims support in PA-PAC-OPTIONS.
|
|
pac_options_claims = case.pop('pac-options:claims-support', None)
|
|
|
|
self.assertFalse(case, 'unexpected parameters in testcase')
|
|
|
|
if pac_options_claims is None:
|
|
pac_options_claims = True
|
|
|
|
if to_self:
|
|
service_creds = self.get_service_creds()
|
|
sname = self.PrincipalName_create(
|
|
name_type=NT_PRINCIPAL,
|
|
names=[service_creds.get_username()])
|
|
ticket_etype = Enctype.RC4
|
|
else:
|
|
service_creds = None
|
|
sname = None
|
|
ticket_etype = None
|
|
|
|
if pac_options_claims:
|
|
pac_options = '1' # claims support
|
|
else:
|
|
pac_options = '0' # no claims support
|
|
|
|
self.get_tgt(creds,
|
|
sname=sname,
|
|
target_creds=service_creds,
|
|
ticket_etype=ticket_etype,
|
|
pac_options=pac_options,
|
|
expect_pac=True,
|
|
expect_client_claims=True,
|
|
expected_client_claims=expected_claims or None,
|
|
unexpected_client_claims=unexpected_claims or None)
|
|
|
|
sample_dn = object()
|
|
binary_dn = object()
|
|
security_descriptor = (b'\x01\x00\x04\x95\x14\x00\x00\x00\x00\x00\x00\x00'
|
|
b'\x00\x00\x00\x00$\x00\x00\x00\x01\x02\x00\x00\x00'
|
|
b'\x00\x00\x05 \x00\x00\x00 \x02\x00\x00\x04\x00'
|
|
b'\x1c\x00\x01\x00\x00\x00\x00\x1f\x14\x00\xff\x01'
|
|
b'\x0f\xf0\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
b'\x00\x00')
|
|
|
|
cases = [
|
|
{
|
|
'name': 'no claims',
|
|
'claims': [],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'simple AD-sourced claim',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'no claims support in pac options',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
# We still get claims in the PAC even if we don't specify
|
|
# claims support in PA-PAC-OPTIONS.
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
'pac-options:claims-support': False,
|
|
},
|
|
{
|
|
'name': 'deny RP',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
# Deny read access to the attribute. It still shows up in
|
|
# the claim.
|
|
'readable': False,
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
# Note: The order of these DNs may differ on Windows.
|
|
'name': 'dn string syntax',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.1
|
|
'enabled': True,
|
|
'attribute': 'msDS-AuthenticatedAtDC',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': UnorderedList([sample_dn, sample_dn, sample_dn]),
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'dn string syntax, wrong value type',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.1
|
|
'enabled': True,
|
|
'attribute': 'msDS-AuthenticatedAtDC',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_BOOLEAN,
|
|
'values': UnorderedList([sample_dn, sample_dn, sample_dn]),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'oid syntax',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.2
|
|
'enabled': True,
|
|
'attribute': 'objectClass',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_UINT64,
|
|
'expected_values': [655369, 65543, 65542, 65536],
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'oid syntax 2',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.2
|
|
'enabled': True,
|
|
'attribute': 'objectClass',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['computer'],
|
|
'value_type': claims.CLAIM_TYPE_UINT64,
|
|
'expected_values': [196638, 655369, 65543, 65542, 65536],
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'computer',
|
|
},
|
|
{
|
|
'name': 'oid syntax, wrong value type',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.2
|
|
'enabled': True,
|
|
'attribute': 'objectClass',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_INT64,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'boolean syntax, true',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.8
|
|
'enabled': True,
|
|
'attribute': 'msTSAllowLogon',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_BOOLEAN,
|
|
'values': (True,),
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'boolean syntax, false',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.8
|
|
'enabled': True,
|
|
'attribute': 'msTSAllowLogon',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_BOOLEAN,
|
|
'values': (False,),
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'boolean syntax, wrong value type',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.8
|
|
'enabled': True,
|
|
'attribute': 'msTSAllowLogon',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': (True,),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
# This test fails on Windows, which for an integer syntax claim
|
|
# issues corrupt data shifted four bytes to the right.
|
|
'name': 'integer syntax',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.9
|
|
'enabled': True,
|
|
'attribute': 'localeID',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_INT64,
|
|
'values': (3, 42, -999, 1000, 20000),
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
# This test fails on Windows, which for an integer syntax claim
|
|
# issues corrupt data that cannot be NDR unpacked.
|
|
'name': 'integer syntax, duplicate claim',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.9
|
|
'enabled': True,
|
|
'attribute': 'localeID',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_INT64,
|
|
'values': (3, 42, -999, 1000, 20000),
|
|
'expected': True,
|
|
},
|
|
] * 2, # Create two integer syntax claims.
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'integer syntax, wrong value type',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.9
|
|
'enabled': True,
|
|
'attribute': 'localeID',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_UINT64,
|
|
'values': (3, 42, -999, 1000),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'security descriptor syntax',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.15
|
|
'enabled': True,
|
|
'attribute': 'msDS-AllowedToActOnBehalfOfOtherIdentity',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['computer'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': (security_descriptor,),
|
|
'expected_values': (
|
|
'O:BAD:PARAI(A;OICINPIOID;CCDCLCSWRPWPDTLOCRSDRCWDWOGAGXGWGR;;;S-1-0-0)',
|
|
),
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'computer',
|
|
},
|
|
{
|
|
'name': 'security descriptor syntax, wrong value type',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.15
|
|
'enabled': True,
|
|
'attribute': 'msDS-AllowedToActOnBehalfOfOtherIdentity',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['computer'],
|
|
'value_type': claims.CLAIM_TYPE_UINT64,
|
|
'values': (security_descriptor,),
|
|
},
|
|
],
|
|
'class': 'computer',
|
|
},
|
|
{
|
|
'name': 'case insensitive string syntax (invalid)',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.4
|
|
'enabled': True,
|
|
'attribute': 'networkAddress',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo', 'bar'),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'printable string syntax (invalid)',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.5
|
|
'enabled': True,
|
|
'attribute': 'displayNamePrintable',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'numeric string syntax (invalid)',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.6
|
|
'enabled': True,
|
|
'attribute': 'internationalISDNNumber',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo', 'bar'),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'dn binary syntax (invalid)',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.7
|
|
'enabled': True,
|
|
'attribute': 'msDS-RevealedUsers',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': (binary_dn, binary_dn, binary_dn),
|
|
},
|
|
],
|
|
'class': 'computer',
|
|
},
|
|
{
|
|
'name': 'octet string syntax (invalid)',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.10
|
|
'enabled': True,
|
|
'attribute': 'jpegPhoto',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo', 'bar'),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'utc time syntax (invalid)',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.11
|
|
'enabled': True,
|
|
'attribute': 'msTSExpireDate2',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('19700101000000.0Z',),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'access point syntax (invalid)',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.17
|
|
'enabled': True,
|
|
'attribute': 'mS-DS-CreatorSID',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'no value set',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'multi-valued claim',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo', 'bar', 'baz'),
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'missing attribute',
|
|
'claims': [
|
|
{
|
|
'enabled': True,
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'invalid attribute',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.10
|
|
'enabled': True,
|
|
'attribute': 'unicodePwd',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'incorrect value type',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_INT64,
|
|
'values': ('foo',),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'invalid value type',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': 0,
|
|
'values': ('foo',),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'missing value type',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'values': ('foo',),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'string syntax, duplicate claim',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
'expected': True,
|
|
},
|
|
] * 2, # Create two string syntax claims.
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'multiple claims',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo', 'bar', 'baz'),
|
|
'expected': True,
|
|
},
|
|
{
|
|
# 2.5.5.8
|
|
'enabled': True,
|
|
'attribute': 'msTSAllowLogon',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_BOOLEAN,
|
|
'values': (True,),
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'case difference for source type',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'ad',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'unhandled source type',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': '<unknown>',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'disabled claim',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': False,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'not enabled claim',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'not applicable to any class',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'not applicable to class',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
},
|
|
],
|
|
'class': 'computer',
|
|
},
|
|
{
|
|
'name': 'applicable to class',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user', 'computer'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'computer',
|
|
},
|
|
{
|
|
'name': 'applicable to base class',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['top'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'applicable to base class 2',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['organizationalPerson'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
{
|
|
'name': 'large compressed claim',
|
|
'claims': [
|
|
{
|
|
# 2.5.5.12
|
|
'enabled': True,
|
|
'attribute': 'carLicense',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['user'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
# a large value that should cause the claim to be
|
|
# compressed.
|
|
'values': ('a' * 10000,),
|
|
'expected': True,
|
|
},
|
|
],
|
|
'class': 'user',
|
|
},
|
|
]
|
|
|
|
def _test_device_claims_with_args(self, case):
|
|
# The group arrangement for the test.
|
|
group_setup = case.pop('groups')
|
|
|
|
# Groups that should be the primary group for the user and machine
|
|
# respectively.
|
|
primary_group = case.pop('primary_group', None)
|
|
mach_primary_group = case.pop('mach:primary_group', None)
|
|
|
|
# Whether the TGS-REQ should be directed to the krbtgt.
|
|
tgs_to_krbtgt = case.pop('tgs:to_krbtgt', None)
|
|
|
|
# Whether the target server of the TGS-REQ should support compound
|
|
# identity or resource SID compression.
|
|
tgs_compound_id = case.pop('tgs:compound_id', None)
|
|
tgs_compression = case.pop('tgs:compression', None)
|
|
|
|
# Optional SIDs to replace those in the machine account PAC prior to a
|
|
# TGS-REQ.
|
|
tgs_mach_sids = case.pop('tgs:mach:sids', None)
|
|
|
|
# Optional machine SID to replace that in the PAC prior to a TGS-REQ.
|
|
tgs_mach_sid = case.pop('tgs:mach_sid', None)
|
|
|
|
# User flags that may be set or reset in the PAC prior to a TGS-REQ.
|
|
tgs_mach_set_user_flags = case.pop('tgs:mach:set_user_flags', None)
|
|
tgs_mach_reset_user_flags = case.pop('tgs:mach:reset_user_flags', None)
|
|
|
|
# The SIDs we expect to see in the PAC after a AS-REQ or a TGS-REQ.
|
|
as_expected = case.pop('as:expected', None)
|
|
as_mach_expected = case.pop('as:mach:expected', None)
|
|
tgs_expected = case.pop('tgs:expected', None)
|
|
tgs_device_expected = case.pop('tgs:device:expected', None)
|
|
|
|
# Whether to specify claims support in PA-PAC-OPTIONS.
|
|
pac_options_claims = case.pop('pac-options:claims-support', None)
|
|
|
|
all_claims = case.pop('claims')
|
|
|
|
# There should be no parameters remaining in the testcase.
|
|
self.assertFalse(case, 'unexpected parameters in testcase')
|
|
|
|
if as_expected is None:
|
|
self.assertIsNotNone(tgs_expected,
|
|
'no set of expected SIDs is provided')
|
|
|
|
if as_mach_expected is None:
|
|
self.assertIsNotNone(tgs_expected,
|
|
'no set of expected machine SIDs is provided')
|
|
|
|
if tgs_to_krbtgt is None:
|
|
tgs_to_krbtgt = False
|
|
|
|
if tgs_compound_id is None and not tgs_to_krbtgt:
|
|
# Assume the service supports compound identity by default.
|
|
tgs_compound_id = True
|
|
|
|
if tgs_to_krbtgt:
|
|
self.assertIsNone(tgs_device_expected,
|
|
'device SIDs are not added for a krbtgt request')
|
|
|
|
self.assertIsNotNone(tgs_expected,
|
|
'no set of expected TGS SIDs is provided')
|
|
|
|
if tgs_mach_sid is not None:
|
|
self.assertIsNotNone(tgs_mach_sids,
|
|
'specified TGS-REQ mach SID, but no '
|
|
'accompanying machine SIDs provided')
|
|
|
|
if tgs_mach_set_user_flags is None:
|
|
tgs_mach_set_user_flags = 0
|
|
else:
|
|
self.assertIsNotNone(tgs_mach_sids,
|
|
'specified TGS-REQ set user flags, but no '
|
|
'accompanying machine SIDs provided')
|
|
|
|
if tgs_mach_reset_user_flags is None:
|
|
tgs_mach_reset_user_flags = 0
|
|
else:
|
|
self.assertIsNotNone(tgs_mach_sids,
|
|
'specified TGS-REQ reset user flags, but no '
|
|
'accompanying machine SIDs provided')
|
|
|
|
if pac_options_claims is None:
|
|
pac_options_claims = True
|
|
|
|
(details, mod_msg,
|
|
expected_claims,
|
|
unexpected_claims) = self.setup_claims(all_claims)
|
|
|
|
samdb = self.get_samdb()
|
|
|
|
domain_sid = samdb.get_domain_sid()
|
|
|
|
user_creds = self.get_cached_creds(
|
|
account_type=self.AccountType.USER)
|
|
user_dn = user_creds.get_dn()
|
|
user_sid = self.get_objectSid(samdb, user_dn)
|
|
|
|
mach_name = self.get_new_username()
|
|
mach_creds, mach_dn_str = self.create_account(
|
|
samdb,
|
|
mach_name,
|
|
account_type=self.AccountType.COMPUTER,
|
|
additional_details=details)
|
|
mach_dn = ldb.Dn(samdb, mach_dn_str)
|
|
mach_sid = self.get_objectSid(samdb, mach_dn)
|
|
|
|
user_principal = Principal(user_dn, user_sid)
|
|
mach_principal = Principal(mach_dn, mach_sid)
|
|
preexisting_groups = {
|
|
self.user: user_principal,
|
|
self.mach: mach_principal,
|
|
}
|
|
primary_groups = {}
|
|
if primary_group is not None:
|
|
primary_groups[user_principal] = primary_group
|
|
if mach_primary_group is not None:
|
|
primary_groups[mach_principal] = mach_primary_group
|
|
groups = self.setup_groups(samdb,
|
|
preexisting_groups,
|
|
group_setup,
|
|
primary_groups)
|
|
del group_setup
|
|
|
|
tgs_user_sid = user_sid
|
|
tgs_user_domain_sid, tgs_user_rid = tgs_user_sid.rsplit('-', 1)
|
|
|
|
if tgs_mach_sid is None:
|
|
tgs_mach_sid = mach_sid
|
|
elif tgs_mach_sid in groups:
|
|
tgs_mach_sid = groups[tgs_mach_sid].sid
|
|
|
|
tgs_mach_domain_sid, tgs_mach_rid = tgs_mach_sid.rsplit('-', 1)
|
|
|
|
expected_groups = self.map_sids(as_expected, groups,
|
|
domain_sid)
|
|
mach_expected_groups = self.map_sids(as_mach_expected, groups,
|
|
domain_sid)
|
|
tgs_mach_sids_mapped = self.map_sids(tgs_mach_sids, groups,
|
|
tgs_mach_domain_sid)
|
|
tgs_expected_mapped = self.map_sids(tgs_expected, groups,
|
|
tgs_user_domain_sid)
|
|
tgs_device_expected_mapped = self.map_sids(tgs_device_expected, groups,
|
|
tgs_mach_domain_sid)
|
|
|
|
user_tgt = self.get_tgt(user_creds, expected_groups=expected_groups)
|
|
|
|
# Get a TGT for the computer.
|
|
mach_tgt = self.get_tgt(mach_creds, expect_pac=True,
|
|
expected_groups=mach_expected_groups,
|
|
expect_client_claims=True,
|
|
expected_client_claims=expected_claims,
|
|
unexpected_client_claims=unexpected_claims)
|
|
|
|
if tgs_mach_sids is not None:
|
|
# Replace the SIDs in the PAC with the ones provided by the test.
|
|
mach_tgt = self.ticket_with_sids(mach_tgt,
|
|
tgs_mach_sids_mapped,
|
|
tgs_mach_domain_sid,
|
|
tgs_mach_rid,
|
|
set_user_flags=tgs_mach_set_user_flags,
|
|
reset_user_flags=tgs_mach_reset_user_flags)
|
|
|
|
if mod_msg:
|
|
self.assertFalse(tgs_to_krbtgt,
|
|
'device claims are omitted for a krbtgt request, '
|
|
'so specifying mod_values is probably a mistake!')
|
|
|
|
# Change the value of attributes used for claims.
|
|
mod_msg.dn = mach_dn
|
|
samdb.modify(mod_msg)
|
|
|
|
domain_sid = samdb.get_domain_sid()
|
|
|
|
subkey = self.RandomKey(user_tgt.session_key.etype)
|
|
|
|
armor_subkey = self.RandomKey(subkey.etype)
|
|
explicit_armor_key = self.generate_armor_key(armor_subkey,
|
|
mach_tgt.session_key)
|
|
armor_key = kcrypto.cf2(explicit_armor_key.key,
|
|
subkey.key,
|
|
b'explicitarmor',
|
|
b'tgsarmor')
|
|
armor_key = Krb5EncryptionKey(armor_key, None)
|
|
|
|
target_creds, sname = self.get_target(
|
|
to_krbtgt=tgs_to_krbtgt,
|
|
compound_id=tgs_compound_id,
|
|
compression=tgs_compression)
|
|
srealm = target_creds.get_realm()
|
|
|
|
decryption_key = self.TicketDecryptionKey_from_creds(
|
|
target_creds)
|
|
|
|
etypes = (AES256_CTS_HMAC_SHA1_96, ARCFOUR_HMAC_MD5)
|
|
|
|
kdc_options = '0'
|
|
if pac_options_claims:
|
|
pac_options = '1' # claims support
|
|
else:
|
|
pac_options = '0' # no claims support
|
|
|
|
requester_sid = None
|
|
if tgs_to_krbtgt:
|
|
requester_sid = user_sid
|
|
|
|
if tgs_to_krbtgt:
|
|
expected_claims = None
|
|
unexpected_claims = None
|
|
|
|
# Get a service ticket for the user, using the computer's TGT as an
|
|
# armor TGT. The claim value should not have changed.
|
|
|
|
kdc_exchange_dict = self.tgs_exchange_dict(
|
|
creds=user_creds,
|
|
expected_crealm=user_tgt.crealm,
|
|
expected_cname=user_tgt.cname,
|
|
expected_srealm=srealm,
|
|
expected_sname=sname,
|
|
ticket_decryption_key=decryption_key,
|
|
generate_fast_fn=self.generate_simple_fast,
|
|
generate_fast_armor_fn=self.generate_ap_req,
|
|
check_rep_fn=self.generic_check_kdc_rep,
|
|
check_kdc_private_fn=self.generic_check_kdc_private,
|
|
tgt=user_tgt,
|
|
armor_key=armor_key,
|
|
armor_tgt=mach_tgt,
|
|
armor_subkey=armor_subkey,
|
|
pac_options=pac_options,
|
|
authenticator_subkey=subkey,
|
|
kdc_options=kdc_options,
|
|
expect_pac=True,
|
|
expect_pac_attrs=tgs_to_krbtgt,
|
|
expect_pac_attrs_pac_request=tgs_to_krbtgt,
|
|
expected_sid=tgs_user_sid,
|
|
expected_requester_sid=requester_sid,
|
|
expected_domain_sid=tgs_user_domain_sid,
|
|
expected_device_domain_sid=tgs_mach_domain_sid,
|
|
expected_groups=tgs_expected_mapped,
|
|
unexpected_groups=None,
|
|
expect_client_claims=True,
|
|
expected_client_claims=None,
|
|
expect_device_info=not tgs_to_krbtgt,
|
|
expected_device_groups=tgs_device_expected_mapped,
|
|
expect_device_claims=not tgs_to_krbtgt,
|
|
expected_device_claims=expected_claims,
|
|
unexpected_device_claims=unexpected_claims)
|
|
|
|
rep = self._generic_kdc_exchange(kdc_exchange_dict,
|
|
cname=None,
|
|
realm=srealm,
|
|
sname=sname,
|
|
etypes=etypes)
|
|
self.check_reply(rep, KRB_TGS_REP)
|
|
|
|
device_claims_cases = [
|
|
{
|
|
# Make a TGS request containing claims, but omit the Claims Valid
|
|
# SID.
|
|
'test': 'device to service no claims valid sid',
|
|
'groups': {
|
|
# Some groups to test how the device info is generated.
|
|
'foo': (GroupType.DOMAIN_LOCAL, {mach}),
|
|
'bar': (GroupType.DOMAIN_LOCAL, {mach}),
|
|
},
|
|
'claims': [
|
|
{
|
|
# 2.5.5.10
|
|
'enabled': True,
|
|
'attribute': 'middleName',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['computer'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
'expected': True,
|
|
'mod_values': ['bar'],
|
|
},
|
|
],
|
|
'as:expected': {
|
|
(asserted_identity, SidType.EXTRA_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
|
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
|
|
},
|
|
'as:mach:expected': {
|
|
(asserted_identity, SidType.EXTRA_SID, default_attrs),
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
|
|
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
|
|
},
|
|
'tgs:mach:sids': {
|
|
(asserted_identity, SidType.EXTRA_SID, default_attrs),
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
|
|
# Omit the Claims Valid SID, and verify that this doesn't
|
|
# affect the propagation of claims into the final ticket.
|
|
|
|
# Some extra SIDs to show how they are propagated into the
|
|
# final ticket.
|
|
('S-1-5-22-1-2-3-4', SidType.EXTRA_SID, default_attrs),
|
|
('S-1-5-22-1-2-3-5', SidType.EXTRA_SID, default_attrs),
|
|
},
|
|
'tgs:to_krbtgt': False,
|
|
'tgs:expected': {
|
|
(security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY, SidType.EXTRA_SID, default_attrs),
|
|
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
|
|
(security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
|
},
|
|
'tgs:device:expected': {
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
|
|
(asserted_identity, SidType.EXTRA_SID, default_attrs),
|
|
('S-1-5-22-1-2-3-4', SidType.EXTRA_SID, default_attrs),
|
|
('S-1-5-22-1-2-3-5', SidType.EXTRA_SID, default_attrs),
|
|
frozenset([
|
|
('foo', SidType.RESOURCE_SID, resource_attrs),
|
|
('bar', SidType.RESOURCE_SID, resource_attrs),
|
|
]),
|
|
},
|
|
},
|
|
{
|
|
# Make a TGS request containing claims to a service that lacks
|
|
# support for compound identity. The claims are still propagated to
|
|
# the final ticket.
|
|
'test': 'device to service no compound id',
|
|
'groups': {
|
|
'foo': (GroupType.DOMAIN_LOCAL, {mach}),
|
|
'bar': (GroupType.DOMAIN_LOCAL, {mach}),
|
|
},
|
|
'claims': [
|
|
{
|
|
# 2.5.5.10
|
|
'enabled': True,
|
|
'attribute': 'middleName',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['computer'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
'expected': True,
|
|
'mod_values': ['bar'],
|
|
},
|
|
],
|
|
'as:expected': {
|
|
(asserted_identity, SidType.EXTRA_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
|
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
|
|
},
|
|
'as:mach:expected': {
|
|
(asserted_identity, SidType.EXTRA_SID, default_attrs),
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
|
|
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
|
|
},
|
|
'tgs:to_krbtgt': False,
|
|
# Compound identity is unsupported.
|
|
'tgs:compound_id': False,
|
|
'tgs:expected': {
|
|
(security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY, SidType.EXTRA_SID, default_attrs),
|
|
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
|
|
(security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
|
},
|
|
'tgs:device:expected': {
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
|
|
frozenset([
|
|
('foo', SidType.RESOURCE_SID, resource_attrs),
|
|
('bar', SidType.RESOURCE_SID, resource_attrs),
|
|
]),
|
|
(asserted_identity, SidType.EXTRA_SID, default_attrs),
|
|
frozenset([(security.SID_CLAIMS_VALID, SidType.RESOURCE_SID, default_attrs)]),
|
|
},
|
|
},
|
|
{
|
|
# Make a TGS request containing claims to a service, but don't
|
|
# specify support for claims in PA-PAC-OPTIONS. We still expect the
|
|
# final PAC to contain claims.
|
|
'test': 'device to service no claims support in pac options',
|
|
'groups': {
|
|
'foo': (GroupType.DOMAIN_LOCAL, {mach}),
|
|
'bar': (GroupType.DOMAIN_LOCAL, {mach}),
|
|
},
|
|
'claims': [
|
|
{
|
|
# 2.5.5.10
|
|
'enabled': True,
|
|
'attribute': 'middleName',
|
|
'single_valued': True,
|
|
'source_type': 'AD',
|
|
'for_classes': ['computer'],
|
|
'value_type': claims.CLAIM_TYPE_STRING,
|
|
'values': ('foo',),
|
|
'expected': True,
|
|
'mod_values': ['bar'],
|
|
},
|
|
],
|
|
'as:expected': {
|
|
(asserted_identity, SidType.EXTRA_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
|
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
|
|
},
|
|
'as:mach:expected': {
|
|
(asserted_identity, SidType.EXTRA_SID, default_attrs),
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
|
|
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
|
|
},
|
|
'tgs:to_krbtgt': False,
|
|
# Claims are unsupported.
|
|
'pac-options:claims-support': False,
|
|
'tgs:expected': {
|
|
(security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY, SidType.EXTRA_SID, default_attrs),
|
|
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, default_attrs),
|
|
(security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
|
},
|
|
'tgs:device:expected': {
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.BASE_SID, default_attrs),
|
|
(security.DOMAIN_RID_DOMAIN_MEMBERS, SidType.PRIMARY_GID, None),
|
|
frozenset([
|
|
('foo', SidType.RESOURCE_SID, resource_attrs),
|
|
('bar', SidType.RESOURCE_SID, resource_attrs),
|
|
]),
|
|
(asserted_identity, SidType.EXTRA_SID, default_attrs),
|
|
frozenset([(security.SID_CLAIMS_VALID, SidType.RESOURCE_SID, default_attrs)]),
|
|
},
|
|
},
|
|
]
|
|
|
|
def test_auth_silo_claim(self):
|
|
self.run_auth_silo_claim_test()
|
|
|
|
def test_auth_silo_claim_unenforced(self):
|
|
# The claim is not present if the silo is unenforced.
|
|
self.run_auth_silo_claim_test(enforced=False,
|
|
expect_claim=False)
|
|
|
|
def test_auth_silo_claim_not_a_member(self):
|
|
# The claim is not present if the user is not a member of the silo.
|
|
self.run_auth_silo_claim_test(add_to_silo=False,
|
|
expect_claim=False)
|
|
|
|
def test_auth_silo_claim_unassigned(self):
|
|
# The claim is not present if the user is not assigned to the silo.
|
|
self.run_auth_silo_claim_test(assigned=False,
|
|
expect_claim=False)
|
|
|
|
def test_auth_silo_claim_assigned_to_wrong_dn(self):
|
|
samdb = self.get_samdb()
|
|
|
|
# The claim is not present if the user is assigned to some other DN.
|
|
self.run_auth_silo_claim_test(assigned=self.get_server_dn(samdb),
|
|
expect_claim=False)
|
|
|
|
def run_auth_silo_claim_test(self, *,
|
|
enforced=True,
|
|
add_to_silo=True,
|
|
assigned=True,
|
|
expect_claim=True):
|
|
# Create a new authentication silo.
|
|
silo_id = self.get_new_username()
|
|
silo_dn = self.create_auth_silo(silo_id, enforced=enforced)
|
|
|
|
account_options = None
|
|
if assigned is not False:
|
|
if assigned is True:
|
|
assigned = silo_dn
|
|
|
|
account_options = {
|
|
'additional_details': self.freeze({
|
|
# The user is assigned to the authentication silo we just
|
|
# created, or to some DN specified by a test.
|
|
'msDS-AssignedAuthNPolicySilo': str(assigned),
|
|
}),
|
|
}
|
|
|
|
# Create the user account.
|
|
creds = self.get_cached_creds(
|
|
account_type=self.AccountType.USER,
|
|
opts=account_options)
|
|
|
|
if add_to_silo:
|
|
# Add the account to the silo.
|
|
self.add_to_group(str(creds.get_dn()),
|
|
silo_dn,
|
|
'msDS-AuthNPolicySiloMembers',
|
|
expect_attr=False)
|
|
|
|
claim_id = self.create_auth_silo_claim_id()
|
|
|
|
if expect_claim:
|
|
expected_claims = {
|
|
claim_id: {
|
|
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
|
'type': claims.CLAIM_TYPE_STRING,
|
|
# Expect a claim containing the name of the silo.
|
|
'values': (silo_id,),
|
|
},
|
|
}
|
|
unexpected_claims = None
|
|
else:
|
|
expected_claims = None
|
|
unexpected_claims = {claim_id}
|
|
|
|
# Get a TGT and check whether the claim is present or missing.
|
|
self.get_tgt(creds,
|
|
expect_pac=True,
|
|
expect_client_claims=True,
|
|
expected_client_claims=expected_claims,
|
|
unexpected_client_claims=unexpected_claims)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
global_asn1_print = False
|
|
global_hexdump = False
|
|
import unittest
|
|
unittest.main()
|