]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
tests/krb5: Add constrained delegation tests
authorJoseph Sutton <josephsutton@catalyst.net.nz>
Thu, 30 Sep 2021 02:03:04 +0000 (15:03 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 14 Oct 2021 18:59:31 +0000 (18:59 +0000)
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14642

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/tests/krb5/rfc4120_constants.py
python/samba/tests/krb5/rodc_tests.py [new file with mode: 0755]
python/samba/tests/krb5/s4u_tests.py
python/samba/tests/usage.py
selftest/knownfail_heimdal_kdc
source4/selftest/tests.py

index 76f2b75d94e43a7cbc14f57f5fe082e54a67ae52..39bb2db8e329e2047b9e9381c151118175a36c2c 100644 (file)
@@ -69,12 +69,17 @@ PADATA_SUPPORTED_ETYPES = int(
 KDC_ERR_C_PRINCIPAL_UNKNOWN = 6
 KDC_ERR_S_PRINCIPAL_UNKNOWN = 7
 KDC_ERR_POLICY = 12
+KDC_ERR_BADOPTION = 13
 KDC_ERR_ETYPE_NOSUPP = 14
+KDC_ERR_SUMTYPE_NOSUPP = 15
 KDC_ERR_PREAUTH_FAILED = 24
 KDC_ERR_PREAUTH_REQUIRED = 25
+KDC_ERR_BAD_INTEGRITY = 31
 KDC_ERR_NOT_US = 35
 KDC_ERR_BADMATCH = 36
 KDC_ERR_SKEW = 37
+KDC_ERR_MODIFIED = 41
+KDC_ERR_INAPP_CKSUM = 50
 KDC_ERR_GENERIC = 60
 KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTIONS = 93
 
diff --git a/python/samba/tests/krb5/rodc_tests.py b/python/samba/tests/krb5/rodc_tests.py
new file mode 100755 (executable)
index 0000000..4579f9e
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+# Unix SMB/CIFS implementation.
+# Copyright (C) Stefan Metzmacher 2020
+#
+# 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
+
+from samba.tests.krb5.kdc_base_test import KDCBaseTest
+
+sys.path.insert(0, "bin/python")
+os.environ["PYTHONUNBUFFERED"] = "1"
+
+global_asn1_print = False
+global_hexdump = False
+
+
+class RodcKerberosTests(KDCBaseTest):
+
+    def setUp(self):
+        super().setUp()
+        self.do_asn1_print = global_asn1_print
+        self.do_hexdump = global_hexdump
+
+    # Ensure that an RODC correctly issues tickets signed with its krbtgt key
+    # and including the RODCIdentifier.
+    def test_rodc_ticket_signature(self):
+        user_creds = self.get_cached_creds(
+            machine_account=False,
+            opts={
+                'revealed_to_rodc': True
+            })
+        target_creds = self.get_cached_creds(
+            machine_account=True,
+            opts={
+                'revealed_to_rodc': True
+            })
+
+        krbtgt_creds = self.get_rodc_krbtgt_creds()
+        rodc_key = self.TicketDecryptionKey_from_creds(krbtgt_creds)
+
+        # Get a TGT from the RODC.
+        tgt = self.get_tgt(user_creds, to_rodc=True)
+
+        # Ensure the PAC contains the expected checksums.
+        self.verify_ticket(tgt, rodc_key)
+
+        # Get a service ticket from the RODC.
+        service_ticket = self.get_service_ticket(tgt, target_creds,
+                                                 to_rodc=True)
+
+        # Ensure the PAC contains the expected checksums.
+        self.verify_ticket(service_ticket, rodc_key)
+
+
+if __name__ == "__main__":
+    global_asn1_print = False
+    global_hexdump = False
+    import unittest
+    unittest.main()
index bbddef4d6e0e80661c3e11e809d2d134ee6ee3df..9a25256081a13457396e283b4599fc3c4dc7d267 100755 (executable)
 
 import sys
 import os
+import functools
 
 sys.path.insert(0, "bin/python")
 os.environ["PYTHONUNBUFFERED"] = "1"
 
+from samba import ntstatus
+from samba.dcerpc import krb5pac, lsa
+
 from samba.tests import env_get_var_value
-from samba.tests.krb5.kcrypto import Cksumtype
-from samba.tests.krb5.raw_testcase import RawKerberosTest
+from samba.tests.krb5.kcrypto import Cksumtype, Enctype
+from samba.tests.krb5.kdc_base_test import KDCBaseTest
+from samba.tests.krb5.raw_testcase import (
+    RodcPacEncryptionKey,
+    ZeroedChecksumKey
+)
 from samba.tests.krb5.rfc4120_constants import (
+    AES256_CTS_HMAC_SHA1_96,
+    ARCFOUR_HMAC_MD5,
+    KDC_ERR_BADOPTION,
+    KDC_ERR_BAD_INTEGRITY,
+    KDC_ERR_GENERIC,
+    KDC_ERR_INAPP_CKSUM,
+    KDC_ERR_MODIFIED,
+    KDC_ERR_SUMTYPE_NOSUPP,
     KU_PA_ENC_TIMESTAMP,
     KU_AS_REP_ENC_PART,
     KU_TGS_REP_ENC_PART_SUB_KEY,
+    NT_PRINCIPAL
 )
 import samba.tests.krb5.rfc4120_pyasn1 as krb5_asn1
 
@@ -36,7 +53,7 @@ global_asn1_print = False
 global_hexdump = False
 
 
-class S4UKerberosTests(RawKerberosTest):
+class S4UKerberosTests(KDCBaseTest):
 
     def setUp(self):
         super(S4UKerberosTests, self).setUp()
@@ -119,8 +136,14 @@ class S4UKerberosTests(RawKerberosTest):
         self.assertEqual(msg_type, 11)
 
         enc_part2 = key.decrypt(KU_AS_REP_ENC_PART, rep['enc-part']['cipher'])
-        enc_part2 = self.der_decode(
-            enc_part2, asn1Spec=krb5_asn1.EncASRepPart())
+        # MIT KDC encodes both EncASRepPart and EncTGSRepPart with
+        # application tag 26
+        try:
+            enc_part2 = self.der_decode(
+                enc_part2, asn1Spec=krb5_asn1.EncASRepPart())
+        except Exception:
+            enc_part2 = self.der_decode(
+                enc_part2, asn1Spec=krb5_asn1.EncTGSRepPart())
 
         # S4U2Self Request
         sname = cname
@@ -195,6 +218,931 @@ class S4UKerberosTests(RawKerberosTest):
         msg_type = self._test_s4u2self(pa_s4u2self_ctype=Cksumtype.CRC32)
         self.assertEqual(msg_type, 30)
 
+    def _run_s4u2self_test(self, kdc_dict):
+        client_opts = kdc_dict.pop('client_opts', None)
+        client_creds = self.get_cached_creds(machine_account=False,
+                                             opts=client_opts)
+
+        service_opts = kdc_dict.pop('service_opts', None)
+        service_creds = self.get_cached_creds(machine_account=True,
+                                              opts=service_opts)
+
+        service_tgt = self.get_tgt(service_creds)
+        modify_service_tgt_fn = kdc_dict.pop('modify_service_tgt_fn', None)
+        if modify_service_tgt_fn is not None:
+            service_tgt = modify_service_tgt_fn(service_tgt)
+
+        client_name = client_creds.get_username()
+        client_cname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
+                                                 names=[client_name])
+
+        service_name = service_creds.get_username()[:-1]
+        service_sname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
+                                                  names=['host', service_name])
+
+        realm = client_creds.get_realm()
+
+        expected_flags = kdc_dict.pop('expected_flags', None)
+        if expected_flags is not None:
+            expected_flags = krb5_asn1.TicketFlags(expected_flags)
+
+        unexpected_flags = kdc_dict.pop('unexpected_flags', None)
+        if unexpected_flags is not None:
+            unexpected_flags = krb5_asn1.TicketFlags(unexpected_flags)
+
+        kdc_options = kdc_dict.pop('kdc_options', '0')
+        kdc_options = krb5_asn1.KDCOptions(kdc_options)
+
+        service_decryption_key = self.TicketDecryptionKey_from_creds(
+            service_creds)
+
+        authenticator_subkey = self.RandomKey(Enctype.AES256)
+
+        etypes = kdc_dict.pop('etypes', (AES256_CTS_HMAC_SHA1_96,
+                                         ARCFOUR_HMAC_MD5))
+
+        def generate_s4u2self_padata(_kdc_exchange_dict,
+                                     _callback_dict,
+                                     req_body):
+            pa_s4u = self.PA_S4U2Self_create(
+                name=client_cname,
+                realm=realm,
+                tgt_session_key=service_tgt.session_key,
+                ctype=None)
+
+            return [pa_s4u], req_body
+
+        kdc_exchange_dict = self.tgs_exchange_dict(
+            expected_crealm=realm,
+            expected_cname=client_cname,
+            expected_srealm=realm,
+            expected_sname=service_sname,
+            expected_flags=expected_flags,
+            unexpected_flags=unexpected_flags,
+            ticket_decryption_key=service_decryption_key,
+            expect_ticket_checksum=True,
+            generate_padata_fn=generate_s4u2self_padata,
+            check_rep_fn=self.generic_check_kdc_rep,
+            check_kdc_private_fn=self.generic_check_kdc_private,
+            expected_error_mode=0,
+            tgt=service_tgt,
+            authenticator_subkey=authenticator_subkey,
+            kdc_options=str(kdc_options),
+            expect_claims=False)
+
+        self._generic_kdc_exchange(kdc_exchange_dict,
+                                   cname=None,
+                                   realm=realm,
+                                   sname=service_sname,
+                                   etypes=etypes)
+
+        # Ensure we used all the parameters given to us.
+        self.assertEqual({}, kdc_dict)
+
+    # Test performing an S4U2Self operation with a forwardable ticket. The
+    # resulting ticket should have the 'forwardable' flag set.
+    def test_s4u2self_forwardable(self):
+        self._run_s4u2self_test(
+            {
+                'client_opts': {
+                    'not_delegated': False
+                },
+                'kdc_options': 'forwardable',
+                'modify_service_tgt_fn': functools.partial(
+                    self.set_ticket_forwardable, flag=True),
+                'expected_flags': 'forwardable'
+            })
+
+    # Test performing an S4U2Self operation without requesting a forwardable
+    # ticket. The resulting ticket should not have the 'forwardable' flag set.
+    def test_s4u2self_without_forwardable(self):
+        self._run_s4u2self_test(
+            {
+                'client_opts': {
+                    'not_delegated': False
+                },
+                'modify_service_tgt_fn': functools.partial(
+                    self.set_ticket_forwardable, flag=True),
+                'unexpected_flags': 'forwardable'
+            })
+
+    # Do an S4U2Self with a non-forwardable TGT. The 'forwardable' flag should
+    # not be set on the ticket.
+    def test_s4u2self_not_forwardable(self):
+        self._run_s4u2self_test(
+            {
+                'client_opts': {
+                    'not_delegated': False
+                },
+                'kdc_options': 'forwardable',
+                'modify_service_tgt_fn': functools.partial(
+                    self.set_ticket_forwardable, flag=False),
+                'unexpected_flags': 'forwardable'
+            })
+
+    # Do an S4U2Self with the not_delegated flag set on the client. The
+    # 'forwardable' flag should not be set on the ticket.
+    def test_s4u2self_client_not_delegated(self):
+        self._run_s4u2self_test(
+            {
+                'client_opts': {
+                    'not_delegated': True
+                },
+                'kdc_options': 'forwardable',
+                'modify_service_tgt_fn': functools.partial(
+                    self.set_ticket_forwardable, flag=True),
+                'unexpected_flags': 'forwardable'
+            })
+
+    # Do an S4U2Self with a service not trusted to authenticate for delegation,
+    # but having an empty msDS-AllowedToDelegateTo attribute. The 'forwardable'
+    # flag should be set on the ticket.
+    def test_s4u2self_not_trusted_empty_allowed(self):
+        self._run_s4u2self_test(
+            {
+                'client_opts': {
+                    'not_delegated': False
+                },
+                'service_opts': {
+                    'trusted_to_auth_for_delegation': False,
+                    'delegation_to_spn': ()
+                },
+                'kdc_options': 'forwardable',
+                'modify_service_tgt_fn': functools.partial(
+                    self.set_ticket_forwardable, flag=True),
+                'expected_flags': 'forwardable'
+            })
+
+    # Do an S4U2Self with a service not trusted to authenticate for delegation
+    # and having a non-empty msDS-AllowedToDelegateTo attribute. The
+    # 'forwardable' flag should not be set on the ticket.
+    def test_s4u2self_not_trusted_nonempty_allowed(self):
+        self._run_s4u2self_test(
+            {
+                'client_opts': {
+                    'not_delegated': False
+                },
+                'service_opts': {
+                    'trusted_to_auth_for_delegation': False,
+                    'delegation_to_spn': ('test',)
+                },
+                'kdc_options': 'forwardable',
+                'modify_service_tgt_fn': functools.partial(
+                    self.set_ticket_forwardable, flag=True),
+                'unexpected_flags': 'forwardable'
+            })
+
+    # Do an S4U2Self with a service trusted to authenticate for delegation and
+    # having an empty msDS-AllowedToDelegateTo attribute. The 'forwardable'
+    # flag should be set on the ticket.
+    def test_s4u2self_trusted_empty_allowed(self):
+        self._run_s4u2self_test(
+            {
+                'client_opts': {
+                    'not_delegated': False
+                },
+                'service_opts': {
+                    'trusted_to_auth_for_delegation': True,
+                    'delegation_to_spn': ()
+                },
+                'kdc_options': 'forwardable',
+                'modify_service_tgt_fn': functools.partial(
+                    self.set_ticket_forwardable, flag=True),
+                'expected_flags': 'forwardable'
+            })
+
+    # Do an S4U2Self with a service trusted to authenticate for delegation and
+    # having a non-empty msDS-AllowedToDelegateTo attribute. The 'forwardable'
+    # flag should be set on the ticket.
+    def test_s4u2self_trusted_nonempty_allowed(self):
+        self._run_s4u2self_test(
+            {
+                'client_opts': {
+                    'not_delegated': False
+                },
+                'service_opts': {
+                    'trusted_to_auth_for_delegation': True,
+                    'delegation_to_spn': ('test',)
+                },
+                'kdc_options': 'forwardable',
+                'modify_service_tgt_fn': functools.partial(
+                    self.set_ticket_forwardable, flag=True),
+                'expected_flags': 'forwardable'
+            })
+
+    def _run_delegation_test(self, kdc_dict):
+        client_opts = kdc_dict.pop('client_opts', None)
+        client_creds = self.get_cached_creds(machine_account=False,
+                                             opts=client_opts)
+
+        service1_opts = kdc_dict.pop('service1_opts', {})
+        service2_opts = kdc_dict.pop('service2_opts', {})
+
+        allow_delegation = kdc_dict.pop('allow_delegation', False)
+        allow_rbcd = kdc_dict.pop('allow_rbcd', False)
+        self.assertFalse(allow_delegation and allow_rbcd)
+
+        if allow_rbcd:
+            service1_creds = self.get_cached_creds(machine_account=True,
+                                                   opts=service1_opts)
+
+            self.assertNotIn('delegation_from_dn', service2_opts)
+            service2_opts['delegation_from_dn'] = str(service1_creds.get_dn())
+
+            service2_creds = self.get_cached_creds(machine_account=True,
+                                                   opts=service2_opts)
+        else:
+            service2_creds = self.get_cached_creds(machine_account=True,
+                                                   opts=service2_opts)
+
+            if allow_delegation:
+                self.assertNotIn('delegation_to_spn', service1_opts)
+                service1_opts['delegation_to_spn'] = service2_creds.get_spn()
+
+            service1_creds = self.get_cached_creds(machine_account=True,
+                                                   opts=service1_opts)
+
+        client_tkt_options = kdc_dict.pop('client_tkt_options', 'forwardable')
+        expected_flags = krb5_asn1.TicketFlags(client_tkt_options)
+
+        client_tgt = self.get_tgt(client_creds,
+                                  kdc_options=client_tkt_options,
+                                  expected_flags=expected_flags)
+        client_service_tkt = self.get_service_ticket(
+            client_tgt,
+            service1_creds,
+            kdc_options=client_tkt_options,
+            expected_flags=expected_flags)
+
+        service1_tgt = self.get_tgt(service1_creds)
+
+        modify_client_tkt_fn = kdc_dict.pop('modify_client_tkt_fn', None)
+        if modify_client_tkt_fn is not None:
+            client_service_tkt = modify_client_tkt_fn(client_service_tkt)
+
+        additional_tickets = [client_service_tkt.ticket]
+
+        modify_service_tgt_fn = kdc_dict.pop('modify_service_tgt_fn', None)
+        if modify_service_tgt_fn is not None:
+            service1_tgt = modify_service_tgt_fn(service1_tgt)
+
+        kdc_options = kdc_dict.pop('kdc_options', None)
+        if kdc_options is None:
+            kdc_options = str(krb5_asn1.KDCOptions('cname-in-addl-tkt'))
+
+        client_username = client_creds.get_username()
+        client_realm = client_creds.get_realm()
+        client_cname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
+                                                 names=[client_username])
+
+        service1_name = service1_creds.get_username()[:-1]
+        service1_realm = service1_creds.get_realm()
+
+        service2_name = service2_creds.get_username()[:-1]
+        service2_realm = service2_creds.get_realm()
+        service2_service = 'host'
+        service2_sname = self.PrincipalName_create(
+            name_type=NT_PRINCIPAL, names=[service2_service,
+                                           service2_name])
+        service2_decryption_key = self.TicketDecryptionKey_from_creds(
+            service2_creds)
+        service2_etypes = service2_creds.tgs_supported_enctypes
+
+        expected_error_mode = kdc_dict.pop('expected_error_mode')
+        expected_status = kdc_dict.pop('expected_status', None)
+        if expected_error_mode:
+            check_error_fn = self.generic_check_kdc_error
+            check_rep_fn = None
+        else:
+            check_error_fn = None
+            check_rep_fn = self.generic_check_kdc_rep
+
+            self.assertIsNone(expected_status)
+
+        expect_edata = kdc_dict.pop('expect_edata', None)
+        if expect_edata is not None:
+            self.assertTrue(expected_error_mode)
+
+        pac_options = kdc_dict.pop('pac_options', None)
+
+        authenticator_subkey = self.RandomKey(Enctype.AES256)
+
+        etypes = kdc_dict.pop('etypes', (AES256_CTS_HMAC_SHA1_96,
+                                         ARCFOUR_HMAC_MD5))
+
+        expected_proxy_target = service2_creds.get_spn()
+
+        expected_transited_services = kdc_dict.pop(
+            'expected_transited_services', [])
+
+        transited_service = f'host/{service1_name}@{service1_realm}'
+        expected_transited_services.append(transited_service)
+
+        kdc_exchange_dict = self.tgs_exchange_dict(
+            expected_crealm=client_realm,
+            expected_cname=client_cname,
+            expected_srealm=service2_realm,
+            expected_sname=service2_sname,
+            expected_supported_etypes=service2_etypes,
+            ticket_decryption_key=service2_decryption_key,
+            check_error_fn=check_error_fn,
+            check_rep_fn=check_rep_fn,
+            check_kdc_private_fn=self.generic_check_kdc_private,
+            expected_error_mode=expected_error_mode,
+            expected_status=expected_status,
+            callback_dict={},
+            tgt=service1_tgt,
+            authenticator_subkey=authenticator_subkey,
+            kdc_options=kdc_options,
+            pac_options=pac_options,
+            expect_edata=expect_edata,
+            expected_proxy_target=expected_proxy_target,
+            expected_transited_services=expected_transited_services)
+
+        self._generic_kdc_exchange(kdc_exchange_dict,
+                                   cname=None,
+                                   realm=service2_realm,
+                                   sname=service2_sname,
+                                   etypes=etypes,
+                                   additional_tickets=additional_tickets)
+
+        # Ensure we used all the parameters given to us.
+        self.assertEqual({}, kdc_dict)
+
+    def test_constrained_delegation(self):
+        # Test constrained delegation.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': 0,
+                'allow_delegation': True
+            })
+
+    def test_constrained_delegation_existing_delegation_info(self):
+        # Test constrained delegation with an existing S4U_DELEGATION_INFO
+        # structure in the PAC.
+
+        services = ['service1', 'service2', 'service3']
+
+        self._run_delegation_test(
+            {
+                'expected_error_mode': 0,
+                'allow_delegation': True,
+                'modify_client_tkt_fn': functools.partial(
+                    self.add_delegation_info, services=services),
+                'expected_transited_services': services
+            })
+
+    def test_constrained_delegation_not_allowed(self):
+        # Test constrained delegation when the delegating service does not
+        # allow it.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': KDC_ERR_BADOPTION,
+                'expected_status': ntstatus.NT_STATUS_NOT_SUPPORTED,
+                'allow_delegation': False
+            })
+
+    def test_constrained_delegation_no_client_pac(self):
+        # Test constrained delegation when the client service ticket does not
+        # contain a PAC.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': (KDC_ERR_BADOPTION,
+                                        KDC_ERR_MODIFIED),
+                'allow_delegation': True,
+                'modify_client_tkt_fn': self.remove_ticket_pac,
+                'expect_edata': False
+            })
+
+    def test_constrained_delegation_no_service_pac(self):
+        # Test constrained delegation when the service TGT does not contain a
+        # PAC.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': 0,
+                'allow_delegation': True,
+                'modify_service_tgt_fn': self.remove_ticket_pac
+            })
+
+    def test_constrained_delegation_non_forwardable(self):
+        # Test constrained delegation with a non-forwardable ticket.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': KDC_ERR_BADOPTION,
+                'expected_status': ntstatus.NT_STATUS_ACCOUNT_RESTRICTION,
+                'allow_delegation': True,
+                'modify_client_tkt_fn': functools.partial(
+                    self.set_ticket_forwardable, flag=False)
+            })
+
+    def test_constrained_delegation_pac_options_rbcd(self):
+        # Test constrained delegation, but with the RBCD bit set in the PAC
+        # options.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': 0,
+                'pac_options': '0001',  # supports RBCD
+                'allow_delegation': True
+            })
+
+    def test_rbcd_existing_delegation_info(self):
+        # Test constrained delegation with an existing S4U_DELEGATION_INFO
+        # structure in the PAC.
+
+        services = ['service1', 'service2', 'service3']
+
+        self._run_delegation_test(
+            {
+                'expected_error_mode': 0,
+                'allow_rbcd': True,
+                'pac_options': '0001',  # supports RBCD
+                'modify_client_tkt_fn': functools.partial(
+                    self.add_delegation_info, services=services),
+                'expected_transited_services': services
+            })
+
+    def test_rbcd_not_allowed(self):
+        # Test resource-based constrained delegation when the target service
+        # does not allow it.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': KDC_ERR_BADOPTION,
+                'expected_status': ntstatus.NT_STATUS_NOT_FOUND,
+                'allow_rbcd': False,
+                'pac_options': '0001'  # supports RBCD
+            })
+
+    def test_rbcd_no_client_pac_a(self):
+        # Test constrained delegation when the client service ticket does not
+        # contain a PAC, and an empty msDS-AllowedToDelegateTo attribute.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': KDC_ERR_MODIFIED,
+                'expected_status': ntstatus.NT_STATUS_NOT_SUPPORTED,
+                'allow_rbcd': True,
+                'pac_options': '0001',  # supports RBCD
+                'modify_client_tkt_fn': self.remove_ticket_pac
+            })
+
+    def test_rbcd_no_client_pac_b(self):
+        # Test constrained delegation when the client service ticket does not
+        # contain a PAC, and a non-empty msDS-AllowedToDelegateTo attribute.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': KDC_ERR_MODIFIED,
+                'expected_status': ntstatus.NT_STATUS_NO_MATCH,
+                'allow_rbcd': True,
+                'pac_options': '0001',  # supports RBCD
+                'modify_client_tkt_fn': self.remove_ticket_pac,
+                'service1_opts': {
+                    'delegation_to_spn': ('host/test')
+                }
+            })
+
+    def test_rbcd_no_service_pac(self):
+        # Test constrained delegation when the service TGT does not contain a
+        # PAC.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': KDC_ERR_BADOPTION,
+                'expected_status':
+                    ntstatus.NT_STATUS_NOT_FOUND,
+                'allow_rbcd': True,
+                'pac_options': '0001',  # supports RBCD
+                'modify_service_tgt_fn': self.remove_ticket_pac
+            })
+
+    def test_rbcd_non_forwardable(self):
+        # Test resource-based constrained delegation with a non-forwardable
+        # ticket.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': KDC_ERR_BADOPTION,
+                'expected_status': ntstatus.NT_STATUS_ACCOUNT_RESTRICTION,
+                'allow_rbcd': True,
+                'pac_options': '0001',  # supports RBCD
+                'modify_client_tkt_fn': functools.partial(
+                    self.set_ticket_forwardable, flag=False)
+            })
+
+    def test_rbcd_no_pac_options_a(self):
+        # Test resource-based constrained delegation without the RBCD bit set
+        # in the PAC options, and an empty msDS-AllowedToDelegateTo attribute.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': KDC_ERR_BADOPTION,
+                'expected_status': ntstatus.NT_STATUS_NOT_SUPPORTED,
+                'allow_rbcd': True,
+                'pac_options': '1'  # does not support RBCD
+            })
+
+    def test_rbcd_no_pac_options_b(self):
+        # Test resource-based constrained delegation without the RBCD bit set
+        # in the PAC options, and a non-empty msDS-AllowedToDelegateTo
+        # attribute.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': KDC_ERR_BADOPTION,
+                'expected_status': ntstatus.NT_STATUS_NO_MATCH,
+                'allow_rbcd': True,
+                'pac_options': '1',  # does not support RBCD
+                'service1_opts': {
+                    'delegation_to_spn': ('host/test')
+                }
+            })
+
+    def test_bronze_bit_constrained_delegation_old_checksum(self):
+        # Attempt to modify the ticket without updating the PAC checksums.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': (KDC_ERR_MODIFIED,
+                                        KDC_ERR_BAD_INTEGRITY),
+                'allow_delegation': True,
+                'client_tkt_options': '0',  # non-forwardable ticket
+                'modify_client_tkt_fn': functools.partial(
+                    self.set_ticket_forwardable,
+                    flag=True, update_pac_checksums=False),
+                'expect_edata': False
+            })
+
+    def test_bronze_bit_rbcd_old_checksum(self):
+        # Attempt to modify the ticket without updating the PAC checksums.
+        self._run_delegation_test(
+            {
+                'expected_error_mode': KDC_ERR_MODIFIED,
+                'expected_status': ntstatus.NT_STATUS_NOT_SUPPORTED,
+                'allow_rbcd': True,
+                'pac_options': '0001',  # supports RBCD
+                'client_tkt_options': '0',  # non-forwardable ticket
+                'modify_client_tkt_fn': functools.partial(
+                    self.set_ticket_forwardable,
+                    flag=True, update_pac_checksums=False)
+            })
+
+    def test_constrained_delegation_missing_client_checksum(self):
+        # Present a user ticket without the required checksums.
+        for checksum in self.pac_checksum_types:
+            with self.subTest(checksum=checksum):
+                if checksum == krb5pac.PAC_TYPE_TICKET_CHECKSUM:
+                    expected_error_mode = (KDC_ERR_BADOPTION,
+                                           KDC_ERR_MODIFIED)
+                else:
+                    expected_error_mode = KDC_ERR_GENERIC
+
+                self._run_delegation_test(
+                    {
+                        'expected_error_mode': expected_error_mode,
+                        'allow_delegation': True,
+                        'modify_client_tkt_fn': functools.partial(
+                            self.remove_pac_checksum, checksum=checksum),
+                        'expect_edata': False
+                    })
+
+    def test_constrained_delegation_missing_service_checksum(self):
+        # Present the service's ticket without the required checksums.
+        for checksum in filter(lambda x: x != krb5pac.PAC_TYPE_TICKET_CHECKSUM,
+                               self.pac_checksum_types):
+            with self.subTest(checksum=checksum):
+                self._run_delegation_test(
+                    {
+                        'expected_error_mode': KDC_ERR_GENERIC,
+                        'expected_status':
+                            ntstatus.NT_STATUS_INSUFFICIENT_RESOURCES,
+                        'allow_delegation': True,
+                        'modify_service_tgt_fn': functools.partial(
+                            self.remove_pac_checksum, checksum=checksum)
+                    })
+
+    def test_rbcd_missing_client_checksum(self):
+        # Present a user ticket without the required checksums.
+        for checksum in self.pac_checksum_types:
+            with self.subTest(checksum=checksum):
+                if checksum == krb5pac.PAC_TYPE_TICKET_CHECKSUM:
+                    expected_error_mode = KDC_ERR_MODIFIED
+                else:
+                    expected_error_mode = KDC_ERR_GENERIC
+
+                self._run_delegation_test(
+                    {
+                        'expected_error_mode': expected_error_mode,
+                        'expected_status':
+                            ntstatus.NT_STATUS_NOT_SUPPORTED,
+                        'allow_rbcd': True,
+                        'pac_options': '0001',  # supports RBCD
+                        'modify_client_tkt_fn': functools.partial(
+                            self.remove_pac_checksum, checksum=checksum)
+                    })
+
+    def test_rbcd_missing_service_checksum(self):
+        # Present the service's ticket without the required checksums.
+        for checksum in filter(lambda x: x != krb5pac.PAC_TYPE_TICKET_CHECKSUM,
+                               self.pac_checksum_types):
+            with self.subTest(checksum=checksum):
+                self._run_delegation_test(
+                    {
+                        'expected_error_mode': KDC_ERR_GENERIC,
+                        'expected_status':
+                            ntstatus.NT_STATUS_INSUFFICIENT_RESOURCES,
+                        'allow_rbcd': True,
+                        'pac_options': '0001',  # supports RBCD
+                        'modify_service_tgt_fn': functools.partial(
+                            self.remove_pac_checksum, checksum=checksum)
+                    })
+
+    def test_constrained_delegation_zeroed_client_checksum(self):
+        # Present a user ticket with invalid checksums.
+        for checksum in self.pac_checksum_types:
+            with self.subTest(checksum=checksum):
+                self._run_delegation_test(
+                    {
+                        'expected_error_mode': (KDC_ERR_MODIFIED,
+                                                KDC_ERR_BAD_INTEGRITY),
+                        'allow_delegation': True,
+                        'modify_client_tkt_fn': functools.partial(
+                            self.zeroed_pac_checksum, checksum=checksum),
+                        'expect_edata': False
+                    })
+
+    def test_constrained_delegation_zeroed_service_checksum(self):
+        # Present the service's ticket with invalid checksums.
+        for checksum in self.pac_checksum_types:
+            with self.subTest(checksum=checksum):
+                if checksum == krb5pac.PAC_TYPE_SRV_CHECKSUM:
+                    expected_error_mode = (KDC_ERR_MODIFIED,
+                                           KDC_ERR_BAD_INTEGRITY)
+                    expected_status = ntstatus.NT_STATUS_WRONG_PASSWORD
+                else:
+                    expected_error_mode = 0
+                    expected_status = None
+
+                self._run_delegation_test(
+                    {
+                        'expected_error_mode': expected_error_mode,
+                        'expected_status': expected_status,
+                        'allow_delegation': True,
+                        'modify_service_tgt_fn': functools.partial(
+                            self.zeroed_pac_checksum, checksum=checksum)
+                    })
+
+    def test_rbcd_zeroed_client_checksum(self):
+        # Present a user ticket with invalid checksums.
+        for checksum in self.pac_checksum_types:
+            with self.subTest(checksum=checksum):
+                self._run_delegation_test(
+                    {
+                        'expected_error_mode': KDC_ERR_MODIFIED,
+                        'expected_status':
+                            ntstatus.NT_STATUS_NOT_SUPPORTED,
+                        'allow_rbcd': True,
+                        'pac_options': '0001',  # supports RBCD
+                        'modify_client_tkt_fn': functools.partial(
+                            self.zeroed_pac_checksum, checksum=checksum)
+                    })
+
+    def test_rbcd_zeroed_service_checksum(self):
+        # Present the service's ticket with invalid checksums.
+        for checksum in self.pac_checksum_types:
+            with self.subTest(checksum=checksum):
+                if checksum == krb5pac.PAC_TYPE_SRV_CHECKSUM:
+                    expected_error_mode = (KDC_ERR_MODIFIED,
+                                           KDC_ERR_BAD_INTEGRITY)
+                    expected_status = ntstatus.NT_STATUS_WRONG_PASSWORD
+                else:
+                    expected_error_mode = 0
+                    expected_status = None
+
+                self._run_delegation_test(
+                    {
+                        'expected_error_mode': expected_error_mode,
+                        'expected_status': expected_status,
+                        'allow_rbcd': True,
+                        'pac_options': '0001',  # supports RBCD
+                        'modify_service_tgt_fn': functools.partial(
+                            self.zeroed_pac_checksum, checksum=checksum)
+                    })
+
+    unkeyed_ctypes = {Cksumtype.MD5, Cksumtype.SHA1, Cksumtype.CRC32}
+
+    def test_constrained_delegation_unkeyed_client_checksum(self):
+        # Present a user ticket with invalid checksums.
+        for checksum in self.pac_checksum_types:
+            for ctype in self.unkeyed_ctypes:
+                with self.subTest(checksum=checksum, ctype=ctype):
+                    if (checksum == krb5pac.PAC_TYPE_SRV_CHECKSUM
+                            and ctype == Cksumtype.SHA1):
+                        expected_error_mode = (KDC_ERR_SUMTYPE_NOSUPP,
+                                               KDC_ERR_INAPP_CKSUM)
+                    else:
+                        expected_error_mode = (KDC_ERR_GENERIC,
+                                               KDC_ERR_INAPP_CKSUM)
+
+                    self._run_delegation_test(
+                        {
+                            'expected_error_mode': expected_error_mode,
+                            'allow_delegation': True,
+                            'modify_client_tkt_fn': functools.partial(
+                                self.unkeyed_pac_checksum,
+                                checksum=checksum, ctype=ctype),
+                            'expect_edata': False
+                        })
+
+    def test_constrained_delegation_unkeyed_service_checksum(self):
+        # Present the service's ticket with invalid checksums.
+        for checksum in self.pac_checksum_types:
+            for ctype in self.unkeyed_ctypes:
+                with self.subTest(checksum=checksum, ctype=ctype):
+                    if checksum == krb5pac.PAC_TYPE_SRV_CHECKSUM:
+                        if ctype == Cksumtype.SHA1:
+                            expected_error_mode = (KDC_ERR_SUMTYPE_NOSUPP,
+                                                   KDC_ERR_INAPP_CKSUM)
+                            expected_status = ntstatus.NT_STATUS_LOGON_FAILURE
+                        else:
+                            expected_error_mode = (KDC_ERR_GENERIC,
+                                                   KDC_ERR_INAPP_CKSUM)
+                            expected_status = (
+                                ntstatus.NT_STATUS_INSUFFICIENT_RESOURCES)
+                    else:
+                        expected_error_mode = 0
+                        expected_status = None
+
+                    self._run_delegation_test(
+                        {
+                            'expected_error_mode': expected_error_mode,
+                            'expected_status': expected_status,
+                            'allow_delegation': True,
+                            'modify_service_tgt_fn': functools.partial(
+                                self.unkeyed_pac_checksum,
+                                checksum=checksum, ctype=ctype)
+                        })
+
+    def test_rbcd_unkeyed_client_checksum(self):
+        # Present a user ticket with invalid checksums.
+        for checksum in self.pac_checksum_types:
+            for ctype in self.unkeyed_ctypes:
+                with self.subTest(checksum=checksum, ctype=ctype):
+                    if (checksum == krb5pac.PAC_TYPE_SRV_CHECKSUM
+                            and ctype == Cksumtype.SHA1):
+                        expected_error_mode = KDC_ERR_SUMTYPE_NOSUPP
+                    else:
+                        expected_error_mode = KDC_ERR_GENERIC
+
+                    self._run_delegation_test(
+                        {
+                            'expected_error_mode': expected_error_mode,
+                            'expected_status':
+                                ntstatus.NT_STATUS_NOT_SUPPORTED,
+                            'allow_rbcd': True,
+                            'pac_options': '0001',  # supports RBCD
+                            'modify_client_tkt_fn': functools.partial(
+                                self.unkeyed_pac_checksum,
+                                checksum=checksum, ctype=ctype)
+                        })
+
+    def test_rbcd_unkeyed_service_checksum(self):
+        # Present the service's ticket with invalid checksums.
+        for checksum in self.pac_checksum_types:
+            for ctype in self.unkeyed_ctypes:
+                with self.subTest(checksum=checksum, ctype=ctype):
+                    if checksum == krb5pac.PAC_TYPE_SRV_CHECKSUM:
+                        if ctype == Cksumtype.SHA1:
+                            expected_error_mode = (KDC_ERR_SUMTYPE_NOSUPP,
+                                                   KDC_ERR_BAD_INTEGRITY)
+                            expected_status = ntstatus.NT_STATUS_LOGON_FAILURE
+                        else:
+                            expected_error_mode = KDC_ERR_GENERIC
+                            expected_status = (
+                                ntstatus.NT_STATUS_INSUFFICIENT_RESOURCES)
+                    else:
+                        expected_error_mode = 0
+                        expected_status = None
+
+                    self._run_delegation_test(
+                        {
+                            'expected_error_mode': expected_error_mode,
+                            'expected_status': expected_status,
+                            'allow_rbcd': True,
+                            'pac_options': '0001',  # supports RBCD
+                            'modify_service_tgt_fn': functools.partial(
+                                self.unkeyed_pac_checksum,
+                                checksum=checksum, ctype=ctype)
+                        })
+
+    def remove_pac_checksum(self, ticket, checksum):
+        checksum_keys = self.get_krbtgt_checksum_key()
+
+        return self.modified_ticket(ticket,
+                                    checksum_keys=checksum_keys,
+                                    include_checksums={checksum: False})
+
+    def zeroed_pac_checksum(self, ticket, checksum):
+        krbtgt_creds = self.get_krbtgt_creds()
+        krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds)
+
+        server_key = ticket.decryption_key
+
+        checksum_keys = {
+            krb5pac.PAC_TYPE_SRV_CHECKSUM: server_key,
+            krb5pac.PAC_TYPE_KDC_CHECKSUM: krbtgt_key,
+            krb5pac.PAC_TYPE_TICKET_CHECKSUM: krbtgt_key,
+        }
+
+        if checksum == krb5pac.PAC_TYPE_SRV_CHECKSUM:
+            zeroed_key = server_key
+        else:
+            zeroed_key = krbtgt_key
+
+        checksum_keys[checksum] = ZeroedChecksumKey(zeroed_key.key,
+                                                    zeroed_key.kvno)
+
+        return self.modified_ticket(ticket,
+                                    checksum_keys=checksum_keys,
+                                    include_checksums={checksum: True})
+
+    def unkeyed_pac_checksum(self, ticket, checksum, ctype):
+        krbtgt_creds = self.get_krbtgt_creds()
+        krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds)
+
+        server_key = ticket.decryption_key
+
+        checksum_keys = {
+            krb5pac.PAC_TYPE_SRV_CHECKSUM: server_key,
+            krb5pac.PAC_TYPE_KDC_CHECKSUM: krbtgt_key,
+            krb5pac.PAC_TYPE_TICKET_CHECKSUM: krbtgt_key,
+        }
+
+        # Make a copy of the existing key and change the ctype.
+        key = checksum_keys[checksum]
+        new_key = RodcPacEncryptionKey(key.key, key.kvno)
+        new_key.ctype = ctype
+        checksum_keys[checksum] = new_key
+
+        return self.modified_ticket(ticket,
+                                    checksum_keys=checksum_keys,
+                                    include_checksums={checksum: True})
+
+    def add_delegation_info(self, ticket, services=None):
+        def modify_pac_fn(pac):
+            pac_buffers = pac.buffers
+            self.assertNotIn(krb5pac.PAC_TYPE_CONSTRAINED_DELEGATION,
+                             (buffer.type for buffer in pac_buffers))
+
+            transited_services = list(map(lsa.String, services))
+
+            delegation = krb5pac.PAC_CONSTRAINED_DELEGATION()
+            delegation.proxy_target = lsa.String('test_proxy_target')
+            delegation.transited_services = transited_services
+            delegation.num_transited_services = len(transited_services)
+
+            info = krb5pac.PAC_CONSTRAINED_DELEGATION_CTR()
+            info.info = delegation
+
+            pac_buffer = krb5pac.PAC_BUFFER()
+            pac_buffer.type = krb5pac.PAC_TYPE_CONSTRAINED_DELEGATION
+            pac_buffer.info = info
+
+            pac_buffers.append(pac_buffer)
+
+            pac.buffers = pac_buffers
+            pac.num_buffers += 1
+
+            return pac
+
+        checksum_keys = self.get_krbtgt_checksum_key()
+
+        return self.modified_ticket(ticket,
+                                    checksum_keys=checksum_keys,
+                                    modify_pac_fn=modify_pac_fn)
+
+    def set_ticket_forwardable(self, ticket, flag, update_pac_checksums=True):
+        flag = '1' if flag else '0'
+
+        def modify_fn(enc_part):
+            # Reset the forwardable flag
+            forwardable_pos = (len(tuple(krb5_asn1.TicketFlags('forwardable')))
+                               - 1)
+
+            flags = enc_part['flags']
+            self.assertLessEqual(forwardable_pos, len(flags))
+            enc_part['flags'] = (flags[:forwardable_pos] +
+                                 flag +
+                                 flags[forwardable_pos+1:])
+
+            return enc_part
+
+        if update_pac_checksums:
+            checksum_keys = self.get_krbtgt_checksum_key()
+        else:
+            checksum_keys = None
+
+        return self.modified_ticket(ticket,
+                                    modify_fn=modify_fn,
+                                    checksum_keys=checksum_keys,
+                                    update_pac_checksums=update_pac_checksums)
+
+    def remove_ticket_pac(self, ticket):
+        return self.modified_ticket(ticket,
+                                    exclude_pac=True)
+
 
 if __name__ == "__main__":
     global_asn1_print = False
