CVE-2022-37966 tests/krb5: Add a test requesting tickets with various encryption...
authorJoseph Sutton <josephsutton@catalyst.net.nz>
Tue, 25 Oct 2022 06:32:27 +0000 (19:32 +1300)
committerStefan Metzmacher <metze@samba.org>
Wed, 14 Dec 2022 10:28:16 +0000 (10:28 +0000)
The KDC should leave the choice of ticket encryption type up to the
target service, and admit no influence from the client.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=15237

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
(similar to commit 177334c04230d0ad74bfc2b6825ffbebd5afb9af)
[jsutton@samba.org Fixed conflicts in usage.py, knownfails, tests.py]

[jsutton@samba.org Fixed knownfail conflicts]

python/samba/tests/krb5/etype_tests.py [new file with mode: 0755]
python/samba/tests/krb5/kdc_tgs_tests.py
python/samba/tests/usage.py
selftest/knownfail_heimdal_kdc
selftest/knownfail_mit_kdc
source4/selftest/tests.py

diff --git a/python/samba/tests/krb5/etype_tests.py b/python/samba/tests/krb5/etype_tests.py
new file mode 100755 (executable)
index 0000000..37dab1e
--- /dev/null
@@ -0,0 +1,256 @@
+#!/usr/bin/env python3
+# Unix SMB/CIFS implementation.
+# Copyright (C) Stefan Metzmacher 2020
+# Copyright (C) 2022 Catalyst.Net Ltd
+#
+# 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.dcerpc import security
+
+from samba.tests.krb5.kdc_tgs_tests import KdcTgsBaseTests
+from samba.tests.krb5.rfc4120_constants import (
+    AES256_CTS_HMAC_SHA1_96,
+    ARCFOUR_HMAC_MD5,
+    KDC_ERR_ETYPE_NOSUPP,
+)
+
+sys.path.insert(0, "bin/python")
+os.environ["PYTHONUNBUFFERED"] = "1"
+
+global_asn1_print = False
+global_hexdump = False
+
+
+class EtypeTests(KdcTgsBaseTests):
+    def setUp(self):
+        super().setUp()
+        self.do_asn1_print = global_asn1_print
+        self.do_hexdump = global_hexdump
+
+    # Perform an AS-REQ for a service ticket, specifying AES. The request
+    # should fail with an error.
+    def test_as_aes_requested(self):
+        creds = self.get_mach_creds()
+        target_creds = self.get_service_creds()
+
+        self._as_req(creds, expected_error=KDC_ERR_ETYPE_NOSUPP,
+                     target_creds=target_creds,
+                     etype=(AES256_CTS_HMAC_SHA1_96,))
+
+    # Perform an AS-REQ for a service ticket, specifying RC4. The resulting
+    # ticket should be encrypted with RC4, with an RC4 session key.
+    def test_as_rc4_requested(self):
+        creds = self.get_mach_creds()
+        target_creds = self.get_service_creds()
+
+        ticket = self._as_req(creds, expected_error=0,
+                              target_creds=target_creds,
+                              etype=(ARCFOUR_HMAC_MD5,))
+
+        self.assertEqual(ARCFOUR_HMAC_MD5, ticket.decryption_key.etype)
+        self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
+
+    # Perform an AS-REQ for a service ticket, specifying AES, when the target
+    # service only supports AES. The resulting ticket should be encrypted with
+    # AES, with an AES session key.
+    def test_as_aes_supported_aes_requested(self):
+        creds = self.get_mach_creds()
+
+        target_creds = self.get_cached_creds(
+            account_type=self.AccountType.COMPUTER,
+            opts={
+                'supported_enctypes':
+                security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+            })
+
+        ticket = self._as_req(creds, expected_error=0,
+                              target_creds=target_creds,
+                              etype=(AES256_CTS_HMAC_SHA1_96,))
+
+        self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
+        self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.session_key.etype)
+
+    # Perform an AS-REQ for a service ticket, specifying RC4, when the target
+    # service only supports AES. The resulting ticket should be encrypted with
+    # AES, with an RC4 session key.
+    def test_as_aes_supported_rc4_requested(self):
+        creds = self.get_mach_creds()
+
+        target_creds = self.get_cached_creds(
+            account_type=self.AccountType.COMPUTER,
+            opts={
+                'supported_enctypes':
+                security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+            })
+
+        ticket = self._as_req(creds, expected_error=0,
+                              target_creds=target_creds,
+                              etype=(ARCFOUR_HMAC_MD5,))
+
+        self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
+        self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
+
+    # Perform an AS-REQ for a service ticket, specifying AES, when the target
+    # service only supports RC4. The request should fail with an error.
+    def test_as_rc4_supported_aes_requested(self):
+        creds = self.get_mach_creds()
+
+        target_creds = self.get_cached_creds(
+            account_type=self.AccountType.COMPUTER,
+            opts={
+                'supported_enctypes':
+                security.KERB_ENCTYPE_RC4_HMAC_MD5,
+            })
+
+        self._as_req(creds, expected_error=KDC_ERR_ETYPE_NOSUPP,
+                     target_creds=target_creds,
+                     etype=(AES256_CTS_HMAC_SHA1_96,))
+
+    # Perform an AS-REQ for a service ticket, specifying RC4, when the target
+    # service only supports RC4. The resulting ticket should be encrypted with
+    # RC4, with an RC4 session key.
+    def test_as_rc4_supported_rc4_requested(self):
+        creds = self.get_mach_creds()
+
+        target_creds = self.get_cached_creds(
+            account_type=self.AccountType.COMPUTER,
+            opts={
+                'supported_enctypes':
+                security.KERB_ENCTYPE_RC4_HMAC_MD5,
+            })
+
+        ticket = self._as_req(creds, expected_error=0,
+                              target_creds=target_creds,
+                              etype=(ARCFOUR_HMAC_MD5,))
+
+        self.assertEqual(ARCFOUR_HMAC_MD5, ticket.decryption_key.etype)
+        self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
+
+    # Perform a TGS-REQ for a service ticket, specifying AES. The request
+    # should fail with an error.
+    def test_tgs_aes_requested(self):
+        creds = self.get_mach_creds()
+        tgt = self.get_tgt(creds)
+
+        target_creds = self.get_mach_creds()
+
+        self._tgs_req(tgt, expected_error=KDC_ERR_ETYPE_NOSUPP,
+                      target_creds=target_creds,
+                      etypes=(AES256_CTS_HMAC_SHA1_96,))
+
+    # Perform a TGS-REQ for a service ticket, specifying RC4. The resulting
+    # ticket should be encrypted with RC4, with an RC4 session key.
+    def test_tgs_rc4_requested(self):
+        creds = self.get_mach_creds()
+        tgt = self.get_tgt(creds)
+
+        target_creds = self.get_mach_creds()
+
+        ticket = self._tgs_req(tgt, expected_error=0,
+                               target_creds=target_creds,
+                               etypes=(ARCFOUR_HMAC_MD5,))
+
+        self.assertEqual(ARCFOUR_HMAC_MD5, ticket.decryption_key.etype)
+        self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
+
+    # Perform a TGS-REQ for a service ticket, specifying AES, when the target
+    # service only supports AES. The resulting ticket should be encrypted with
+    # AES, with an AES session key.
+    def test_tgs_aes_supported_aes_requested(self):
+        creds = self.get_mach_creds()
+        tgt = self.get_tgt(creds)
+
+        target_creds = self.get_cached_creds(
+            account_type=self.AccountType.COMPUTER,
+            opts={
+                'supported_enctypes':
+                security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+            })
+
+        ticket = self._tgs_req(tgt, expected_error=0,
+                               target_creds=target_creds,
+                               etypes=(AES256_CTS_HMAC_SHA1_96,))
+
+        self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
+        self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.session_key.etype)
+
+    # Perform a TGS-REQ for a service ticket, specifying RC4, when the target
+    # service only supports AES. The resulting ticket should be encrypted with
+    # AES, with an RC4 session key.
+    def test_tgs_aes_supported_rc4_requested(self):
+        creds = self.get_mach_creds()
+        tgt = self.get_tgt(creds)
+
+        target_creds = self.get_cached_creds(
+            account_type=self.AccountType.COMPUTER,
+            opts={
+                'supported_enctypes':
+                security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+            })
+
+        ticket = self._tgs_req(tgt, expected_error=0,
+                               target_creds=target_creds,
+                               etypes=(ARCFOUR_HMAC_MD5,))
+
+        self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
+        self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
+
+    # Perform a TGS-REQ for a service ticket, specifying AES, when the target
+    # service only supports RC4. The request should fail with an error.
+    def test_tgs_rc4_supported_aes_requested(self):
+        creds = self.get_mach_creds()
+        tgt = self.get_tgt(creds)
+
+        target_creds = self.get_cached_creds(
+            account_type=self.AccountType.COMPUTER,
+            opts={
+                'supported_enctypes':
+                security.KERB_ENCTYPE_RC4_HMAC_MD5,
+            })
+
+        self._tgs_req(tgt, expected_error=KDC_ERR_ETYPE_NOSUPP,
+                      target_creds=target_creds,
+                      etypes=(AES256_CTS_HMAC_SHA1_96,))
+
+    # Perform a TGS-REQ for a service ticket, specifying RC4, when the target
+    # service only supports RC4. The resulting ticket should be encrypted with
+    # RC4, with an RC4 session key.
+    def test_tgs_rc4_supported_rc4_requested(self):
+        creds = self.get_mach_creds()
+        tgt = self.get_tgt(creds)
+
+        target_creds = self.get_cached_creds(
+            account_type=self.AccountType.COMPUTER,
+            opts={
+                'supported_enctypes':
+                security.KERB_ENCTYPE_RC4_HMAC_MD5,
+            })
+
+        ticket = self._tgs_req(tgt, expected_error=0,
+                               target_creds=target_creds,
+                               etypes=(ARCFOUR_HMAC_MD5,))
+
+        self.assertEqual(ARCFOUR_HMAC_MD5, ticket.decryption_key.etype)
+        self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
+
+
+if __name__ == "__main__":
+    global_asn1_print = False
+    global_hexdump = False
+    import unittest
+    unittest.main()
index 2b08325c6f96cadb8e703d3c0257761a765506be..6b1960806e0651f29b20fe3d440ed080d660d013 100755 (executable)
@@ -43,6 +43,7 @@ from samba.tests.krb5.rfc4120_constants import (
     KDC_ERR_MODIFIED,
     KDC_ERR_NOT_US,
     KDC_ERR_POLICY,
+    KDC_ERR_PREAUTH_REQUIRED,
     KDC_ERR_C_PRINCIPAL_UNKNOWN,
     KDC_ERR_S_PRINCIPAL_UNKNOWN,
     KDC_ERR_TGT_REVOKED,
@@ -59,6 +60,111 @@ global_hexdump = False
 
 
 class KdcTgsBaseTests(KDCBaseTest):
+    def _as_req(self,
+                creds,
+                expected_error,
+                target_creds,
+                etype):
+        user_name = creds.get_username()
+        cname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
+                                          names=user_name.split('/'))
+
+        target_name = target_creds.get_username()
+        sname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
+                                          names=['host', target_name[:-1]])
+
+        if expected_error:
+            expected_sname = sname
+        else:
+            expected_sname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
+                                                       names=[target_name])
+
+        realm = creds.get_realm()
+        salt = creds.get_salt()
+
+        till = self.get_KerberosTime(offset=36000)
+
+        ticket_decryption_key = (
+            self.TicketDecryptionKey_from_creds(target_creds))
+        expected_etypes = target_creds.tgs_supported_enctypes
+
+        kdc_options = ('forwardable,'
+                       'renewable,'
+                       'canonicalize,'
+                       'renewable-ok')
+        kdc_options = krb5_asn1.KDCOptions(kdc_options)
+
+        if expected_error:
+            initial_error = (KDC_ERR_PREAUTH_REQUIRED, expected_error)
+        else:
+            initial_error = KDC_ERR_PREAUTH_REQUIRED
+
+        rep, kdc_exchange_dict = self._test_as_exchange(
+            cname=cname,
+            realm=realm,
+            sname=sname,
+            till=till,
+            client_as_etypes=etype,
+            expected_error_mode=initial_error,
+            expected_crealm=realm,
+            expected_cname=cname,
+            expected_srealm=realm,
+            expected_sname=sname,
+            expected_salt=salt,
+            expected_supported_etypes=expected_etypes,
+            etypes=etype,
+            padata=None,
+            kdc_options=kdc_options,
+            preauth_key=None,
+            ticket_decryption_key=ticket_decryption_key)
+        self.assertIsNotNone(rep)
+        self.assertEqual(KRB_ERROR, rep['msg-type'])
+        error_code = rep['error-code']
+        if expected_error:
+            self.assertIn(error_code, initial_error)
+            if error_code == expected_error:
+                return
+        else:
+            self.assertEqual(initial_error, error_code)
+
+        etype_info2 = kdc_exchange_dict['preauth_etype_info2']
+
+        preauth_key = self.PasswordKey_from_etype_info2(creds,
+                                                        etype_info2[0],
+                                                        creds.get_kvno())
+
+        ts_enc_padata = self.get_enc_timestamp_pa_data_from_key(preauth_key)
+
+        padata = [ts_enc_padata]
+
+        expected_realm = realm.upper()
+
+        rep, kdc_exchange_dict = self._test_as_exchange(
+            cname=cname,
+            realm=realm,
+            sname=sname,
+            till=till,
+            client_as_etypes=etype,
+            expected_error_mode=expected_error,
+            expected_crealm=expected_realm,
+            expected_cname=cname,
+            expected_srealm=expected_realm,
+            expected_sname=expected_sname,
+            expected_salt=salt,
+            expected_supported_etypes=expected_etypes,
+            etypes=etype,
+            padata=padata,
+            kdc_options=kdc_options,
+            preauth_key=preauth_key,
+            ticket_decryption_key=ticket_decryption_key,
+            expect_edata=False)
+        if expected_error:
+            self.check_error_rep(rep, expected_error)
+            return None
+
+        self.check_as_reply(rep)
+        return kdc_exchange_dict['rep_ticket_creds']
+
     def _tgs_req(self, tgt, expected_error, target_creds,
                  armor_tgt=None,
                  kdc_options='0',
index 77c12bd01af4d50949a44aae84cc3d0a91da8d21..712f81f05c5545da5cedcb4107f39dc2b8c418d0 100644 (file)
@@ -112,6 +112,7 @@ EXCLUDE_USAGE = {
     'python/samba/tests/krb5/pac_align_tests.py',
     'python/samba/tests/krb5/kpasswd_tests.py',
     'python/samba/tests/krb5/lockout_tests.py',
+    'python/samba/tests/krb5/etype_tests.py',
 }
 
 EXCLUDE_HELP = {
index a5a995d92f04bb55e34654f789cacf14d083f22b..360b1960927f7ab62add9e22061691610bef4d4a 100644 (file)
@@ -47,3 +47,8 @@
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_revealed
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_not_revealed
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_revealed
+#
+# Encryption type tests
+#
+^samba.tests.krb5.etype_tests.samba.tests.krb5.etype_tests.EtypeTests.test_as_aes_requested.ad_dc
+^samba.tests.krb5.etype_tests.samba.tests.krb5.etype_tests.EtypeTests.test_as_rc4_supported_aes_requested.ad_dc
index a560941de1b99516790873e42ed595b0a66b22f3..269d1fe77030e05a1ad4d164d183a95c96657144 100644 (file)
@@ -560,3 +560,10 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_
 ^samba.tests.krb5.lockout_tests.samba.tests.krb5.lockout_tests.LockoutTests.test_lockout_transaction_kdc.ad_dc:local
 ^samba.tests.krb5.lockout_tests.samba.tests.krb5.lockout_tests.LockoutTests.test_lockout_transaction_rename_kdc.ad_dc:local
 ^samba.tests.krb5.lockout_tests.samba.tests.krb5.lockout_tests.LockoutTests.test_logon_kdc.ad_dc:local
+#
+# Encryption type tests
+#
+^samba.tests.krb5.etype_tests.samba.tests.krb5.etype_tests.EtypeTests.test_as_aes_supported_aes_requested.ad_dc
+^samba.tests.krb5.etype_tests.samba.tests.krb5.etype_tests.EtypeTests.test_as_aes_supported_rc4_requested.ad_dc
+^samba.tests.krb5.etype_tests.samba.tests.krb5.etype_tests.EtypeTests.test_as_rc4_requested.ad_dc
+^samba.tests.krb5.etype_tests.samba.tests.krb5.etype_tests.EtypeTests.test_as_rc4_supported_rc4_requested.ad_dc
index 3ee001e147da292cfac4f9d7454a4920ed5bef0c..1429fe7f34cc14595c8a00e81b02af5adba09d13 100755 (executable)
@@ -1684,6 +1684,10 @@ planoldpythontestsuite(
     'ad_dc:local',
     'samba.tests.krb5.lockout_tests',
     environ=krb5_environ)
+planoldpythontestsuite(
+    'ad_dc',
+    'samba.tests.krb5.etype_tests',
+    environ=krb5_environ)
 
 for env in [
         'vampire_dc',