CVE-2022-37967 Add new PAC checksum
authorJoseph Sutton <josephsutton@catalyst.net.nz>
Wed, 9 Nov 2022 00:45:13 +0000 (13:45 +1300)
committerStefan Metzmacher <metze@samba.org>
Wed, 14 Dec 2022 10:28:16 +0000 (10:28 +0000)
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]

[jsutton@samba.org Fixed conflicts in kdc_base_test.py, raw_testcase.py,
 knownfails, tests.py. Adapted KDC PAC changes to older function.]

14 files changed:
librpc/idl/krb5pac.idl
python/samba/tests/krb5/compatability_tests.py
python/samba/tests/krb5/kdc_base_test.py
python/samba/tests/krb5/kdc_tgs_tests.py
python/samba/tests/krb5/raw_testcase.py
python/samba/tests/krb5/rodc_tests.py
python/samba/tests/krb5/s4u_tests.py
selftest/knownfail_mit_kdc
source4/kdc/wdc-samba4.c
source4/selftest/tests.py
source4/torture/rpc/remote_pac.c
third_party/heimdal/kdc/kerberos5.c
third_party/heimdal/kdc/krb5tgs.c
third_party/heimdal/lib/krb5/pac.c

index bbe4a253e3a24e7f8ad9950237c9ef058017b4c4..a21533fdc3c439aba65904fdfc4e687f535405fa 100644 (file)
@@ -146,7 +146,8 @@ interface krb5pac
                PAC_TYPE_DEVICE_CLAIMS_INFO = 15,
                PAC_TYPE_TICKET_CHECKSUM = 16,
                PAC_TYPE_ATTRIBUTES_INFO = 17,