index e97be071a5d012d5d8a5322abb6f4c17a1009a98..88bbff11053dac5e1798a6cebfb6096d235980a4 100644 (file)
@@ -103,6 +103,7 @@ EXCLUDE_USAGE = {
     'python/samba/tests/krb5/ms_kile_client_principal_lookup_tests.py',
     'python/samba/tests/krb5/as_req_tests.py',
     'python/samba/tests/krb5/fast_tests.py',
+    'python/samba/tests/krb5/rodc_tests.py',
 }
 
 EXCLUDE_HELP = {
index f9764a426dcd023dd6671772edbd5cdb6e6e2aa4..c10ea43e5559da79bc507cf1b7339ff627d98f96 100644 (file)
 # Heimdal currently does not generate ticket signatures
 #
 ^samba.tests.krb5.compatability_tests.samba.tests.krb5.compatability_tests.SimpleKerberosTests.test_heimdal_ticket_signature
+#
+# S4U tests
+#
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_bronze_bit_rbcd_old_checksum
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_missing_client_checksum
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_unkeyed_client_checksum
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_unkeyed_service_checksum
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_zeroed_client_checksum
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_zeroed_service_checksum
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_existing_delegation_info
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_missing_client_checksum
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_no_client_pac_a
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_no_client_pac_b
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_unkeyed_client_checksum
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_unkeyed_service_checksum
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_zeroed_client_checksum
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_zeroed_service_checksum
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_client_not_delegated
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_forwardable
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_not_forwardable
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_not_trusted_empty_allowed
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_not_trusted_nonempty_allowed
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_trusted_empty_allowed
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_trusted_nonempty_allowed
+^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_without_forwardable
+#
+# RODC tests
+#
+^samba.tests.krb5.rodc_tests.samba.tests.krb5.rodc_tests.RodcKerberosTests.test_rodc_ticket_signature
index dc51af89d8a566bc1b73c506251d9aca76e47b53..7d3572924c18e16e8b771f527722cceb0acfae9b 100755 (executable)
@@ -920,9 +920,16 @@ planoldpythontestsuite("ad_dc_default", "samba.tests.krb5.simple_tests",
                                 'FAST_SUPPORT': have_fast_support,
                                 'TKT_SIG_SUPPORT': tkt_sig_support})
 planoldpythontestsuite("ad_dc_default:local", "samba.tests.krb5.s4u_tests",
-                       environ={'SERVICE_USERNAME':'srv_account',
-                                'SERVICE_PASSWORD':'$PASSWORD',
+                       environ={'ADMIN_USERNAME':'$USERNAME',
+                                'ADMIN_PASSWORD':'$PASSWORD',
                                 'FOR_USER':'$USERNAME',
+                                'STRICT_CHECKING':'0',
+                                'FAST_SUPPORT': have_fast_support,
+                                'TKT_SIG_SUPPORT': tkt_sig_support})
+planoldpythontestsuite("rodc:local", "samba.tests.krb5.rodc_tests",
+                       environ={'ADMIN_USERNAME':'$USERNAME',
+                                'ADMIN_PASSWORD':'$PASSWORD',
+                                'STRICT_CHECKING':'0',
                                 'FAST_SUPPORT': have_fast_support,
                                 'TKT_SIG_SUPPORT': tkt_sig_support})