s4:mitkdc: Add support for MIT Kerberos 1.20
[asn/samba.git] / python / samba / tests / krb5 / compatability_tests.py
1 #!/usr/bin/env python3
2 # Unix SMB/CIFS implementation.
3 # Copyright (C) Stefan Metzmacher 2020
4 # Copyright (C) Catalyst.Net Ltd 2020
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import sys
21 import os
22
23 sys.path.insert(0, "bin/python")
24 os.environ["PYTHONUNBUFFERED"] = "1"
25
26 from samba.tests.krb5.kdc_base_test import KDCBaseTest
27 import samba.tests.krb5.rfc4120_pyasn1 as krb5_asn1
28 from samba.tests.krb5.rfc4120_constants import (
29     AES128_CTS_HMAC_SHA1_96,
30     AES256_CTS_HMAC_SHA1_96,
31     ARCFOUR_HMAC_MD5,
32     KDC_ERR_PREAUTH_REQUIRED,
33     KRB_AS_REP,
34     KRB_ERROR,
35     KU_AS_REP_ENC_PART,
36     KU_PA_ENC_TIMESTAMP,
37     PADATA_ENC_TIMESTAMP,
38     PADATA_ETYPE_INFO2,
39     NT_PRINCIPAL,
40     NT_SRV_INST,
41 )
42
43 global_asn1_print = False
44 global_hexdump = False
45
46 HIEMDAL_ENC_AS_REP_PART_TYPE_TAG = 0x79
47 # MIT uses the EncTGSRepPart tag for the EncASRepPart
48 MIT_ENC_AS_REP_PART_TYPE_TAG = 0x7A
49
50 ENC_PA_REP_FLAG = 0x00010000
51
52
53 class SimpleKerberosTests(KDCBaseTest):
54
55     def setUp(self):
56         super(SimpleKerberosTests, self).setUp()
57         self.do_asn1_print = global_asn1_print
58         self.do_hexdump = global_hexdump
59
60     def test_mit_EncASRepPart_tag(self):
61         creds = self.get_user_creds()
62         (enc, _) = self.as_req(creds)
63         self.assertEqual(MIT_ENC_AS_REP_PART_TYPE_TAG, enc[0])
64
65     def test_heimdal_EncASRepPart_tag(self):
66         creds = self.get_user_creds()
67         (enc, _) = self.as_req(creds)
68         self.assertEqual(HIEMDAL_ENC_AS_REP_PART_TYPE_TAG, enc[0])
69
70     def test_mit_EncryptedData_kvno(self):
71         creds = self.get_user_creds()
72         (_, enc) = self.as_req(creds)
73         if 'kvno' in enc:
74             self.fail("kvno present in EncryptedData")
75
76     def test_heimdal_EncryptedData_kvno(self):
77         creds = self.get_user_creds()
78         (_, enc) = self.as_req(creds)
79         if 'kvno' not in enc:
80             self.fail("kvno absent in EncryptedData")
81
82     def test_mit_EncASRepPart_FAST_support(self):
83         creds = self.get_user_creds()
84         (enc, _) = self.as_req(creds)
85         self.assertEqual(MIT_ENC_AS_REP_PART_TYPE_TAG, enc[0])
86         as_rep = self.der_decode(enc, asn1Spec=krb5_asn1.EncTGSRepPart())
87         flags = int(as_rep['flags'], base=2)
88         # MIT sets enc-pa-rep, flag bit 15
89         # RFC 6806 11. Negotiation of FAST and Detecting Modified Requests
90         self.assertTrue(ENC_PA_REP_FLAG & flags)
91
92     def test_heimdal_and_windows_EncASRepPart_FAST_support(self):
93         creds = self.get_user_creds()
94         (enc, _) = self.as_req(creds)
95         self.assertEqual(HIEMDAL_ENC_AS_REP_PART_TYPE_TAG, enc[0])
96         as_rep = self.der_decode(enc, asn1Spec=krb5_asn1.EncASRepPart())
97         flags = as_rep['flags']
98         flags = int(as_rep['flags'], base=2)
99         # Heimdal and Windows does set enc-pa-rep, flag bit 15
100         # RFC 6806 11. Negotiation of FAST and Detecting Modified Requests
101         self.assertTrue(ENC_PA_REP_FLAG & flags)
102
103     def test_mit_arcfour_salt(self):
104         creds = self.get_user_creds()
105         etypes = (ARCFOUR_HMAC_MD5,)
106         (rep, *_) = self.as_pre_auth_req(creds, etypes)
107         self.check_preauth_rep(rep)
108         etype_info2 = self.get_etype_info2(rep)
109         if 'salt' not in etype_info2[0]:
110             self.fail(
111                 "(MIT) Salt not populated for ARCFOUR_HMAC_MD5 encryption")
112
113     def test_heimdal_arcfour_salt(self):
114         creds = self.get_user_creds()
115         etypes = (ARCFOUR_HMAC_MD5,)
116         (rep, *_) = self.as_pre_auth_req(creds, etypes)
117         self.check_preauth_rep(rep)
118         etype_info2 = self.get_etype_info2(rep)
119         if 'salt' in etype_info2[0]:
120             self.fail(
121                 "(Heimdal) Salt populated for ARCFOUR_HMAC_MD5 encryption")
122
123     # This tests also passes again Samba AD built with MIT Kerberos 1.20 which
124     # is not released yet.
125     #
126     # FIXME: Should be moved to to a new kdc_tgt_tests.py once MIT KRB5 1.20
127     # is released.
128     def test_ticket_signature(self):
129         # Ensure that a DC correctly issues tickets signed with its krbtgt key.
130         user_creds = self.get_client_creds()
131         target_creds = self.get_service_creds()
132
133         krbtgt_creds = self.get_krbtgt_creds()
134         key = self.TicketDecryptionKey_from_creds(krbtgt_creds)
135
136         # Get a TGT from the DC.
137         tgt = self.get_tgt(user_creds)
138
139         # Ensure the PAC contains the expected checksums.
140         self.verify_ticket(tgt, key, service_ticket=False)
141
142         # Get a service ticket from the DC.
143         service_ticket = self.get_service_ticket(tgt, target_creds)
144
145         # Ensure the PAC contains the expected checksums.
146         self.verify_ticket(service_ticket, key, service_ticket=True,
147                            expect_ticket_checksum=True)
148
149     def test_mit_pre_1_20_ticket_signature(self):
150         # Ensure that a DC does not issue tickets signed with its krbtgt key.
151         user_creds = self.get_client_creds()
152         target_creds = self.get_service_creds()
153
154         krbtgt_creds = self.get_krbtgt_creds()
155         key = self.TicketDecryptionKey_from_creds(krbtgt_creds)
156
157         # Get a TGT from the DC.
158         tgt = self.get_tgt(user_creds)
159
160         # Ensure the PAC contains the expected checksums.
161         self.verify_ticket(tgt, key, service_ticket=False)
162
163         # Get a service ticket from the DC.
164         service_ticket = self.get_service_ticket(tgt, target_creds)
165
166         # Ensure the PAC does not contain the expected checksums.
167         self.verify_ticket(service_ticket, key, service_ticket=True,
168                            expect_ticket_checksum=False)
169
170     def as_pre_auth_req(self, creds, etypes):
171         user = creds.get_username()
172         realm = creds.get_realm()
173
174         cname = self.PrincipalName_create(
175             name_type=NT_PRINCIPAL,
176             names=[user])
177         sname = self.PrincipalName_create(
178             name_type=NT_SRV_INST,
179             names=["krbtgt", realm])
180
181         till = self.get_KerberosTime(offset=36000)
182
183         kdc_options = krb5_asn1.KDCOptions('forwardable')
184         padata = None
185
186         req = self.AS_REQ_create(padata=padata,
187                                  kdc_options=str(kdc_options),
188                                  cname=cname,
189                                  realm=realm,
190                                  sname=sname,
191                                  from_time=None,
192                                  till_time=till,
193                                  renew_time=None,
194                                  nonce=0x7fffffff,
195                                  etypes=etypes,
196                                  addresses=None,
197                                  additional_tickets=None)
198         rep = self.send_recv_transaction(req)
199
200         return (rep, cname, sname, realm, till)
201
202     def check_preauth_rep(self, rep):
203         self.assertIsNotNone(rep)
204         self.assertEqual(rep['msg-type'], KRB_ERROR)
205         self.assertEqual(rep['error-code'], KDC_ERR_PREAUTH_REQUIRED)
206
207     def get_etype_info2(self, rep):
208
209         rep_padata = self.der_decode(
210             rep['e-data'],
211             asn1Spec=krb5_asn1.METHOD_DATA())
212
213         for pa in rep_padata:
214             if pa['padata-type'] == PADATA_ETYPE_INFO2:
215                 etype_info2 = pa['padata-value']
216                 break
217
218         etype_info2 = self.der_decode(
219             etype_info2,
220             asn1Spec=krb5_asn1.ETYPE_INFO2())
221         return etype_info2
222
223     def as_req(self, creds):
224         etypes = (
225             AES256_CTS_HMAC_SHA1_96,
226             AES128_CTS_HMAC_SHA1_96,
227             ARCFOUR_HMAC_MD5)
228         (rep, cname, sname, realm, till) = self.as_pre_auth_req(creds, etypes)
229         self.check_preauth_rep(rep)
230
231         etype_info2 = self.get_etype_info2(rep)
232         key = self.PasswordKey_from_etype_info2(creds, etype_info2[0])
233
234         (patime, pausec) = self.get_KerberosTimeWithUsec()
235         pa_ts = self.PA_ENC_TS_ENC_create(patime, pausec)
236         pa_ts = self.der_encode(pa_ts, asn1Spec=krb5_asn1.PA_ENC_TS_ENC())
237
238         pa_ts = self.EncryptedData_create(key, KU_PA_ENC_TIMESTAMP, pa_ts)
239         pa_ts = self.der_encode(pa_ts, asn1Spec=krb5_asn1.EncryptedData())
240
241         pa_ts = self.PA_DATA_create(PADATA_ENC_TIMESTAMP, pa_ts)
242
243         kdc_options = krb5_asn1.KDCOptions('forwardable')
244         padata = [pa_ts]
245
246         req = self.AS_REQ_create(padata=padata,
247                                  kdc_options=str(kdc_options),
248                                  cname=cname,
249                                  realm=realm,
250                                  sname=sname,
251                                  from_time=None,
252                                  till_time=till,
253                                  renew_time=None,
254                                  nonce=0x7fffffff,
255                                  etypes=etypes,
256                                  addresses=None,
257                                  additional_tickets=None)
258         rep = self.send_recv_transaction(req)
259         self.assertIsNotNone(rep)
260
261         msg_type = rep['msg-type']
262         self.assertEqual(msg_type, KRB_AS_REP)
263
264         enc_part = rep['enc-part']
265         enc_as_rep_part = key.decrypt(
266             KU_AS_REP_ENC_PART, rep['enc-part']['cipher'])
267         return (enc_as_rep_part, enc_part)
268
269
270 if __name__ == "__main__":
271     global_asn1_print = False
272     global_hexdump = False
273     import unittest
274     unittest.main()