summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorJoseph Sutton <josephsutton@catalyst.net.nz>2022-11-09 13:45:13 +1300
committerStefan Metzmacher <metze@samba.org>2022-12-14 11:39:17 +0000
commit25918f9c16c1e74d9fd5ea9fd1901f4eba157324 (patch)
treecb7b1c79aab84a116b5e17938d23f68e978225b9 /python
parent6ff9fc58cd3a4cea1cf2c565e0060427c6e9af77 (diff)
downloadsamba-25918f9c16c1e74d9fd5ea9fd1901f4eba157324.tar.gz
CVE-2022-37967 Add new PAC checksum
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15231 Pair-Programmed-With: Andrew Bartlett <abartlet@samba.org> Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> Signed-off-by: Andrew Bartlett <abartlet@samba.org> Reviewed-by: Stefan Metzmacher <metze@samba.org> (similar to commit a50a2be622afaa7a280312ea12f5eb9c9a0c41da) [jsutton@samba.org Fixed conflicts in krb5pac.idl and raw_testcase.py]
Diffstat (limited to 'python')
-rwxr-xr-xpython/samba/tests/krb5/compatability_tests.py22
-rw-r--r--python/samba/tests/krb5/kdc_base_test.py8
-rwxr-xr-xpython/samba/tests/krb5/kdc_tgs_tests.py59
-rw-r--r--python/samba/tests/krb5/raw_testcase.py78
-rwxr-xr-xpython/samba/tests/krb5/rodc_tests.py4
-rwxr-xr-xpython/samba/tests/krb5/s4u_tests.py61
6 files changed, 215 insertions, 17 deletions
diff --git a/python/samba/tests/krb5/compatability_tests.py b/python/samba/tests/krb5/compatability_tests.py
index b862f381bc5..72be7b34c4a 100755
--- a/python/samba/tests/krb5/compatability_tests.py
+++ b/python/samba/tests/krb5/compatability_tests.py
@@ -167,6 +167,28 @@ class SimpleKerberosTests(KDCBaseTest):
self.verify_ticket(service_ticket, key, service_ticket=True,
expect_ticket_checksum=False)
+ def test_full_signature(self):
+ # Ensure that a DC correctly issues tickets signed with its krbtgt key.
+ user_creds = self.get_client_creds()
+ target_creds = self.get_service_creds()
+
+ krbtgt_creds = self.get_krbtgt_creds()
+ key = self.TicketDecryptionKey_from_creds(krbtgt_creds)
+
+ # Get a TGT from the DC.
+ tgt = self.get_tgt(user_creds)
+
+ # Ensure the PAC contains the expected checksums.
+ self.verify_ticket(tgt, key, service_ticket=False)
+
+ # Get a service ticket from the DC.
+ service_ticket = self.get_service_ticket(tgt, target_creds)
+
+ # Ensure the PAC contains the expected checksums.
+ self.verify_ticket(service_ticket, key, service_ticket=True,
+ expect_ticket_checksum=True,
+ expect_full_checksum=True)
+
def as_pre_auth_req(self, creds, etypes):
user = creds.get_username()
realm = creds.get_realm()
diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py
index adf67fe7241..f5bfa68c433 100644
--- a/python/samba/tests/krb5/kdc_base_test.py
+++ b/python/samba/tests/krb5/kdc_base_test.py
@@ -1518,11 +1518,15 @@ class KDCBaseTest(RawKerberosTest):
krbtgt_creds = self.get_krbtgt_creds()
krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds)
+ is_tgs_princ = self.is_tgs_principal(sname)
expect_ticket_checksum = (self.tkt_sig_support
- and not self.is_tgs_principal(sname))
+ and not is_tgs_princ)
+ expect_full_checksum = (self.full_sig_support
+ and not is_tgs_princ)
self.verify_ticket(service_ticket_creds, krbtgt_key,
service_ticket=True, expect_pac=expect_pac,
- expect_ticket_checksum=expect_ticket_checksum)
+ expect_ticket_checksum=expect_ticket_checksum,
+ expect_full_checksum=expect_full_checksum)
self.tkt_cache[cache_key] = service_ticket_creds
diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py
index 3643644cf0a..f3779099596 100755
--- a/python/samba/tests/krb5/kdc_tgs_tests.py
+++ b/python/samba/tests/krb5/kdc_tgs_tests.py
@@ -1594,6 +1594,43 @@ class KdcTgsTests(KdcTgsBaseTests):
self._fast(tgt, creds, expected_error=KDC_ERR_TGT_REVOKED,
expected_sname=self.get_krbtgt_sname())
+ # Test making a TGS request with an RC4-encrypted TGT.
+ def test_tgs_rc4(self):
+ creds = self._get_creds()
+ tgt = self._get_tgt(creds, etype=kcrypto.Enctype.RC4)
+ self._run_tgs(tgt, expected_error=KDC_ERR_GENERIC)
+
+ def test_renew_rc4(self):
+ creds = self._get_creds()
+ tgt = self._get_tgt(creds, renewable=True, etype=kcrypto.Enctype.RC4)
+ self._renew_tgt(tgt, expected_error=KDC_ERR_GENERIC,
+ expect_pac_attrs=True,
+ expect_pac_attrs_pac_request=True,
+ expect_requester_sid=True)
+
+ def test_validate_rc4(self):
+ creds = self._get_creds()
+ tgt = self._get_tgt(creds, invalid=True, etype=kcrypto.Enctype.RC4)
+ self._validate_tgt(tgt, expected_error=KDC_ERR_GENERIC,
+ expect_pac_attrs=True,
+ expect_pac_attrs_pac_request=True,
+ expect_requester_sid=True)
+
+ def test_s4u2self_rc4(self):
+ creds = self._get_creds()
+ tgt = self._get_tgt(creds, etype=kcrypto.Enctype.RC4)
+ self._s4u2self(tgt, creds, expected_error=KDC_ERR_GENERIC)
+
+ def test_user2user_rc4(self):
+ creds = self._get_creds()
+ tgt = self._get_tgt(creds, etype=kcrypto.Enctype.RC4)
+ self._user2user(tgt, creds, expected_error=KDC_ERR_GENERIC)
+
+ def test_fast_rc4(self):
+ creds = self._get_creds()
+ tgt = self._get_tgt(creds, etype=kcrypto.Enctype.RC4)
+ self._fast(tgt, creds, expected_error=KDC_ERR_GENERIC)
+
# Test user-to-user with incorrect service principal names.
def test_user2user_matching_sname_host(self):
creds = self._get_creds()
@@ -2605,7 +2642,9 @@ class KdcTgsTests(KdcTgsBaseTests):
can_modify_logon_info=True,
can_modify_requester_sid=True,
remove_pac_attrs=False,
- remove_requester_sid=False):
+ remove_requester_sid=False,
+ etype=None,
+ cksum_etype=None):
self.assertFalse(renewable and invalid)
if remove_pac:
@@ -2624,7 +2663,9 @@ class KdcTgsTests(KdcTgsBaseTests):
can_modify_logon_info=can_modify_logon_info,
can_modify_requester_sid=can_modify_requester_sid,
remove_pac_attrs=remove_pac_attrs,
- remove_requester_sid=remove_requester_sid)
+ remove_requester_sid=remove_requester_sid,
+ etype=None,
+ cksum_etype=cksum_etype)
def _modify_tgt(self,
tgt,
@@ -2639,7 +2680,9 @@ class KdcTgsTests(KdcTgsBaseTests):
can_modify_logon_info=True,
can_modify_requester_sid=True,
remove_pac_attrs=False,
- remove_requester_sid=False):
+ remove_requester_sid=False,
+ etype=None,
+ cksum_etype=None):
if from_rodc:
krbtgt_creds = self.get_mock_rodc_krbtgt_creds()
else:
@@ -2678,13 +2721,19 @@ class KdcTgsTests(KdcTgsBaseTests):
else:
change_sid_fn = None
- krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds)
+ krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds,
+ etype)
if remove_pac:
checksum_keys = None
else:
+ if etype == cksum_etype:
+ cksum_key = krbtgt_key
+ else:
+ cksum_key = self.TicketDecryptionKey_from_creds(krbtgt_creds,
+ cksum_etype)
checksum_keys = {
- krb5pac.PAC_TYPE_KDC_CHECKSUM: krbtgt_key
+ krb5pac.PAC_TYPE_KDC_CHECKSUM: cksum_key
}
if renewable:
diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py
index 7b9444d7425..b6752fb2080 100644
--- a/python/samba/tests/krb5/raw_testcase.py
+++ b/python/samba/tests/krb5/raw_testcase.py
@@ -547,7 +547,8 @@ class RawKerberosTest(TestCaseInTempDir):
pac_checksum_types = {krb5pac.PAC_TYPE_SRV_CHECKSUM,
krb5pac.PAC_TYPE_KDC_CHECKSUM,
- krb5pac.PAC_TYPE_TICKET_CHECKSUM}
+ krb5pac.PAC_TYPE_TICKET_CHECKSUM,
+ krb5pac.PAC_TYPE_FULL_CHECKSUM}
etypes_to_test = (
{"value": -1111, "name": "dummy", },
@@ -641,6 +642,12 @@ class RawKerberosTest(TestCaseInTempDir):
tkt_sig_support = '0'
cls.tkt_sig_support = bool(int(tkt_sig_support))
+ full_sig_support = samba.tests.env_get_var_value('FULL_SIG_SUPPORT',
+ allow_missing=True)
+ if full_sig_support is None:
+ full_sig_support = '0'
+ cls.full_sig_support = bool(int(full_sig_support))
+
gnutls_pbkdf2_support = samba.tests.env_get_var_value(
'GNUTLS_PBKDF2_SUPPORT',
allow_missing=True)
@@ -2416,6 +2423,7 @@ class RawKerberosTest(TestCaseInTempDir):
unexpected_flags=None,
ticket_decryption_key=None,
expect_ticket_checksum=None,
+ expect_full_checksum=None,
generate_fast_fn=None,
generate_fast_armor_fn=None,
generate_fast_padata_fn=None,
@@ -2478,6 +2486,7 @@ class RawKerberosTest(TestCaseInTempDir):
'unexpected_flags': unexpected_flags,
'ticket_decryption_key': ticket_decryption_key,
'expect_ticket_checksum': expect_ticket_checksum,
+ 'expect_full_checksum': expect_full_checksum,
'generate_fast_fn': generate_fast_fn,
'generate_fast_armor_fn': generate_fast_armor_fn,
'generate_fast_padata_fn': generate_fast_padata_fn,
@@ -2536,6 +2545,7 @@ class RawKerberosTest(TestCaseInTempDir):
unexpected_flags=None,
ticket_decryption_key=None,
expect_ticket_checksum=None,
+ expect_full_checksum=None,
generate_fast_fn=None,
generate_fast_armor_fn=None,
generate_fast_padata_fn=None,
@@ -2599,6 +2609,7 @@ class RawKerberosTest(TestCaseInTempDir):
'unexpected_flags': unexpected_flags,
'ticket_decryption_key': ticket_decryption_key,
'expect_ticket_checksum': expect_ticket_checksum,
+ 'expect_full_checksum': expect_full_checksum,
'generate_fast_fn': generate_fast_fn,
'generate_fast_armor_fn': generate_fast_armor_fn,
'generate_fast_padata_fn': generate_fast_padata_fn,
@@ -3075,7 +3086,8 @@ class RawKerberosTest(TestCaseInTempDir):
self.check_pac_buffers(pac_data, kdc_exchange_dict)
expect_ticket_checksum = kdc_exchange_dict['expect_ticket_checksum']
- if expect_ticket_checksum:
+ expect_full_checksum = kdc_exchange_dict['expect_full_checksum']
+ if expect_ticket_checksum or expect_full_checksum:
self.assertIsNotNone(ticket_decryption_key)
if ticket_decryption_key is not None:
@@ -3085,7 +3097,9 @@ class RawKerberosTest(TestCaseInTempDir):
service_ticket=service_ticket,
expect_pac=expect_pac,
expect_ticket_checksum=expect_ticket_checksum
- or self.tkt_sig_support)
+ or self.tkt_sig_support,
+ expect_full_checksum=expect_full_checksum
+ or self.full_sig_support)
kdc_exchange_dict['rep_ticket_creds'] = ticket_creds
@@ -3123,12 +3137,15 @@ class RawKerberosTest(TestCaseInTempDir):
if rep_msg_type == KRB_TGS_REP:
if not self.is_tgs_principal(expected_sname):
expected_types.append(krb5pac.PAC_TYPE_TICKET_CHECKSUM)
+ expected_types.append(krb5pac.PAC_TYPE_FULL_CHECKSUM)
require_strict = {krb5pac.PAC_TYPE_CLIENT_CLAIMS_INFO,
krb5pac.PAC_TYPE_DEVICE_INFO,
krb5pac.PAC_TYPE_DEVICE_CLAIMS_INFO}
if not self.tkt_sig_support:
require_strict.add(krb5pac.PAC_TYPE_TICKET_CHECKSUM)
+ if not self.full_sig_support:
+ require_strict.add(krb5pac.PAC_TYPE_FULL_CHECKSUM)
expect_extra_pac_buffers = self.is_tgs(expected_sname)
@@ -3839,7 +3856,8 @@ class RawKerberosTest(TestCaseInTempDir):
def verify_ticket(self, ticket, krbtgt_keys, service_ticket,
expect_pac=True,
- expect_ticket_checksum=True):
+ expect_ticket_checksum=True,
+ expect_full_checksum=None):
# Decrypt the ticket.
key = ticket.decryption_key
@@ -3884,6 +3902,8 @@ class RawKerberosTest(TestCaseInTempDir):
checksums = {}
+ full_checksum_buffer = None
+
for pac_buffer, raw_pac_buffer in zip(pac.buffers, raw_pac.buffers):
buffer_type = pac_buffer.type
if buffer_type in self.pac_checksum_types:
@@ -3898,7 +3918,9 @@ class RawKerberosTest(TestCaseInTempDir):
checksums[buffer_type] = checksum, ctype
- if buffer_type != krb5pac.PAC_TYPE_TICKET_CHECKSUM:
+ if buffer_type == krb5pac.PAC_TYPE_FULL_CHECKSUM:
+ full_checksum_buffer = raw_pac_buffer
+ elif buffer_type != krb5pac.PAC_TYPE_TICKET_CHECKSUM:
# Zero the checksum field so that we can later verify the
# checksums. The ticket checksum field is not zeroed.
@@ -3912,6 +3934,17 @@ class RawKerberosTest(TestCaseInTempDir):
# Re-encode the PAC.
pac_data = ndr_pack(raw_pac)
+ if full_checksum_buffer is not None:
+ signature = ndr_unpack(
+ krb5pac.PAC_SIGNATURE_DATA,
+ full_checksum_buffer.info.remaining)
+ signature.signature = bytes(len(checksum))
+ full_checksum_buffer.info.remaining = ndr_pack(
+ signature)
+
+ # Re-encode the PAC.
+ full_pac_data = ndr_pack(raw_pac)
+
# Verify the signatures.
server_checksum, server_ctype = checksums[
@@ -3940,6 +3973,7 @@ class RawKerberosTest(TestCaseInTempDir):
if not service_ticket:
self.assertNotIn(krb5pac.PAC_TYPE_TICKET_CHECKSUM, checksums)
+ self.assertNotIn(krb5pac.PAC_TYPE_FULL_CHECKSUM, checksums)
else:
ticket_checksum, ticket_ctype = checksums.get(
krb5pac.PAC_TYPE_TICKET_CHECKSUM,
@@ -3958,6 +3992,19 @@ class RawKerberosTest(TestCaseInTempDir):
ticket_ctype,
ticket_checksum)
+ full_checksum, full_ctype = checksums.get(
+ krb5pac.PAC_TYPE_FULL_CHECKSUM,
+ (None, None))
+ if expect_full_checksum:
+ self.assertIsNotNone(full_checksum)
+ elif expect_full_checksum is False:
+ self.assertIsNone(full_checksum)
+ if full_checksum is not None:
+ krbtgt_key.verify_rodc_checksum(KU_NON_KERB_CKSUM_SALT,
+ full_pac_data,
+ full_ctype,
+ full_checksum)
+
def modified_ticket(self,
ticket, *,
new_ticket_key=None,
@@ -4015,6 +4062,14 @@ class RawKerberosTest(TestCaseInTempDir):
checksum_keys[krb5pac.PAC_TYPE_TICKET_CHECKSUM] = (
kdc_checksum_key)
+ if krb5pac.PAC_TYPE_FULL_CHECKSUM not in checksum_keys:
+ # If the full signature key is not present, fall back to the key
+ # used for the KDC signature.
+ kdc_checksum_key = checksum_keys.get(krb5pac.PAC_TYPE_KDC_CHECKSUM)
+ if kdc_checksum_key is not None:
+ checksum_keys[krb5pac.PAC_TYPE_FULL_CHECKSUM] = (
+ kdc_checksum_key)
+
# Decrypt the ticket.
enc_part = ticket.ticket['enc-part']
@@ -4167,6 +4222,19 @@ class RawKerberosTest(TestCaseInTempDir):
# Add the new checksum buffers to the PAC.
pac.buffers = pac_buffers
+ # Calculate the full checksum and insert it into the PAC.
+ full_checksum_buffer = checksum_buffers.get(
+ krb5pac.PAC_TYPE_FULL_CHECKSUM)
+ if full_checksum_buffer is not None:
+ full_checksum_key = checksum_keys[krb5pac.PAC_TYPE_FULL_CHECKSUM]
+
+ pac_data = ndr_pack(pac)
+ full_checksum = full_checksum_key.make_checksum(
+ KU_NON_KERB_CKSUM_SALT,
+ pac_data)
+
+ full_checksum_buffer.info.signature = full_checksum
+
# Calculate the server and KDC checksums and insert them into the PAC.
server_checksum_buffer = checksum_buffers.get(
diff --git a/python/samba/tests/krb5/rodc_tests.py b/python/samba/tests/krb5/rodc_tests.py
index 83ee35d650a..3e0e2a7712e 100755
--- a/python/samba/tests/krb5/rodc_tests.py
+++ b/python/samba/tests/krb5/rodc_tests.py
@@ -65,7 +65,9 @@ class RodcKerberosTests(KDCBaseTest):
to_rodc=True)
# Ensure the PAC contains the expected checksums.
- self.verify_ticket(service_ticket, rodc_key, service_ticket=True)
+ self.verify_ticket(service_ticket, rodc_key, service_ticket=True,
+ expect_ticket_checksum=True,
+ expect_full_checksum=True)
if __name__ == "__main__":
diff --git a/python/samba/tests/krb5/s4u_tests.py b/python/samba/tests/krb5/s4u_tests.py
index 76e8bbe990e..dcdd67e2b64 100755
--- a/python/samba/tests/krb5/s4u_tests.py
+++ b/python/samba/tests/krb5/s4u_tests.py
@@ -1126,8 +1126,8 @@ class S4UKerberosTests(KDCBaseTest):
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):
+ for checksum in (krb5pac.PAC_TYPE_SRV_CHECKSUM,
+ krb5pac.PAC_TYPE_KDC_CHECKSUM):
with self.subTest(checksum=checksum):
self._run_delegation_test(
{
@@ -1161,8 +1161,8 @@ class S4UKerberosTests(KDCBaseTest):
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):
+ for checksum in (krb5pac.PAC_TYPE_SRV_CHECKSUM,
+ krb5pac.PAC_TYPE_KDC_CHECKSUM):
with self.subTest(checksum=checksum):
self._run_delegation_test(
{
@@ -1351,6 +1351,33 @@ class S4UKerberosTests(KDCBaseTest):
checksum=checksum, ctype=ctype)
})
+ def test_constrained_delegation_rc4_client_checksum(self):
+ # Present a user ticket with RC4 checksums.
+ 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': self.rc4_pac_checksums,
+ 'expect_edata': False,
+ })
+
+ def test_rbcd_rc4_client_checksum(self):
+ # Present a user ticket with RC4 checksums.
+ expected_error_mode = (KDC_ERR_GENERIC,
+ KDC_ERR_BADOPTION)
+
+ 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': self.rc4_pac_checksums,
+ })
+
def remove_pac_checksum(self, ticket, checksum):
checksum_keys = self.get_krbtgt_checksum_key()
@@ -1392,6 +1419,7 @@ class S4UKerberosTests(KDCBaseTest):
krb5pac.PAC_TYPE_SRV_CHECKSUM: server_key,
krb5pac.PAC_TYPE_KDC_CHECKSUM: krbtgt_key,
krb5pac.PAC_TYPE_TICKET_CHECKSUM: krbtgt_key,
+ krb5pac.PAC_TYPE_FULL_CHECKSUM: krbtgt_key,
}
# Make a copy of the existing key and change the ctype.
@@ -1404,6 +1432,31 @@ class S4UKerberosTests(KDCBaseTest):
checksum_keys=checksum_keys,
include_checksums={checksum: True})
+ def rc4_pac_checksums(self, ticket):
+ krbtgt_creds = self.get_krbtgt_creds()
+ rc4_krbtgt_key = self.TicketDecryptionKey_from_creds(
+ krbtgt_creds, etype=Enctype.RC4)
+
+ server_key = ticket.decryption_key
+
+ checksum_keys = {
+ krb5pac.PAC_TYPE_SRV_CHECKSUM: server_key,
+ krb5pac.PAC_TYPE_KDC_CHECKSUM: rc4_krbtgt_key,
+ krb5pac.PAC_TYPE_TICKET_CHECKSUM: rc4_krbtgt_key,
+ krb5pac.PAC_TYPE_FULL_CHECKSUM: rc4_krbtgt_key,
+ }
+
+ include_checksums = {
+ krb5pac.PAC_TYPE_SRV_CHECKSUM: True,
+ krb5pac.PAC_TYPE_KDC_CHECKSUM: True,
+ krb5pac.PAC_TYPE_TICKET_CHECKSUM: True,
+ krb5pac.PAC_TYPE_FULL_CHECKSUM: True,
+ }
+
+ return self.modified_ticket(ticket,
+ checksum_keys=checksum_keys,
+ include_checksums=include_checksums)
+
def add_delegation_info(self, ticket, services=None):
def modify_pac_fn(pac):
pac_buffers = pac.buffers