-               PAC_TYPE_REQUESTER_SID = 18
+               PAC_TYPE_REQUESTER_SID = 18,
+               PAC_TYPE_FULL_CHECKSUM = 19
        } PAC_TYPE;
 
        typedef struct {
@@ -165,6 +166,7 @@ interface krb5pac
                [case(PAC_TYPE_TICKET_CHECKSUM)]        PAC_SIGNATURE_DATA ticket_checksum;
                [case(PAC_TYPE_ATTRIBUTES_INFO)]        PAC_ATTRIBUTES_INFO attributes_info;
                [case(PAC_TYPE_REQUESTER_SID)]  PAC_REQUESTER_SID requester_sid;
+               [case(PAC_TYPE_FULL_CHECKSUM)]  PAC_SIGNATURE_DATA full_checksum;
                /* when new PAC info types are added they are supposed to be done
                   in such a way that they are backwards compatible with existing
                   servers. This makes it safe to just use a [default] for
index 44c2afd41dce0c597158f892a94374023cbb8d84..5dc09bcc46f69ce9fdd0b94a09c44b73e172f616 100755 (executable)
@@ -162,6 +162,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()
index 1c03c24693c47495c9fb8b7f994e52c3f24ce6ce..a6e5540b13d098e5aa24d4b695818d04d7e1015a 100644 (file)
@@ -1425,7 +1425,8 @@ class KDCBaseTest(RawKerberosTest):
         krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds)
         self.verify_ticket(service_ticket_creds, krbtgt_key,
                            service_ticket=True, expect_pac=expect_pac,
-                           expect_ticket_checksum=self.tkt_sig_support)
+                           expect_ticket_checksum=self.tkt_sig_support,
+                           expect_full_checksum=self.full_sig_support)
 
         self.tkt_cache[cache_key] = service_ticket_creds
 
index 6b1960806e0651f29b20fe3d440ed080d660d013..8040bf62d6c198e0c6927b97cbaddf7abcfba6c2 100755 (executable)
@@ -1593,6 +1593,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()
@@ -2604,7 +2641,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:
@@ -2623,7 +2662,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,
@@ -2638,7 +2679,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:
@@ -2677,13 +2720,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:
index b1113e6d4eab29be1b54ff1600214630616c1100..50faa8b2c836c353466c7ad75803b8913eb78e35 100644 (file)
@@ -543,7 +543,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", },
@@ -637,6 +638,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))
+
         expect_pac = samba.tests.env_get_var_value('EXPECT_PAC',
                                                    allow_missing=True)
         if expect_pac is None:
@@ -2397,6 +2404,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,
@@ -2456,6 +2464,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,
@@ -2511,6 +2520,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,
@@ -2571,6 +2581,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,
@@ -3046,7 +3057,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:
@@ -3056,7 +3068,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
 
@@ -3093,12 +3107,15 @@ class RawKerberosTest(TestCaseInTempDir):
 
         if not self.is_tgs(expected_sname) and rep_msg_type == KRB_TGS_REP:
             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)
 
@@ -3774,7 +3791,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
@@ -3819,6 +3837,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:
@@ -3833,7 +3853,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.
 
@@ -3847,6 +3869,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[
@@ -3875,6 +3908,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,
@@ -3893,6 +3927,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,
@@ -3950,6 +3997,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']
@@ -4102,6 +4157,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(
index 83ee35d650afcab9fa9097b76be05326a376b488..3e0e2a7712e6f3ab9cb10d6dc060fdcb5ad3ecc3 100755 (executable)
@@ -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__":
index 49dd89cd7640764ee6887656133f8a30cd519a84..1d8e4ae56606999c90a3b77df2c939d766ecabbe 100755 (executable)
@@ -1024,8 +1024,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(
                     {
@@ -1059,8 +1059,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(
                     {
@@ -1249,6 +1249,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()
 
@@ -1290,6 +1317,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.
@@ -1302,6 +1330,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
index 269d1fe77030e05a1ad4d164d183a95c96657144..a86c9ada823d02adaa924248e13a4c19b0392b10 100644 (file)
@@ -359,8 +359,6 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_
 #
 # PAC tests
 #
-^netr-bdc-arcfour.verify-sig-arcfour
-^netr-bdc-arcfour.verify-sig-arcfour
 ^samba4.blackbox.pkinit_pac.STEP1 remote.pac verification.ad_dc:local
 ^samba4.blackbox.pkinit_pac.STEP1 remote.pac verification.ad_dc_ntvfs:local
 ^samba4.blackbox.pkinit_pac.netr-bdc-aes.verify-sig-aes.ad_dc:local
@@ -412,6 +410,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_fast_pac_request_false
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_fast_pac_request_none
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_fast_pac_request_true
+^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_fast_rc4.ad_dc
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_fast_req
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_fast_req_invalid
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_fast_rodc_allowed_denied
@@ -427,6 +426,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_authdata_no_pac
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_no_pac
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_pac_request_true
+^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rc4.ad_dc
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_req
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_allowed_denied
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_denied
@@ -446,6 +446,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_rodc_validate_pac_request_true
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_authdata_no_pac
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_no_pac
+^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rc4.ad_dc
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_req(?!_invalid)
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_allowed_denied
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_denied
@@ -460,6 +461,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_nonexisting
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_no_pac
+^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rc4.ad_dc
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rename
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_allowed_denied
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_denied
@@ -473,6 +475,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_sname
+^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rc4.ad_dc
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_req_invalid
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_allowed_denied
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_denied
@@ -492,6 +495,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_no_pac
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_pac_request_true
+^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rc4.ad_dc
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_req
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_allowed_denied
 ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_denied
@@ -567,3 +571,8 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_
 ^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
+#
+# KDC compatibility
+#
+^samba.tests.krb5.compatability_tests.samba.tests.krb5.compatability_tests.SimpleKerberosTests.test_full_signature.ad_dc
+^samba.tests.krb5.compatability_tests.samba.tests.krb5.compatability_tests.SimpleKerberosTests.test_full_signature.fl2003dc
index ae0dc22fd49a63651d581d1e00650bf5e4aa749a..fc3605c0877979bf3e314b8a0e48186a1ecaf739 100644 (file)
@@ -151,6 +151,7 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context,
        ssize_t tkt_checksum_idx = -1;
        ssize_t attrs_info_idx = -1;
        ssize_t requester_sid_idx = -1;
+       ssize_t full_checksum_idx = -1;
 
        if (!mem_ctx) {
                return ENOMEM;
@@ -222,7 +223,7 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context,
                        return ret;
                }
 
-               /* Check the KDC and ticket signatures. */
+               /* Check the KDC, whole-PAC and ticket signatures. */
                ret = krb5_pac_verify(context,
                                      *pac,
                                      0,
@@ -434,6 +435,19 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context,
                        }
                        requester_sid_idx = i;
                        break;
+               case PAC_TYPE_FULL_CHECKSUM:
+                       if (full_checksum_idx != -1) {
+                               DBG_WARNING("full checksum type[%"PRIu32"] twice "
+                                           "[%zd] and [%zu]: \n",
+                                           types[i],
+                                           full_checksum_idx,
+                                           i);
+                               SAFE_FREE(types);
+                               talloc_free(mem_ctx);
+                               return EINVAL;
+                       }
+                       full_checksum_idx = i;
+                       break;
                default:
                        continue;
                }
