f4456c3cb4db07cbfa7411f15346bdf08dbca6d1
[samba.git] / python / samba / tests / krb5 / etype_tests.py
1 #!/usr/bin/env python3
2 # Unix SMB/CIFS implementation.
3 # Copyright (C) Stefan Metzmacher 2020
4 # Copyright (C) 2022 Catalyst.Net Ltd
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 itertools
21 import sys
22 import os
23
24 from samba.dcerpc import security
25
26 from samba.tests import DynamicTestCase
27 from samba.tests.krb5.kdc_tgs_tests import KdcTgsBaseTests
28 from samba.tests.krb5.raw_testcase import KerberosCredentials
29 from samba.tests.krb5.rfc4120_constants import (
30     AES128_CTS_HMAC_SHA1_96,
31     AES256_CTS_HMAC_SHA1_96,
32     ARCFOUR_HMAC_MD5,
33     KDC_ERR_ETYPE_NOSUPP,
34 )
35
36 sys.path.insert(0, "bin/python")
37 os.environ["PYTHONUNBUFFERED"] = "1"
38
39 global_asn1_print = False
40 global_hexdump = False
41
42 rc4_bit = security.KERB_ENCTYPE_RC4_HMAC_MD5
43 aes128_bit = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
44 aes256_bit = security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
45 aes256_sk_bit = security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96_SK
46 fast_bit = security.KERB_ENCTYPE_FAST_SUPPORTED
47
48 etype_bits = rc4_bit | aes128_bit | aes256_bit
49 extra_bits = aes256_sk_bit | fast_bit
50
51
52 @DynamicTestCase
53 class EtypeTests(KdcTgsBaseTests):
54     def setUp(self):
55         super().setUp()
56         self.do_asn1_print = global_asn1_print
57         self.do_hexdump = global_hexdump
58
59         self.default_supported_enctypes = self.default_etypes
60         if self.default_supported_enctypes is None:
61             lp = self.get_lp()
62             self.default_supported_enctypes = lp.get(
63                 'kdc default domain supported enctypes')
64
65     def _server_creds(self, supported=None):
66         return self.get_cached_creds(
67             account_type=self.AccountType.COMPUTER,
68             opts={
69                 'supported_enctypes': supported,
70             })
71
72     def only_non_etype_bits_set(self, bits):
73         return bits is not None and (
74             bits & extra_bits and
75             not (bits & etype_bits))
76
77     @classmethod
78     def setUpDynamicTestCases(cls):
79         all_etypes = (AES256_CTS_HMAC_SHA1_96,
80                       AES128_CTS_HMAC_SHA1_96,
81                       ARCFOUR_HMAC_MD5)
82
83         # An iterator yielding all permutations consisting of at least one
84         # etype.
85         requested_etype_cases = itertools.chain.from_iterable(
86             itertools.permutations(all_etypes, x)
87             for x in range(1, len(all_etypes) + 1))
88
89         # Some combinations of msDS-SupportedEncryptionTypes bits to be set on
90         # the target server.
91         supported_etype_cases = (
92             # Not set.
93             None,
94             # Every possible combination of RC4, AES128, AES256, and AES256-SK.
95             0,
96             rc4_bit,
97             aes256_sk_bit,
98             aes256_sk_bit | rc4_bit,
99             aes256_bit,
100             aes256_bit | rc4_bit,
101             aes256_bit | aes256_sk_bit,
102             aes256_bit | aes256_sk_bit | rc4_bit,
103             aes128_bit,
104             aes128_bit | rc4_bit,
105             aes128_bit | aes256_sk_bit,
106             aes128_bit | aes256_sk_bit | rc4_bit,
107             aes128_bit | aes256_bit,
108             aes128_bit | aes256_bit | rc4_bit,
109             aes128_bit | aes256_bit | aes256_sk_bit,
110             aes128_bit | aes256_bit | aes256_sk_bit | rc4_bit,
111             # Some combinations with an extra bit (the FAST-supported bit) set.
112             fast_bit,
113             fast_bit | rc4_bit,
114             fast_bit | aes256_sk_bit,
115             fast_bit | aes256_bit,
116         )
117
118         for requested_etypes in requested_etype_cases:
119             for supported_etypes in supported_etype_cases:
120                 tname = (f'{supported_etypes}_supported_'
121                          f'{requested_etypes}_requested')
122                 targs = supported_etypes, requested_etypes
123                 cls.generate_dynamic_test('test_etype_as', tname, *targs)
124
125     def _test_etype_as_with_args(self, supported_bits, requested_etypes):
126         # The ticket will be encrypted with the strongest enctype for which the
127         # server explicitly declares support, falling back to RC4 if the server
128         # has no declared supported encryption types. The enctype of the
129         # session key is the first enctype listed in the request that the
130         # server supports, taking the AES-SK bit as an indication of support
131         # for both AES types.
132
133         # If none of the enctypes in the request are supported by the target
134         # server, implicitly or explicitly, return ETYPE_NOSUPP.
135
136         expected_error = 0
137
138         if not supported_bits:
139             # If msDS-SupportedEncryptionTypes is missing or set to zero, the
140             # default value, provided by smb.conf, is assumed.
141             supported_bits = self.default_supported_enctypes
142
143         # If msDS-SupportedEncryptionTypes specifies only non-etype bits, we
144         # expect an error.
145         if self.only_non_etype_bits_set(supported_bits):
146             expected_error = KDC_ERR_ETYPE_NOSUPP
147
148         virtual_bits = supported_bits
149
150         if self.forced_rc4 and not (virtual_bits & rc4_bit):
151             # If our fallback smb.conf option is set, force in RC4 support.
152             virtual_bits |= rc4_bit
153
154         if virtual_bits & aes256_sk_bit:
155             # If strong session keys are enabled, force in the AES bits.
156             virtual_bits |= aes256_bit | aes128_bit
157
158         virtual_etypes = KerberosCredentials.bits_to_etypes(virtual_bits)
159
160         # The enctype of the session key is the first listed in the request
161         # that the server supports, implicitly or explicitly.
162         for requested_etype in requested_etypes:
163             if requested_etype in virtual_etypes:
164                 expected_session_etype = requested_etype
165                 break
166         else:
167             # If there is no such enctype, expect an error.
168             expected_error = KDC_ERR_ETYPE_NOSUPP
169
170         # Get the credentials of the client and server accounts.
171         creds = self.get_client_creds()
172         target_creds = self._server_creds(supported=supported_bits)
173
174         # Perform the TGS-REQ.
175         ticket = self._as_req(creds, expected_error=expected_error,
176                               target_creds=target_creds,
177                               etype=requested_etypes)
178         if expected_error:
179             # There's no more to check. Return.
180             return
181
182         # We expect the ticket etype to be the strongest the server claims to
183         # support, with a fallback to RC4.
184         expected_etype = ARCFOUR_HMAC_MD5
185         if supported_bits is not None:
186             if supported_bits & aes256_bit:
187                 expected_etype = AES256_CTS_HMAC_SHA1_96
188             elif supported_bits & aes128_bit:
189                 expected_etype = AES128_CTS_HMAC_SHA1_96
190
191         # Check the etypes of the ticket and session key.
192         self.assertEqual(expected_etype, ticket.decryption_key.etype)
193         self.assertEqual(expected_session_etype, ticket.session_key.etype)
194
195     # Perform an AS-REQ for a service ticket, specifying AES, when the target
196     # service only supports AES. The resulting ticket should be encrypted with
197     # AES, with an AES session key.
198     def test_as_aes_supported_aes_requested(self):
199         creds = self.get_client_creds()
200         target_creds = self._server_creds(supported=aes256_bit)
201
202         ticket = self._as_req(creds, expected_error=0,
203                               target_creds=target_creds,
204                               etype=(AES256_CTS_HMAC_SHA1_96,))
205
206         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
207         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.session_key.etype)
208
209     # Perform an AS-REQ for a service ticket, specifying RC4, when the target
210     # service only supports AES. The request should fail with an error.
211     def test_as_aes_supported_rc4_requested(self):
212         creds = self.get_client_creds()
213         target_creds = self._server_creds(supported=aes256_bit)
214
215         if self.forced_rc4:
216             expected_error = 0
217             expected_session_etype = ARCFOUR_HMAC_MD5
218         else:
219             expected_error = KDC_ERR_ETYPE_NOSUPP
220             expected_session_etype = AES256_CTS_HMAC_SHA1_96
221
222         ticket = self._as_req(creds, expected_error=expected_error,
223                               target_creds=target_creds,
224                               etype=(ARCFOUR_HMAC_MD5,))
225
226         if not self.forced_rc4:
227             return
228
229         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
230         self.assertEqual(expected_session_etype, ticket.session_key.etype)
231
232     # Perform an AS-REQ for a service ticket, specifying AES, when the target
233     # service only supports AES, and supports AES256 session keys. The
234     # resulting ticket should be encrypted with AES, with an AES session key.
235     def test_as_aes_supported_aes_session_aes_requested(self):
236         creds = self.get_client_creds()
237         target_creds = self._server_creds(supported=aes256_bit | aes256_sk_bit)
238
239         ticket = self._as_req(creds, expected_error=0,
240                               target_creds=target_creds,
241                               etype=(AES256_CTS_HMAC_SHA1_96,))
242
243         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
244         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.session_key.etype)
245
246     # Perform an AS-REQ for a service ticket, specifying RC4, when the target
247     # service only supports AES, and supports AES256 session keys. The request
248     # should fail with an error.
249     def test_as_aes_supported_aes_session_rc4_requested(self):
250         creds = self.get_client_creds()
251         target_creds = self._server_creds(supported=aes256_bit | aes256_sk_bit)
252
253         if self.forced_rc4:
254             expected_error = 0
255             expected_session_etype = ARCFOUR_HMAC_MD5
256         else:
257             expected_error = KDC_ERR_ETYPE_NOSUPP
258             expected_session_etype = AES256_CTS_HMAC_SHA1_96
259
260         ticket = self._as_req(creds, expected_error=expected_error,
261                      target_creds=target_creds,
262                      etype=(ARCFOUR_HMAC_MD5,))
263
264         if not self.forced_rc4:
265             return
266
267         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
268         self.assertEqual(expected_session_etype, ticket.session_key.etype)
269
270     # Perform an AS-REQ for a service ticket, specifying AES, when the target
271     # service only supports RC4. The request should fail with an error.
272     def test_as_rc4_supported_aes_requested(self):
273         creds = self.get_client_creds()
274         target_creds = self._server_creds(supported=rc4_bit)
275
276         self._as_req(creds, expected_error=KDC_ERR_ETYPE_NOSUPP,
277                      target_creds=target_creds,
278                      etype=(AES256_CTS_HMAC_SHA1_96,))
279
280     # Perform an AS-REQ for a service ticket, specifying RC4, when the target
281     # service only supports RC4. The resulting ticket should be encrypted with
282     # RC4, with an RC4 session key.
283     def test_as_rc4_supported_rc4_requested(self):
284         creds = self.get_client_creds()
285         target_creds = self._server_creds(supported=rc4_bit)
286
287         ticket = self._as_req(creds, expected_error=0,
288                               target_creds=target_creds,
289                               etype=(ARCFOUR_HMAC_MD5,))
290
291         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.decryption_key.etype)
292         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
293
294     # Perform an AS-REQ for a service ticket, specifying AES, when the target
295     # service only supports RC4, but supports AES256 session keys. The
296     # resulting ticket should be encrypted with RC4, with an AES256 session
297     # key.
298     def test_as_rc4_supported_aes_session_aes_requested(self):
299         creds = self.get_client_creds()
300         target_creds = self._server_creds(supported=rc4_bit | aes256_sk_bit)
301
302         ticket = self._as_req(creds, expected_error=0,
303                               target_creds=target_creds,
304                               etype=(AES256_CTS_HMAC_SHA1_96,))
305
306         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.decryption_key.etype)
307         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.session_key.etype)
308
309     # Perform an AS-REQ for a service ticket, specifying RC4, when the target
310     # service only supports RC4, but supports AES256 session keys. The
311     # resulting ticket should be encrypted with RC4, with an RC4 session key.
312     def test_as_rc4_supported_aes_session_rc4_requested(self):
313         creds = self.get_client_creds()
314         target_creds = self._server_creds(supported=rc4_bit | aes256_sk_bit)
315
316         ticket = self._as_req(creds, expected_error=0,
317                               target_creds=target_creds,
318                               etype=(ARCFOUR_HMAC_MD5,))
319
320         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.decryption_key.etype)
321         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
322
323     # Perform a TGS-REQ for a service ticket, specifying AES, when the target
324     # service only supports AES. The resulting ticket should be encrypted with
325     # AES, with an AES session key.
326     def test_tgs_aes_supported_aes_requested(self):
327         creds = self.get_client_creds()
328         tgt = self.get_tgt(creds)
329
330         target_creds = self._server_creds(supported=aes256_bit)
331
332         ticket = self._tgs_req(tgt, expected_error=0,
333                                target_creds=target_creds,
334                                etypes=(AES256_CTS_HMAC_SHA1_96,))
335
336         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
337         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.session_key.etype)
338
339     # Perform a TGS-REQ for a service ticket, specifying RC4, when the target
340     # service only supports AES. The request should fail with an error.
341     def test_tgs_aes_supported_rc4_requested(self):
342         creds = self.get_client_creds()
343         tgt = self.get_tgt(creds)
344
345         target_creds = self._server_creds(supported=aes256_bit)
346
347         if self.forced_rc4:
348             expected_error = 0
349         else:
350             expected_error = KDC_ERR_ETYPE_NOSUPP
351
352         ticket = self._tgs_req(tgt, expected_error=expected_error,
353                                target_creds=target_creds,
354                                etypes=(ARCFOUR_HMAC_MD5,))
355
356         if not self.forced_rc4:
357             return
358
359         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
360         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
361
362     # Perform a TGS-REQ for a service ticket, specifying AES, when the target
363     # service only supports AES, and supports AES256 session keys. The
364     # resulting ticket should be encrypted with AES, with an AES session key.
365     def test_tgs_aes_supported_aes_session_aes_requested(self):
366         creds = self.get_client_creds()
367         tgt = self.get_tgt(creds)
368
369         target_creds = self._server_creds(supported=aes256_bit | aes256_sk_bit)
370
371         ticket = self._tgs_req(tgt, expected_error=0,
372                                target_creds=target_creds,
373                                etypes=(AES256_CTS_HMAC_SHA1_96,))
374
375         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
376         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.session_key.etype)
377
378     # Perform a TGS-REQ for a service ticket, specifying RC4, when the target
379     # service only supports AES, and supports AES256 session keys. The request
380     # should fail with an error.
381     def test_tgs_aes_supported_aes_session_rc4_requested(self):
382         creds = self.get_client_creds()
383         tgt = self.get_tgt(creds)
384
385         target_creds = self._server_creds(supported=aes256_bit | aes256_sk_bit)
386
387         if self.forced_rc4:
388             expected_error = 0
389         else:
390             expected_error = KDC_ERR_ETYPE_NOSUPP
391
392         ticket = self._tgs_req(tgt, expected_error=expected_error,
393                                target_creds=target_creds,
394                                etypes=(ARCFOUR_HMAC_MD5,))
395
396         if not self.forced_rc4:
397             return
398
399         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.decryption_key.etype)
400         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
401
402     # Perform a TGS-REQ for a service ticket, specifying AES, when the target
403     # service only supports RC4. The request should fail with an error.
404     def test_tgs_rc4_supported_aes_requested(self):
405         creds = self.get_client_creds()
406         tgt = self.get_tgt(creds)
407
408         target_creds = self._server_creds(supported=rc4_bit)
409
410         self._tgs_req(tgt, expected_error=KDC_ERR_ETYPE_NOSUPP,
411                       target_creds=target_creds,
412                       etypes=(AES256_CTS_HMAC_SHA1_96,))
413
414     # Perform a TGS-REQ for a service ticket, specifying RC4, when the target
415     # service only supports RC4. The resulting ticket should be encrypted with
416     # RC4, with an RC4 session key.
417     def test_tgs_rc4_supported_rc4_requested(self):
418         creds = self.get_client_creds()
419         tgt = self.get_tgt(creds)
420
421         target_creds = self._server_creds(supported=rc4_bit)
422
423         ticket = self._tgs_req(tgt, expected_error=0,
424                                target_creds=target_creds,
425                                etypes=(ARCFOUR_HMAC_MD5,))
426
427         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.decryption_key.etype)
428         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
429
430     # Perform a TGS-REQ for a service ticket, specifying AES, when the target
431     # service only supports RC4, but supports AES256 session keys. The
432     # resulting ticket should be encrypted with RC4, with an AES256 session
433     # key.
434     def test_tgs_rc4_supported_aes_session_aes_requested(self):
435         creds = self.get_client_creds()
436         tgt = self.get_tgt(creds)
437
438         target_creds = self._server_creds(supported=rc4_bit | aes256_sk_bit)
439
440         ticket = self._tgs_req(tgt, expected_error=0,
441                                target_creds=target_creds,
442                                etypes=(AES256_CTS_HMAC_SHA1_96,))
443
444         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.decryption_key.etype)
445         self.assertEqual(AES256_CTS_HMAC_SHA1_96, ticket.session_key.etype)
446
447     # Perform a TGS-REQ for a service ticket, specifying RC4, when the target
448     # service only supports RC4, but supports AES256 session keys. The
449     # resulting ticket should be encrypted with RC4, with an RC4 session key.
450     def test_tgs_rc4_supported_aes_session_rc4_requested(self):
451         creds = self.get_client_creds()
452         tgt = self.get_tgt(creds)
453
454         target_creds = self._server_creds(supported=rc4_bit | aes256_sk_bit)
455
456         ticket = self._tgs_req(tgt, expected_error=0,
457                                target_creds=target_creds,
458                                etypes=(ARCFOUR_HMAC_MD5,))
459
460         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.decryption_key.etype)
461         self.assertEqual(ARCFOUR_HMAC_MD5, ticket.session_key.etype)
462
463
464 if __name__ == "__main__":
465     global_asn1_print = False
466     global_hexdump = False
467     import unittest
468     unittest.main()