2 # Unix SMB/CIFS implementation.
3 # Copyright (C) Catalyst.Net Ltd 2023
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <https://www.gnu.org/licenses/>.
22 sys.path.insert(0, "bin/python")
23 os.environ["PYTHONUNBUFFERED"] = "1"
27 from typing import ClassVar, Optional
29 from samba.dcerpc import gkdi, misc
30 from samba.gkdi import (
40 from samba.hresult import HRES_E_INVALIDARG, HRES_NTE_BAD_KEY, HRES_NTE_NO_KEY
41 from samba.nt_time import timedelta_from_nt_time_delta
43 from samba.tests.gkdi import GetKeyError, GkdiBaseTest, ROOT_KEY_START_TIME
44 from samba.tests.krb5.kdc_base_test import KDCBaseTest
47 class GkdiKdcBaseTest(GkdiBaseTest, KDCBaseTest):
48 def new_root_key(self, *args, **kwargs) -> misc.GUID:
49 samdb = self.get_samdb()
50 domain_dn = self.get_server_dn(samdb)
51 return self.create_root_key(samdb, domain_dn, *args, **kwargs)
53 def gkdi_conn(self) -> gkdi.gkdi:
54 # The seed keys used by Group Managed Service Accounts correspond to the
55 # Enterprise DCs SID (S-1-5-9); as such they can be retrieved only by
57 return self.gkdi_connect(
60 self.get_cached_creds(account_type=self.AccountType.SERVER),
63 def check_rpc_get_key(
64 self, root_key_id: Optional[misc.GUID], gkid: Gkid
66 got_key_pair = self.rpc_get_key(
67 self.gkdi_conn(), self.gmsa_sd, root_key_id, gkid
69 expected_key_pair = self.get_key(
74 root_key_id_hint=got_key_pair.root_key_id if root_key_id is None else None,
77 got_key_pair.root_key_id,
78 expected_key_pair.root_key_id,
79 "root key IDs must match",
81 self.assertEqual(got_key_pair, expected_key_pair, "key pairs must match")
86 class GkdiExplicitRootKeyTests(GkdiKdcBaseTest):
87 def test_current_l0_idx(self):
88 """Request a key with the current L0 index. This index is regularly
89 incremented every 427 days or so."""
90 root_key_id = self.new_root_key()
92 # It actually doesn’t matter what we specify for the L1 and L2 indices.
93 # We’ll get the same result regardless — they just cannot specify a key
95 current_gkid = self.current_gkid(self.get_samdb())
96 key = self.check_rpc_get_key(root_key_id, current_gkid)
98 self.assertEqual(current_gkid, key.gkid)
99 self.assertEqual(root_key_id, key.root_key_id)
101 def test_previous_l0_idx(self):
102 """Request a key with a previous L0 index."""
103 root_key_id = self.new_root_key(use_start_time=ROOT_KEY_START_TIME)
105 # It actually doesn’t matter what we specify for the L1 and L2 indices.
106 # We’ll get the same result regardless.
107 previous_l0_idx = self.current_gkid(self.get_samdb()).l0_idx - 1
108 key = self.check_rpc_get_key(root_key_id, Gkid(previous_l0_idx, 0, 0))
110 # Expect to get an L1 seed key.
111 self.assertIsNotNone(key.l1_key)
112 self.assertIsNone(key.l2_key)
113 self.assertEqual(Gkid(previous_l0_idx, 31, 31), key.gkid)
114 self.assertEqual(root_key_id, key.root_key_id)
116 def test_algorithm_sha1(self):
117 """Test with the SHA1 algorithm."""
118 key = self.check_rpc_get_key(
119 self.new_root_key(hash_algorithm=Algorithm.SHA1),
120 self.current_gkid(self.get_samdb()),
122 self.assertIs(Algorithm.SHA1, key.hash_algorithm)
124 def test_algorithm_sha256(self):
125 """Test with the SHA256 algorithm."""
126 key = self.check_rpc_get_key(
127 self.new_root_key(hash_algorithm=Algorithm.SHA256),
128 self.current_gkid(self.get_samdb()),
130 self.assertIs(Algorithm.SHA256, key.hash_algorithm)
132 def test_algorithm_sha384(self):
133 """Test with the SHA384 algorithm."""
134 key = self.check_rpc_get_key(
135 self.new_root_key(hash_algorithm=Algorithm.SHA384),
136 self.current_gkid(self.get_samdb()),
138 self.assertIs(Algorithm.SHA384, key.hash_algorithm)
140 def test_algorithm_sha512(self):
141 """Test with the SHA512 algorithm."""
142 key = self.check_rpc_get_key(
143 self.new_root_key(hash_algorithm=Algorithm.SHA512),
144 self.current_gkid(self.get_samdb()),
146 self.assertIs(Algorithm.SHA512, key.hash_algorithm)
148 def test_algorithm_none(self):
149 """Test without a specified algorithm."""
150 key = self.check_rpc_get_key(
151 self.new_root_key(hash_algorithm=None),
152 self.current_gkid(self.get_samdb()),
154 self.assertIs(Algorithm.SHA256, key.hash_algorithm)
156 def test_future_key(self):
157 """Try to request a key from the future."""
158 root_key_id = self.new_root_key(use_start_time=ROOT_KEY_START_TIME)
160 future_gkid = self.current_gkid(
162 offset=timedelta_from_nt_time_delta(
163 NtTimeDelta(KEY_CYCLE_DURATION + MAX_CLOCK_SKEW)
167 with self.assertRaises(GetKeyError) as err:
168 self.get_key(self.get_samdb(), self.gmsa_sd, root_key_id, future_gkid)
172 err.exception.args[0],
173 "requesting a key from the future should fail with INVALID_PARAMETER",
176 with self.assertRaises(GetKeyError) as rpc_err:
177 self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, root_key_id, future_gkid)
181 rpc_err.exception.args[0],
182 "requesting a key from the future should fail with INVALID_PARAMETER",
185 def test_root_key_use_start_time_zero(self):
186 """Attempt to use a root key with an effective time of zero."""
187 root_key_id = self.new_root_key(use_start_time=NtTime(0))
189 gkid = self.current_gkid(self.get_samdb())
191 with self.assertRaises(GetKeyError) as err:
192 self.get_key(self.get_samdb(), self.gmsa_sd, root_key_id, gkid)
196 err.exception.args[0],
197 "using a root key with an effective time of zero should fail with BAD_KEY",
200 with self.assertRaises(GetKeyError) as rpc_err:
201 self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, root_key_id, gkid)
205 rpc_err.exception.args[0],
206 "using a root key with an effective time of zero should fail with BAD_KEY",
209 def test_root_key_use_start_time_too_low(self):
210 """Attempt to use a root key with an effective time set too low."""
211 root_key_id = self.new_root_key(use_start_time=NtTime(ROOT_KEY_START_TIME - 1))
213 gkid = self.current_gkid(self.get_samdb())
215 with self.assertRaises(GetKeyError) as err:
216 self.get_key(self.get_samdb(), self.gmsa_sd, root_key_id, gkid)
220 err.exception.args[0],
221 "using a root key with too low effective time should fail with"
222 " INVALID_PARAMETER",
225 with self.assertRaises(GetKeyError) as rpc_err:
226 self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, root_key_id, gkid)
230 rpc_err.exception.args[0],
231 "using a root key with too low effective time should fail with"
232 " INVALID_PARAMETER",
235 def test_before_valid(self):
236 """Attempt to use a key before it is valid."""
237 gkid = self.current_gkid(self.get_samdb())
238 valid_start_time = NtTime(
239 gkid.start_nt_time() + KEY_CYCLE_DURATION + MAX_CLOCK_SKEW
242 # Using a valid root key is allowed.
243 valid_root_key_id = self.new_root_key(use_start_time=valid_start_time)
244 self.check_rpc_get_key(valid_root_key_id, gkid)
246 # But attempting to use a root key that is not yet valid will result in
247 # an INVALID_PARAMETER error.
248 invalid_root_key_id = self.new_root_key(use_start_time=valid_start_time + 1)
250 with self.assertRaises(GetKeyError) as err:
251 self.get_key(self.get_samdb(), self.gmsa_sd, invalid_root_key_id, gkid)
255 err.exception.args[0],
256 "using a key before it is valid should fail with INVALID_PARAMETER",
259 with self.assertRaises(GetKeyError) as rpc_err:
260 self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, invalid_root_key_id, gkid)
264 rpc_err.exception.args[0],
265 "using a key before it is valid should fail with INVALID_PARAMETER",
268 def test_non_existent_root_key(self):
269 """Attempt to use a non‐existent root key."""
270 root_key_id = misc.GUID(secrets.token_bytes(16))
272 gkid = self.current_gkid(self.get_samdb())
274 with self.assertRaises(GetKeyError) as err:
275 self.get_key(self.get_samdb(), self.gmsa_sd, root_key_id, gkid)
279 err.exception.args[0],
280 "using a non‐existent root key should fail with NO_KEY",
283 with self.assertRaises(GetKeyError) as rpc_err:
284 self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, root_key_id, gkid)
288 rpc_err.exception.args[0],
289 "using a non‐existent root key should fail with NO_KEY",
292 def test_root_key_wrong_length(self):
293 """Attempt to use a root key that is the wrong length."""
294 root_key_id = self.new_root_key(data=bytes(KEY_LEN_BYTES // 2))
296 gkid = self.current_gkid(self.get_samdb())
298 with self.assertRaises(GetKeyError) as err:
299 self.get_key(self.get_samdb(), self.gmsa_sd, root_key_id, gkid)
303 err.exception.args[0],
304 "using a root key that is the wrong length should fail with BAD_KEY",
307 with self.assertRaises(GetKeyError) as rpc_err:
308 self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, root_key_id, gkid)
312 rpc_err.exception.args[0],
313 "using a root key that is the wrong length should fail with BAD_KEY",
317 class GkdiImplicitRootKeyTests(GkdiKdcBaseTest):
318 _root_key: ClassVar[misc.GUID]
321 def setUpClass(cls) -> None:
326 def setUp(self) -> None:
329 if self._root_key is None:
330 # GKDI requires a root key to operate. Creating a root key here
331 # saves creating one before every test.
333 # We cannot delete this key after the tests have run, as Windows
334 # might have decided to cache it to be used in subsequent runs. It
335 # will keep a root key cached even if the corresponding AD object
336 # has been deleted, leading to various problems later.
338 cls._root_key = self.new_root_key(use_start_time=ROOT_KEY_START_TIME)
340 def test_l1_seed_key(self):
341 """Request a key and expect to receive an L1 seed key."""
342 gkid = Gkid(300, 0, 31)
343 key = self.check_rpc_get_key(None, gkid)
345 # Expect to get an L1 seed key.
346 self.assertIsNotNone(key.l1_key)
347 self.assertIsNone(key.l2_key)
348 self.assertEqual(gkid, key.gkid)
350 def test_l2_seed_key(self):
351 """Request a key and expect to receive an L2 seed key."""
352 gkid = Gkid(300, 0, 0)
353 key = self.check_rpc_get_key(None, gkid)
355 # Expect to get an L2 seed key.
356 self.assertIsNone(key.l1_key)
357 self.assertIsNotNone(key.l2_key)
358 self.assertEqual(gkid, key.gkid)
360 def test_both_seed_keys(self):
361 """Request a key and expect to receive L1 and L2 seed keys."""
362 gkid = Gkid(300, 1, 0)
363 key = self.check_rpc_get_key(None, gkid)
365 # Expect to get both L1 and L2 seed keys.
366 self.assertIsNotNone(key.l1_key)
367 self.assertIsNotNone(key.l2_key)
368 self.assertEqual(gkid, key.gkid)
370 def test_both_seed_keys_no_hint(self):
371 """Request a key, but don’t specify ‘root_key_id_hint’."""
372 gkid = Gkid(300, 1, 0)
380 # Expect to get both L1 and L2 seed keys.
381 self.assertIsNotNone(key.l1_key)
382 self.assertIsNotNone(key.l2_key)
383 self.assertEqual(gkid, key.gkid)
385 def test_request_l0_seed_key(self):
386 """Attempt to request an L0 seed key."""
387 gkid = Gkid.l0_seed_key(300)
389 with self.assertRaises(GetKeyError) as err:
390 self.get_key(self.get_samdb(), self.gmsa_sd, None, gkid)
394 err.exception.args[0],
395 "requesting an L0 seed key should fail with INVALID_PARAMETER",
398 with self.assertRaises(GetKeyError) as rpc_err:
399 self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, None, gkid)
403 rpc_err.exception.args[0],
404 "requesting an L0 seed key should fail with INVALID_PARAMETER",
407 def test_request_l1_seed_key(self):
408 """Attempt to request an L1 seed key."""
409 gkid = Gkid.l1_seed_key(300, 0)
411 with self.assertRaises(GetKeyError) as err:
412 self.get_key(self.get_samdb(), self.gmsa_sd, None, gkid)
416 err.exception.args[0],
417 "requesting an L1 seed key should fail with INVALID_PARAMETER",
420 with self.assertRaises(GetKeyError) as rpc_err:
421 self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, None, gkid)
425 rpc_err.exception.args[0],
426 "requesting an L1 seed key should fail with INVALID_PARAMETER",
429 def test_request_default_seed_key(self):
430 """Try to make a request with the default GKID."""
431 gkid = Gkid.default()
442 self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, None, gkid)
445 class GkdiSelfTests(GkdiKdcBaseTest):
446 def test_current_l0_idx_l1_seed_key(self):
447 """Request a key with the current L0 index, expecting to receive an L1
449 root_key_id = self.new_root_key(
450 use_start_time=ROOT_KEY_START_TIME,
451 hash_algorithm=Algorithm.SHA512,
452 guid=misc.GUID("89f70521-9d66-441f-c314-1b462f9b1052"),
454 "a6ef87dbbbf86b6bbe55750b941f13ca99efe5185e2e2bded5b838d8a0e77647"
455 "0537e68cae45a7a0f4b1d6c9bf5494c3f879e172e326557cdbb6a56e8799a722"
459 current_gkid = Gkid(255, 24, 31)
465 current_gkid=current_gkid,
468 # Expect to get an L1 seed key.
469 self.assertEqual(current_gkid, key.gkid)
470 self.assertEqual(root_key_id, key.root_key_id)
471 self.assertEqual(Algorithm.SHA512, key.hash_algorithm)
474 "bd538a073490f3cf9451c933025de9b22c97eaddaffa94b379e2b919a4bed147"
475 "5bc67f6a9175b139c69204c57d4300a0141ffe34d12ced84614593b1aa13af1c"
479 self.assertIsNone(key.l2_key)
481 def test_current_l0_idx_l2_seed_key(self):
482 """Request a key with the current L0 index, expecting to receive an L2
484 root_key_id = self.new_root_key(
485 use_start_time=ROOT_KEY_START_TIME,
486 hash_algorithm=Algorithm.SHA512,
487 guid=misc.GUID("1a3d6c30-aa81-cb7f-d3fe-80775d135dfe"),
489 "dfd95be3153a0805c65694e7d284aace5ab0aa493350025eb8dbc6df0b4e9256"
490 "fb4cbfbe6237ce3732694e2608760076b67082d39abd3c0fedba1b8873645064"
494 current_gkid = Gkid(321, 0, 12)
500 current_gkid=current_gkid,
503 # Expect to get an L2 seed key.
504 self.assertEqual(current_gkid, key.gkid)
505 self.assertEqual(root_key_id, key.root_key_id)
506 self.assertEqual(Algorithm.SHA512, key.hash_algorithm)
507 self.assertIsNone(key.l1_key)
510 "bbbd9376cd16c247ed40f5912d1908218c08f0915bae02fe02cbfb3753bde406"
511 "f9c553acd95143cf63906a0440e3cf237d2335ae4e4b9cd2d946a71351ebcb7b"
516 def test_current_l0_idx_both_seed_keys(self):
517 """Request a key with the current L0 index, expecting to receive L1 and
519 root_key_id = self.new_root_key(
520 use_start_time=ROOT_KEY_START_TIME,
521 hash_algorithm=Algorithm.SHA512,
522 guid=misc.GUID("09de0b38-c743-7abf-44ea-7a3c3e404314"),
524 "d5912d0eb3bd60e1371b1e525dd83be7fc5baf77018b0dba6bd948b7a98ebe5a"
525 "f37674332506a46c52c108a62f2a3e89251ad1bde6d539004679c0658853bb68"
529 current_gkid = Gkid(123, 21, 0)
535 current_gkid=current_gkid,
538 # Expect to get both L1 and L2 seed keys.
539 self.assertEqual(current_gkid, key.gkid)
540 self.assertEqual(root_key_id, key.root_key_id)
541 self.assertEqual(Algorithm.SHA512, key.hash_algorithm)
544 "b1f7c5896e7dc791d9c0aaf8ca7dbab8c172a4f8b873db488a3c4cbd0f559b11"
545 "52ffba39d4aff2d9e8aada90b27a3c94a5af996f4b8f584a4f37ccab4d505d3d"
551 "133c9bbd20d9227aeb38dfcd3be6bcbfc5983ba37202088ff5c8a70511214506"
552 "a69c195a8807cd844bcb955e9569c8e4d197759f28577cc126d15f16a7da4ee0"
557 def test_previous_l0_idx(self):
558 """Request a key with a previous L0 index."""
559 root_key_id = self.new_root_key(
560 use_start_time=ROOT_KEY_START_TIME,
561 hash_algorithm=Algorithm.SHA512,
562 guid=misc.GUID("27136e8f-e093-6fe3-e57f-1d915b102e1c"),
564 "b41118c60a19cafa5ecf858d1a2a2216527b2daedf386e9d599e42a46add6c7d"
565 "c93868619761c880ff3674a77c6e5fbf3434d130a9727bb2cd2a2557bdcfc752"
574 current_gkid=Gkid(101, 2, 3),
577 # Expect to get an L1 seed key.
578 self.assertEqual(Gkid(100, 31, 31), key.gkid)
579 self.assertEqual(root_key_id, key.root_key_id)
580 self.assertEqual(Algorithm.SHA512, key.hash_algorithm)
583 "935cbdc06198eb28fa44b8d8278f51072c4613999236585041ede8e72d02fe95"
584 "e3454f046382cbc0a700779b79474dd7e080509d76302d2937407e96e3d3d022"
588 self.assertIsNone(key.l2_key)
591 """Request a key derived with SHA1."""
592 root_key_id = self.new_root_key(
593 use_start_time=ROOT_KEY_START_TIME,
594 hash_algorithm=Algorithm.SHA1,
595 guid=misc.GUID("970abad6-fe55-073a-caf1-b801d3f26bd3"),
597 "3bed03bf0fb7d4013149154f24ca2d59b98db6d588cb1f54eca083855e25eb28"
598 "d3562a01adc78c4b70e0b72a59515863e7732b853fba02dd7646e63108441211"
602 current_gkid = Gkid(1, 2, 3)
608 current_gkid=current_gkid,
611 # Expect to get both L1 and L2 seed keys.
612 self.assertEqual(current_gkid, key.gkid)
613 self.assertEqual(root_key_id, key.root_key_id)
614 self.assertEqual(Algorithm.SHA1, key.hash_algorithm)
617 "576cb68f2e52eb739f817b488c3590d86f1c2c365f3fc9201d9c7fee7494853d"
618 "58746ee13e48f18aa6fa69f7157de3d07de34e13836792b7c088ffb6914a89c2"
624 "3ffb825adaf116b6533207d568a30ed3d3f21c68840941c9456684f9afa11b05"
625 "6e0c59391b4d88c495d984c3d680029cc5c594630f34179119c1c5acaae5e90e"
630 def test_sha256(self):
631 """Request a key derived with SHA256."""
632 root_key_id = self.new_root_key(
633 use_start_time=ROOT_KEY_START_TIME,
634 hash_algorithm=Algorithm.SHA256,
635 guid=misc.GUID("45e26207-ed33-dcd5-925a-518a0deef69e"),
637 "28b5b6503d3c1d24814de781bb7bfce3ef69eed1ce4809372bee2c506270c5f0"
638 "b5c6df597472623f256c86daa0991e8a11a1705f21b2cfdc0bb9db4ba23246a2"
642 current_gkid = Gkid(222, 22, 22)
648 current_gkid=current_gkid,
651 # Expect to get both L1 and L2 seed keys.
652 self.assertEqual(current_gkid, key.gkid)
653 self.assertEqual(root_key_id, key.root_key_id)
654 self.assertEqual(Algorithm.SHA256, key.hash_algorithm)
657 "57aced6e75f83f3af4f879b38b60f090b42e4bfa022fae3e6fd94280b469b0ec"
658 "15d8b853a870b5fbdf28708cce19273b74a573acbe0deda8ef515db4691e2dcb"
664 "752a0879ae2424c0504c7493599f13e588e1bbdc252f83325ad5b1fb91c24c89"
665 "01d440f3ff9ffba59fcd65bb975732d9f383dd50b898174bb9393e383d25d540"
670 def test_sha384(self):
671 """Request a key derived with SHA384."""
672 root_key_id = self.new_root_key(
673 use_start_time=ROOT_KEY_START_TIME,
674 hash_algorithm=Algorithm.SHA384,
675 guid=misc.GUID("66e6d9f7-4924-f3fc-fe34-605634d42ebd"),
677 "23e5ba86cbd88f7b432ee66dbb03bf4eebf401cbfc3df735d4d728b503c87f84"
678 "3207c6f6153f190dfe85a86cb8d8b74df13b25305981be8d7e29c96ee54c9630"
682 current_gkid = Gkid(287, 28, 27)
688 current_gkid=current_gkid,
691 # Expect to get both L1 and L2 seed keys.
692 self.assertEqual(current_gkid, key.gkid)
693 self.assertEqual(root_key_id, key.root_key_id)
694 self.assertEqual(Algorithm.SHA384, key.hash_algorithm)
697 "fabadd7a9a63df57d6832df7a735aebb6e181888b2eaf301a2e4ff9a70246d38"
698 "ab1d2416325bf3eb726a0267bab4bd950c7291f05ea5f17197ece56992af3eb8"
704 "ec1c65634b5694818e1d341da9996db8f2a1ef6a2c776a7126a7ebd18b37a073"
705 "afdac44c41b167b14e4b872d485bbb6d7b70964215d0e84a2ff142a9d943f205"
710 def test_derive_key_exact(self):
711 """Derive a key at an exact GKID."""
712 root_key_id = self.new_root_key(
713 use_start_time=ROOT_KEY_START_TIME,
714 hash_algorithm=Algorithm.SHA512,
715 guid=misc.GUID("d95fb06f-5a9c-1829-e20d-27f3f2ecfbeb"),
717 "489f3531c537774d432d6b97e3bc1f43d2e8c6dc17eb0e4fd9a0870d2f1ebf92"
718 "e2496668a8b5bd11aea2d32d0aab716f48fe569f5c9b50ff3f9bf5deaea572fb"
722 gkid = Gkid(333, 22, 11)
723 key = self.get_key_exact(
728 current_gkid=self.current_gkid(self.get_samdb()),
731 self.assertEqual(gkid, key.gkid)
732 self.assertEqual(root_key_id, key.root_key_id)
733 self.assertEqual(Algorithm.SHA512, key.hash_algorithm)
736 "d6ab3b14f4f4c8908aa3464011b39f10a8bfadb9974af90f7d9a9fede2fdc6e5"
737 "f68a628ec00f9994a3abd8a52ae9e2db4f68e83648311e9d7765f2535515b5e2"
743 if __name__ == "__main__":