@@ -613,6 +627,13 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context,
                        } else {
                                continue;
                        }
+               case PAC_TYPE_FULL_CHECKSUM:
+                       /*
+                        * this is generated in the main KDC code
+                        * we just add a place holder here.
+                        */
+                       type_blob = data_blob_const(&zero_byte, 1);
+                       break;
                default:
                        /* just copy... */
                        break;
index 1429fe7f34cc14595c8a00e81b02af5adba09d13..ce5ffb7c2e7294f612c4bc172de165cf7b46bb66 100755 (executable)
@@ -965,6 +965,7 @@ have_fast_support = 1
 claims_support = 0
 compound_id_support = 0
 tkt_sig_support = int('SAMBA4_USES_HEIMDAL' in config_hash)
+full_sig_support = int('SAMBA4_USES_HEIMDAL' in config_hash)
 expect_pac = int('SAMBA4_USES_HEIMDAL' in config_hash)
 extra_pac_buffers = int('SAMBA4_USES_HEIMDAL' in config_hash)
 check_cname = int('SAMBA4_USES_HEIMDAL' in config_hash)
@@ -980,6 +981,7 @@ krb5_environ = {
     'CLAIMS_SUPPORT': claims_support,
     'COMPOUND_ID_SUPPORT': compound_id_support,
     'TKT_SIG_SUPPORT': tkt_sig_support,
+    'FULL_SIG_SUPPORT': full_sig_support,
     'EXPECT_PAC': expect_pac,
     'EXPECT_EXTRA_PAC_BUFFERS': extra_pac_buffers,
     'CHECK_CNAME': check_cname,
@@ -1648,8 +1650,13 @@ for env in ["rodc", "promoted_dc", "fl2000dc", "fl2008r2dc"]:
 
 planpythontestsuite("ad_dc", "samba.tests.krb5.as_canonicalization_tests",
                     environ=krb5_environ)
-planpythontestsuite("ad_dc", "samba.tests.krb5.compatability_tests",
-                    environ=krb5_environ)
+for env, fast_support in [("ad_dc", True),
+                          ("fl2003dc", False)]:
+    planpythontestsuite(env, "samba.tests.krb5.compatability_tests",
+                        environ={
+                            **krb5_environ,
+                            'FAST_SUPPORT': int(fast_support),
+                        })
 planpythontestsuite("ad_dc", "samba.tests.krb5.kdc_tests",
                     environ=krb5_environ)
 planpythontestsuite(
index 4744f48aba4a5e73fea1cb66e4a649072020f6d0..ac2f2f491d9f19bd75b662025b47f4ada3908ad9 100644 (file)
@@ -313,7 +313,7 @@ static bool test_PACVerify(struct torture_context *tctx,
                                       (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA);
        torture_assert(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), "ndr_pull_struct_blob of PAC_DATA structure failed");
 
-       num_pac_buffers = 5;
+       num_pac_buffers = 6;
        if (expect_pac_upn_dns_info) {
                num_pac_buffers += 1;
        }
@@ -370,6 +370,12 @@ static bool test_PACVerify(struct torture_context *tctx,
                       pac_buf->info != NULL,
                       "PAC_TYPE_TICKET_CHECKSUM info");
 
+       pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_FULL_CHECKSUM);
+       torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_FULL_CHECKSUM");
+       torture_assert(tctx,
+                      pac_buf->info != NULL,
+                      "PAC_TYPE_FULL_CHECKSUM info");
+
        ok = netlogon_validate_pac(tctx, p, server_creds, secure_channel_type, test_machine_name,
                                   negotiate_flags, pac_data, session_info);
 
@@ -1133,7 +1139,7 @@ static bool test_S4U2Proxy(struct torture_context *tctx,
                                       (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA);
        torture_assert(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), "ndr_pull_struct_blob of PAC_DATA structure failed");
 
-       num_pac_buffers = 7;
+       num_pac_buffers = 8;
 
        torture_assert_int_equal(tctx, pac_data_struct.version, 0, "version");
        torture_assert_int_equal(tctx, pac_data_struct.num_buffers, num_pac_buffers, "num_buffers");
@@ -1162,6 +1168,10 @@ static bool test_S4U2Proxy(struct torture_context *tctx,
        torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_TICKET_CHECKSUM");
        torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_TICKET_CHECKSUM info");
 
+       pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_FULL_CHECKSUM);
+       torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_FULL_CHECKSUM");
+       torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_FULL_CHECKSUM info");
+
        pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_CONSTRAINED_DELEGATION);
        torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_CONSTRAINED_DELEGATION");
        torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_CONSTRAINED_DELEGATION info");
index 3e0f2dbd6b636bc6874880afe1eeb63407798db5..b4968afcaaf3e621161ecfa7cf1cee9c8cf28c9f 100644 (file)
@@ -1913,6 +1913,7 @@ generate_pac(astgs_request_t r, const Key *skey, const Key *tkey,
                         rodc_id,
                         NULL, /* UPN */
                         canon_princ,
+                        false, /* add_full_sig */
                         is_tgs ? &r->pac_attributes : NULL,
                         &data);
     krb5_free_principal(r->context, client);
index aab6806fbe12f06a56d5c02c59f4c0c8f2761204..893e77749cf94ef210d701abcc96b511935fad6a 100644 (file)
@@ -778,7 +778,7 @@ tgs_make_reply(astgs_request_t r,
 
            ret = _krb5_kdc_pac_sign_ticket(r->context, r->pac, r->client_princ, serverkey,
                                            krbtgtkey, rodc_id, NULL, r->canon_client_princ,
-                                           add_ticket_sig, et,
+                                           add_ticket_sig, add_ticket_sig, et,
                                            is_tgs ? &r->pac_attributes : NULL);
            if (ret)
                goto out;
index c11990a16067c0123fe2c5d6094a7a24c9385c84..e58ab908085c2eb7e543c01bc7f3be6b05db4336 100644 (file)
@@ -74,6 +74,7 @@ struct krb5_pac_data {
     struct PAC_INFO_BUFFER *upn_dns_info;
     struct PAC_INFO_BUFFER *ticket_checksum;
     struct PAC_INFO_BUFFER *attributes_info;
+    struct PAC_INFO_BUFFER *full_checksum;
     krb5_data ticket_sign_data;
 
     /* PAC_UPN_DNS_INFO */
@@ -101,6 +102,7 @@ struct krb5_pac_data {
 #define PAC_TICKET_CHECKSUM            16
 #define PAC_ATTRIBUTES_INFO            17
 #define PAC_REQUESTOR_SID              18
+#define PAC_FULL_CHECKSUM              19
 
 /* Flag in PAC_UPN_DNS_INFO */
 #define PAC_EXTRA_LOGON_INFO_FLAGS_UPN_DEFAULTED       0x1
@@ -398,6 +400,13 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
             else
                 p->attributes_info = &p->pac->buffers[i];
             break;
+        case PAC_FULL_CHECKSUM:
+           if (p->full_checksum)
+               krb5_set_error_message(context, ret = EINVAL,
+                                      N_("PAC has multiple full checksums", ""));
+            else
+                p->full_checksum = &p->pac->buffers[i];
+            break;
         default: break;
         }
     }
@@ -668,7 +677,8 @@ verify_checksum(krb5_context context,
                const struct PAC_INFO_BUFFER *sig,
                const krb5_data *data,
                void *ptr, size_t len,
-               const krb5_keyblock *key)
+               const krb5_keyblock *key,
+               krb5_boolean strict_cksumtype_match)
 {
     krb5_storage *sp = NULL;
     uint32_t type;
@@ -726,7 +736,7 @@ verify_checksum(krb5_context context,
      * http://blogs.msdn.com/b/openspecification/archive/2010/01/01/verifying-the-server-signature-in-kerberos-privilege-account-certificate.aspx
      * for Microsoft's explaination */
 
-    if (cksum.cksumtype == CKSUMTYPE_HMAC_MD5) {
+    if (cksum.cksumtype == CKSUMTYPE_HMAC_MD5 && !strict_cksumtype_match) {
        Checksum local_checksum;
 
        memset(&local_checksum, 0, sizeof(local_checksum));
@@ -1189,7 +1199,7 @@ parse_attributes_info(krb5_context context,
  * @param pac the pac structure returned by krb5_pac_parse().
  * @param authtime The time of the ticket the PAC belongs to.
  * @param principal the principal to verify.
- * @param server The service key, most always be given.
+ * @param server The service key, may be given.
  * @param privsvr The KDC key, may be given.
 
  * @return Returns 0 to indicate success. Otherwise an kerberos et
@@ -1207,6 +1217,21 @@ krb5_pac_verify(krb5_context context,
                const krb5_keyblock *privsvr)
 {
     krb5_error_code ret;
+    /*
+     * If we are in the KDC, we expect back a full signature in the PAC
+     *
+     * This is set up as a seperate variable to make it easier if a
+     * subsequent patch is added to make this configurable in the
+     * krb5.conf (or forced into the krb5_context via Samba)
+     */
+    krb5_boolean expect_full_sig = privsvr != NULL;
+
+    /*
+     * If we are on the KDC, then we trust we are not in a realm with
+     * buggy Windows 2008 or similar era DCs that give our HMAC-MD5
+     * sigatures over AES keys.  DES is also already gone.
+     */
+    krb5_boolean strict_cksumtype_match = expect_full_sig;
 
     if (pac->server_checksum == NULL) {
        krb5_set_error_message(context, EINVAL, "PAC missing server checksum");
@@ -1220,6 +1245,10 @@ krb5_pac_verify(krb5_context context,
        krb5_set_error_message(context, EINVAL, "PAC missing logon name");
        return EINVAL;
     }
+    if (expect_full_sig && pac->full_checksum == NULL) {
+       krb5_set_error_message(context, EINVAL, "PAC missing full checksum");
+       return EINVAL;
+    }
 
     if (principal != NULL) {
        ret = verify_logonname(context, pac->logon_name, &pac->data, authtime,
@@ -1232,14 +1261,15 @@ krb5_pac_verify(krb5_context context,
         pac->privsvr_checksum->buffersize < 4)
        return EINVAL;
 
-    /*
-     * in the service case, clean out data option of the privsvr and
-     * server checksum before checking the checksum.
-     */
-    if (server != NULL)
+    if (server != NULL || privsvr != NULL)
     {
        krb5_data *copy;
 
+       /*
+        * in the service case, clean out data option of the privsvr and
+        * server checksum before checking the checksum.
+        */
+
        ret = krb5_copy_data(context, &pac->data, &copy);
        if (ret)
            return ret;
@@ -1252,15 +1282,43 @@ krb5_pac_verify(krb5_context context,
               0,
               pac->privsvr_checksum->buffersize - 4);
 
-       ret = verify_checksum(context,
-                             pac->server_checksum,
-                             &pac->data,
-                             copy->data,
-                             copy->length,
-                             server);
+       if (server != NULL) {
+           ret = verify_checksum(context,
+                                 pac->server_checksum,
+                                 &pac->data,
+                                 copy->data,
+                                 copy->length,
+                                 server,
+                                 strict_cksumtype_match);
+           if (ret) {
+               krb5_free_data(context, copy);
+               return ret;
+           }
+       }
+
+       if (privsvr != NULL && pac->full_checksum != NULL) {
+           /*
+            * in the full checksum case, also clean out the full
+            * checksum before verifying it.
+            */
+           memset((char *)copy->data + pac->full_checksum->offset + 4,
+                  0,
+                  pac->full_checksum->buffersize - 4);
+
+           ret = verify_checksum(context,
+                                 pac->full_checksum,
+                                 &pac->data,
+                                 copy->data,
+                                 copy->length,
+                                 privsvr,
+                                 strict_cksumtype_match);
+           if (ret) {
+               krb5_free_data(context, copy);
+               return ret;
+           }
+       }
+
        krb5_free_data(context, copy);
-       if (ret)
-           return ret;
     }
     if (privsvr) {
        /* The priv checksum covers the server checksum */
@@ -1270,7 +1328,8 @@ krb5_pac_verify(krb5_context context,
                              (char *)pac->data.data
                              + pac->server_checksum->offset + 4,
                              pac->server_checksum->buffersize - 4,
-                             privsvr);
+                             privsvr,
+                             strict_cksumtype_match);
        if (ret)
            return ret;
 
@@ -1283,7 +1342,8 @@ krb5_pac_verify(krb5_context context,
 
            ret = verify_checksum(context, pac->ticket_checksum, &pac->data,
                                 pac->ticket_sign_data.data,
-                                pac->ticket_sign_data.length, privsvr);
+                                pac->ticket_sign_data.length, privsvr,
+                                strict_cksumtype_match);
            if (ret)
                return ret;
        }
@@ -1374,6 +1434,7 @@ _krb5_pac_sign(krb5_context context,
               uint16_t rodc_id,
               krb5_const_principal upn_princ,
               krb5_const_principal canon_princ,
+              krb5_boolean add_full_sig,
               uint64_t *pac_attributes, /* optional */
               krb5_data *data)
 {
@@ -1381,7 +1442,7 @@ _krb5_pac_sign(krb5_context context,
     krb5_storage *sp = NULL, *spdata = NULL;
     uint32_t end;
     size_t server_size, priv_size;
-    uint32_t server_offset = 0, priv_offset = 0, ticket_offset = 0;
+    uint32_t server_offset = 0, priv_offset = 0, ticket_offset = 0, full_offset = 0;
     uint32_t server_cksumtype = 0, priv_cksumtype = 0;
     uint32_t num = 0;
     uint32_t i, sz;
@@ -1458,6 +1519,16 @@ _krb5_pac_sign(krb5_context context,
                                       N_("PAC has multiple attributes info buffers", ""));
                goto out;
            }
+       } else if (p->pac->buffers[i].type == PAC_FULL_CHECKSUM) {
+           if (p->full_checksum == NULL) {
+               p->full_checksum = &p->pac->buffers[i];
+           }
+           if (p->full_checksum != &p->pac->buffers[i]) {
+               ret = KRB5KDC_ERR_BADOPTION;
+               krb5_set_error_message(context, ret,
+                                      N_("PAC has multiple full checksums", ""));
+               goto out;
+           }
        }
     }
 
@@ -1470,6 +1541,8 @@ _krb5_pac_sign(krb5_context context,
        num++;
     if (p->ticket_sign_data.length != 0 && p->ticket_checksum == NULL)
        num++;
+    if (add_full_sig && p->full_checksum == NULL)
+       num++;
 
     /* Allocate any missing-but-necessary buffers */
     if (num) {
@@ -1512,6 +1585,11 @@ _krb5_pac_sign(krb5_context context,
            p->ticket_checksum = &p->pac->buffers[p->pac->numbuffers++];
            p->ticket_checksum->type = PAC_TICKET_CHECKSUM;
        }
+       if (add_full_sig && p->full_checksum == NULL) {
+           p->full_checksum = &p->pac->buffers[p->pac->numbuffers++];
+           memset(p->full_checksum, 0, sizeof(*p->full_checksum));
+           p->full_checksum->type = PAC_FULL_CHECKSUM;
+       }
     }
 
     /* Calculate LOGON NAME */
@@ -1644,6 +1722,31 @@ _krb5_pac_sign(krb5_context context,
                len += sizeof(rodc_id);
                CHECK(ret, krb5_store_uint16(spdata, rodc_id), out);
            }
+       } else if (add_full_sig &&
+                  p->pac->buffers[i].type == PAC_FULL_CHECKSUM) {
+           if (priv_size > UINT32_MAX - 4) {
+               ret = EINVAL;
+               krb5_set_error_message(context, ret, "integer overrun");
+               goto out;
+           }
+           len = priv_size + 4;
+           if (end > UINT32_MAX - 4) {
+               ret = EINVAL;
+               krb5_set_error_message(context, ret, "integer overrun");
+               goto out;
+           }
+           full_offset = end + 4;
+           CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out);
+           CHECK(ret, fill_zeros(context, spdata, priv_size), out);
+           if (rodc_id != 0) {
+               if (len > UINT32_MAX - sizeof(rodc_id)) {
+                   ret = EINVAL;
+                   krb5_set_error_message(context, ret, "integer overrun");
+                   goto out;
+               }
+               len += sizeof(rodc_id);
+               CHECK(ret, fill_zeros(context, spdata, sizeof(rodc_id)), out);
+           }
        } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
            len = krb5_storage_write(spdata, logon.data, logon.length);
            if (logon.length != len) {
@@ -1705,6 +1808,21 @@ _krb5_pac_sign(krb5_context context,
                              p->ticket_sign_data.data,
                              p->ticket_sign_data.length,
                              (char *)d.data + ticket_offset, priv_size);
+    if (ret == 0 && add_full_sig)
+        ret = create_checksum(context, priv_key, priv_cksumtype,
+                              d.data, d.length,
+                              (char *)d.data + full_offset, priv_size);
+    if (ret == 0 && add_full_sig && rodc_id != 0) {
+       void *buf = (char *)d.data + full_offset + priv_size;
+       krb5_storage *rs = krb5_storage_from_mem(buf, sizeof(rodc_id));
+       if (rs == NULL)
+           ret = krb5_enomem(context);
+        else
+            krb5_storage_set_flags(rs, KRB5_STORAGE_BYTEORDER_LE);
+        if (ret == 0)
+            ret = krb5_store_uint16(rs, rodc_id);
+       krb5_storage_free(rs);
+    }
     if (ret == 0)
         ret = create_checksum(context, server_key, server_cksumtype,
                               d.data, d.length,
@@ -1714,22 +1832,15 @@ _krb5_pac_sign(krb5_context context,
                               (char *)d.data + server_offset, server_size,
                               (char *)d.data + priv_offset, priv_size);
     if (ret == 0 && rodc_id != 0) {
-       krb5_data rd;
-       krb5_storage *rs = krb5_storage_emem();
+       void *buf = (char *)d.data + priv_offset + priv_size;
+       krb5_storage *rs = krb5_storage_from_mem(buf, sizeof(rodc_id));
        if (rs == NULL)
            ret = krb5_enomem(context);
         else
             krb5_storage_set_flags(rs, KRB5_STORAGE_BYTEORDER_LE);
         if (ret == 0)
             ret = krb5_store_uint16(rs, rodc_id);
-        if (ret == 0)
-            ret = krb5_storage_to_data(rs, &rd);
        krb5_storage_free(rs);
-       if (ret)
-           goto out;
-       heim_assert(rd.length == sizeof(rodc_id), "invalid length");
-       memcpy((char *)d.data + priv_offset + priv_size, rd.data, rd.length);
-       krb5_data_free(&rd);
     }
 
     if (ret)
@@ -1972,6 +2083,7 @@ _krb5_kdc_pac_sign_ticket(krb5_context context,
                          krb5_const_principal upn,
                          krb5_const_principal canon_name,
                          krb5_boolean add_ticket_sig,
+                         krb5_boolean add_full_sig,
                          EncTicketPart *tkt,
                          uint64_t *pac_attributes) /* optional */
 {
@@ -2009,6 +2121,7 @@ _krb5_kdc_pac_sign_ticket(krb5_context context,
 
     ret = _krb5_pac_sign(context, pac, tkt->authtime, client, server_key,
                         kdc_key, rodc_id, upn, canon_name,
+                        add_full_sig,
                         pac_attributes, &rspac);
     if (ret == 0)
         ret = _kdc_tkt_insert_pac(context, tkt, &rspac);