tests/gkdi: Allow current time to be overridden
[asn/samba-autobuild/.git] / python / samba / tests / krb5 / gkdi_tests.py
1 #!/usr/bin/env python3
2 # Unix SMB/CIFS implementation.
3 # Copyright (C) Catalyst.Net Ltd 2023
4 #
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.
9 #
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.
14 #
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/>.
17 #
18
19 import sys
20 import os
21
22 sys.path.insert(0, "bin/python")
23 os.environ["PYTHONUNBUFFERED"] = "1"
24
25 import secrets
26
27 from typing import ClassVar, Optional
28
29 from samba.dcerpc import gkdi, misc
30 from samba.gkdi import (
31     Algorithm,
32     Gkid,
33     KEY_CYCLE_DURATION,
34     KEY_LEN_BYTES,
35     MAX_CLOCK_SKEW,
36     NtTime,
37     NtTimeDelta,
38     SeedKeyPair,
39 )
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
42
43 from samba.tests.gkdi import GetKeyError, GkdiBaseTest, ROOT_KEY_START_TIME
44 from samba.tests.krb5.kdc_base_test import KDCBaseTest
45
46
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)
52
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
56         # server accounts.
57         return self.gkdi_connect(
58             self.dc_host,
59             self.get_lp(),
60             self.get_cached_creds(account_type=self.AccountType.SERVER),
61         )
62
63     def check_rpc_get_key(
64         self, root_key_id: Optional[misc.GUID], gkid: Gkid
65     ) -> SeedKeyPair:
66         got_key_pair = self.rpc_get_key(
67             self.gkdi_conn(), self.gmsa_sd, root_key_id, gkid
68         )
69         expected_key_pair = self.get_key(
70             self.get_samdb(),
71             self.gmsa_sd,
72             root_key_id,
73             gkid,
74             root_key_id_hint=got_key_pair.root_key_id if root_key_id is None else None,
75         )
76         self.assertEqual(
77             got_key_pair.root_key_id,
78             expected_key_pair.root_key_id,
79             "root key IDs must match",
80         )
81         self.assertEqual(got_key_pair, expected_key_pair, "key pairs must match")
82
83         return got_key_pair
84
85
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()
91
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
94         # from the future.
95         current_gkid = self.current_gkid(self.get_samdb())
96         key = self.check_rpc_get_key(root_key_id, current_gkid)
97
98         self.assertEqual(current_gkid, key.gkid)
99         self.assertEqual(root_key_id, key.root_key_id)
100
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)
104
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))
109
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)
115
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()),
121         )
122         self.assertIs(Algorithm.SHA1, key.hash_algorithm)
123
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()),
129         )
130         self.assertIs(Algorithm.SHA256, key.hash_algorithm)
131
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()),
137         )
138         self.assertIs(Algorithm.SHA384, key.hash_algorithm)
139
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()),
145         )
146         self.assertIs(Algorithm.SHA512, key.hash_algorithm)
147
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()),
153         )
154         self.assertIs(Algorithm.SHA256, key.hash_algorithm)
155
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)
159
160         future_gkid = self.current_gkid(
161             self.get_samdb(),
162             offset=timedelta_from_nt_time_delta(
163                 NtTimeDelta(KEY_CYCLE_DURATION + MAX_CLOCK_SKEW)
164             )
165         )
166
167         with self.assertRaises(GetKeyError) as err:
168             self.get_key(self.get_samdb(), self.gmsa_sd, root_key_id, future_gkid)
169
170         self.assertEqual(
171             HRES_E_INVALIDARG,
172             err.exception.args[0],
173             "requesting a key from the future should fail with INVALID_PARAMETER",
174         )
175
176         with self.assertRaises(GetKeyError) as rpc_err:
177             self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, root_key_id, future_gkid)
178
179         self.assertEqual(
180             HRES_E_INVALIDARG,
181             rpc_err.exception.args[0],
182             "requesting a key from the future should fail with INVALID_PARAMETER",
183         )
184
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))
188
189         gkid = self.current_gkid(self.get_samdb())
190
191         with self.assertRaises(GetKeyError) as err:
192             self.get_key(self.get_samdb(), self.gmsa_sd, root_key_id, gkid)
193
194         self.assertEqual(
195             HRES_NTE_BAD_KEY,
196             err.exception.args[0],
197             "using a root key with an effective time of zero should fail with BAD_KEY",
198         )
199
200         with self.assertRaises(GetKeyError) as rpc_err:
201             self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, root_key_id, gkid)
202
203         self.assertEqual(
204             HRES_NTE_BAD_KEY,
205             rpc_err.exception.args[0],
206             "using a root key with an effective time of zero should fail with BAD_KEY",
207         )
208
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))
212
213         gkid = self.current_gkid(self.get_samdb())
214
215         with self.assertRaises(GetKeyError) as err:
216             self.get_key(self.get_samdb(), self.gmsa_sd, root_key_id, gkid)
217
218         self.assertEqual(
219             HRES_E_INVALIDARG,
220             err.exception.args[0],
221             "using a root key with too low effective time should fail with"
222             " INVALID_PARAMETER",
223         )
224
225         with self.assertRaises(GetKeyError) as rpc_err:
226             self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, root_key_id, gkid)
227
228         self.assertEqual(
229             HRES_E_INVALIDARG,
230             rpc_err.exception.args[0],
231             "using a root key with too low effective time should fail with"
232             " INVALID_PARAMETER",
233         )
234
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
240         )
241
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)
245
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)
249
250         with self.assertRaises(GetKeyError) as err:
251             self.get_key(self.get_samdb(), self.gmsa_sd, invalid_root_key_id, gkid)
252
253         self.assertEqual(
254             HRES_E_INVALIDARG,
255             err.exception.args[0],
256             "using a key before it is valid should fail with INVALID_PARAMETER",
257         )
258
259         with self.assertRaises(GetKeyError) as rpc_err:
260             self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, invalid_root_key_id, gkid)
261
262         self.assertEqual(
263             HRES_E_INVALIDARG,
264             rpc_err.exception.args[0],
265             "using a key before it is valid should fail with INVALID_PARAMETER",
266         )
267
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))
271
272         gkid = self.current_gkid(self.get_samdb())
273
274         with self.assertRaises(GetKeyError) as err:
275             self.get_key(self.get_samdb(), self.gmsa_sd, root_key_id, gkid)
276
277         self.assertEqual(
278             HRES_NTE_NO_KEY,
279             err.exception.args[0],
280             "using a non‐existent root key should fail with NO_KEY",
281         )
282
283         with self.assertRaises(GetKeyError) as rpc_err:
284             self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, root_key_id, gkid)
285
286         self.assertEqual(
287             HRES_NTE_NO_KEY,
288             rpc_err.exception.args[0],
289             "using a non‐existent root key should fail with NO_KEY",
290         )
291
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))
295
296         gkid = self.current_gkid(self.get_samdb())
297
298         with self.assertRaises(GetKeyError) as err:
299             self.get_key(self.get_samdb(), self.gmsa_sd, root_key_id, gkid)
300
301         self.assertEqual(
302             HRES_NTE_BAD_KEY,
303             err.exception.args[0],
304             "using a root key that is the wrong length should fail with BAD_KEY",
305         )
306
307         with self.assertRaises(GetKeyError) as rpc_err:
308             self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, root_key_id, gkid)
309
310         self.assertEqual(
311             HRES_NTE_BAD_KEY,
312             rpc_err.exception.args[0],
313             "using a root key that is the wrong length should fail with BAD_KEY",
314         )
315
316
317 class GkdiImplicitRootKeyTests(GkdiKdcBaseTest):
318     _root_key: ClassVar[misc.GUID]
319
320     @classmethod
321     def setUpClass(cls) -> None:
322         super().setUpClass()
323
324         cls._root_key = None
325
326     def setUp(self) -> None:
327         super().setUp()
328
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.
332             #
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.
337             cls = type(self)
338             cls._root_key = self.new_root_key(use_start_time=ROOT_KEY_START_TIME)
339
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)
344
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)
349
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)
354
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)
359
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)
364
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)
369
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)
373         key = self.get_key(
374             self.get_samdb(),
375             self.gmsa_sd,
376             None,
377             gkid,
378         )
379
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)
384
385     def test_request_l0_seed_key(self):
386         """Attempt to request an L0 seed key."""
387         gkid = Gkid.l0_seed_key(300)
388
389         with self.assertRaises(GetKeyError) as err:
390             self.get_key(self.get_samdb(), self.gmsa_sd, None, gkid)
391
392         self.assertEqual(
393             HRES_E_INVALIDARG,
394             err.exception.args[0],
395             "requesting an L0 seed key should fail with INVALID_PARAMETER",
396         )
397
398         with self.assertRaises(GetKeyError) as rpc_err:
399             self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, None, gkid)
400
401         self.assertEqual(
402             HRES_E_INVALIDARG,
403             rpc_err.exception.args[0],
404             "requesting an L0 seed key should fail with INVALID_PARAMETER",
405         )
406
407     def test_request_l1_seed_key(self):
408         """Attempt to request an L1 seed key."""
409         gkid = Gkid.l1_seed_key(300, 0)
410
411         with self.assertRaises(GetKeyError) as err:
412             self.get_key(self.get_samdb(), self.gmsa_sd, None, gkid)
413
414         self.assertEqual(
415             HRES_E_INVALIDARG,
416             err.exception.args[0],
417             "requesting an L1 seed key should fail with INVALID_PARAMETER",
418         )
419
420         with self.assertRaises(GetKeyError) as rpc_err:
421             self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, None, gkid)
422
423         self.assertEqual(
424             HRES_E_INVALIDARG,
425             rpc_err.exception.args[0],
426             "requesting an L1 seed key should fail with INVALID_PARAMETER",
427         )
428
429     def test_request_default_seed_key(self):
430         """Try to make a request with the default GKID."""
431         gkid = Gkid.default()
432
433         self.assertRaises(
434             NotImplementedError,
435             self.get_key,
436             self.get_samdb(),
437             self.gmsa_sd,
438             None,
439             gkid,
440         )
441
442         self.rpc_get_key(self.gkdi_conn(), self.gmsa_sd, None, gkid)
443
444
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
448         seed key."""
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"),
453             data=bytes.fromhex(
454                 "a6ef87dbbbf86b6bbe55750b941f13ca99efe5185e2e2bded5b838d8a0e77647"
455                 "0537e68cae45a7a0f4b1d6c9bf5494c3f879e172e326557cdbb6a56e8799a722"
456             ),
457         )
458
459         current_gkid = Gkid(255, 24, 31)
460         key = self.get_key(
461             self.get_samdb(),
462             self.gmsa_sd,
463             root_key_id,
464             Gkid(255, 2, 5),
465             current_gkid=current_gkid,
466         )
467
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)
472         self.assertEqual(
473             bytes.fromhex(
474                 "bd538a073490f3cf9451c933025de9b22c97eaddaffa94b379e2b919a4bed147"
475                 "5bc67f6a9175b139c69204c57d4300a0141ffe34d12ced84614593b1aa13af1c"
476             ),
477             key.l1_key,
478         )
479         self.assertIsNone(key.l2_key)
480
481     def test_current_l0_idx_l2_seed_key(self):
482         """Request a key with the current L0 index, expecting to receive an L2
483         seed key."""
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"),
488             data=bytes.fromhex(
489                 "dfd95be3153a0805c65694e7d284aace5ab0aa493350025eb8dbc6df0b4e9256"
490                 "fb4cbfbe6237ce3732694e2608760076b67082d39abd3c0fedba1b8873645064"
491             ),
492         )
493
494         current_gkid = Gkid(321, 0, 12)
495         key = self.get_key(
496             self.get_samdb(),
497             self.gmsa_sd,
498             root_key_id,
499             Gkid(321, 0, 1),
500             current_gkid=current_gkid,
501         )
502
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)
508         self.assertEqual(
509             bytes.fromhex(
510                 "bbbd9376cd16c247ed40f5912d1908218c08f0915bae02fe02cbfb3753bde406"
511                 "f9c553acd95143cf63906a0440e3cf237d2335ae4e4b9cd2d946a71351ebcb7b"
512             ),
513             key.l2_key,
514         )
515
516     def test_current_l0_idx_both_seed_keys(self):
517         """Request a key with the current L0 index, expecting to receive L1 and
518         L2 seed keys."""
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"),
523             data=bytes.fromhex(
524                 "d5912d0eb3bd60e1371b1e525dd83be7fc5baf77018b0dba6bd948b7a98ebe5a"
525                 "f37674332506a46c52c108a62f2a3e89251ad1bde6d539004679c0658853bb68"
526             ),
527         )
528
529         current_gkid = Gkid(123, 21, 0)
530         key = self.get_key(
531             self.get_samdb(),
532             self.gmsa_sd,
533             root_key_id,
534             Gkid(123, 2, 1),
535             current_gkid=current_gkid,
536         )
537
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)
542         self.assertEqual(
543             bytes.fromhex(
544                 "b1f7c5896e7dc791d9c0aaf8ca7dbab8c172a4f8b873db488a3c4cbd0f559b11"
545                 "52ffba39d4aff2d9e8aada90b27a3c94a5af996f4b8f584a4f37ccab4d505d3d"
546             ),
547             key.l1_key,
548         )
549         self.assertEqual(
550             bytes.fromhex(
551                 "133c9bbd20d9227aeb38dfcd3be6bcbfc5983ba37202088ff5c8a70511214506"
552                 "a69c195a8807cd844bcb955e9569c8e4d197759f28577cc126d15f16a7da4ee0"
553             ),
554             key.l2_key,
555         )
556
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"),
563             data=bytes.fromhex(
564                 "b41118c60a19cafa5ecf858d1a2a2216527b2daedf386e9d599e42a46add6c7d"
565                 "c93868619761c880ff3674a77c6e5fbf3434d130a9727bb2cd2a2557bdcfc752"
566             ),
567         )
568
569         key = self.get_key(
570             self.get_samdb(),
571             self.gmsa_sd,
572             root_key_id,
573             Gkid(100, 20, 30),
574             current_gkid=Gkid(101, 2, 3),
575         )
576
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)
581         self.assertEqual(
582             bytes.fromhex(
583                 "935cbdc06198eb28fa44b8d8278f51072c4613999236585041ede8e72d02fe95"
584                 "e3454f046382cbc0a700779b79474dd7e080509d76302d2937407e96e3d3d022"
585             ),
586             key.l1_key,
587         )
588         self.assertIsNone(key.l2_key)
589
590     def test_sha1(self):
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"),
596             data=bytes.fromhex(
597                 "3bed03bf0fb7d4013149154f24ca2d59b98db6d588cb1f54eca083855e25eb28"
598                 "d3562a01adc78c4b70e0b72a59515863e7732b853fba02dd7646e63108441211"
599             ),
600         )
601
602         current_gkid = Gkid(1, 2, 3)
603         key = self.get_key(
604             self.get_samdb(),
605             self.gmsa_sd,
606             root_key_id,
607             Gkid(1, 1, 1),
608             current_gkid=current_gkid,
609         )
610
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)
615         self.assertEqual(
616             bytes.fromhex(
617                 "576cb68f2e52eb739f817b488c3590d86f1c2c365f3fc9201d9c7fee7494853d"
618                 "58746ee13e48f18aa6fa69f7157de3d07de34e13836792b7c088ffb6914a89c2"
619             ),
620             key.l1_key,
621         )
622         self.assertEqual(
623             bytes.fromhex(
624                 "3ffb825adaf116b6533207d568a30ed3d3f21c68840941c9456684f9afa11b05"
625                 "6e0c59391b4d88c495d984c3d680029cc5c594630f34179119c1c5acaae5e90e"
626             ),
627             key.l2_key,
628         )
629
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"),
636             data=bytes.fromhex(
637                 "28b5b6503d3c1d24814de781bb7bfce3ef69eed1ce4809372bee2c506270c5f0"
638                 "b5c6df597472623f256c86daa0991e8a11a1705f21b2cfdc0bb9db4ba23246a2"
639             ),
640         )
641
642         current_gkid = Gkid(222, 22, 22)
643         key = self.get_key(
644             self.get_samdb(),
645             self.gmsa_sd,
646             root_key_id,
647             Gkid(222, 11, 0),
648             current_gkid=current_gkid,
649         )
650
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)
655         self.assertEqual(
656             bytes.fromhex(
657                 "57aced6e75f83f3af4f879b38b60f090b42e4bfa022fae3e6fd94280b469b0ec"
658                 "15d8b853a870b5fbdf28708cce19273b74a573acbe0deda8ef515db4691e2dcb"
659             ),
660             key.l1_key,
661         )
662         self.assertEqual(
663             bytes.fromhex(
664                 "752a0879ae2424c0504c7493599f13e588e1bbdc252f83325ad5b1fb91c24c89"
665                 "01d440f3ff9ffba59fcd65bb975732d9f383dd50b898174bb9393e383d25d540"
666             ),
667             key.l2_key,
668         )
669
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"),
676             data=bytes.fromhex(
677                 "23e5ba86cbd88f7b432ee66dbb03bf4eebf401cbfc3df735d4d728b503c87f84"
678                 "3207c6f6153f190dfe85a86cb8d8b74df13b25305981be8d7e29c96ee54c9630"
679             ),
680         )
681
682         current_gkid = Gkid(287, 28, 27)
683         key = self.get_key(
684             self.get_samdb(),
685             self.gmsa_sd,
686             root_key_id,
687             Gkid(287, 8, 7),
688             current_gkid=current_gkid,
689         )
690
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)
695         self.assertEqual(
696             bytes.fromhex(
697                 "fabadd7a9a63df57d6832df7a735aebb6e181888b2eaf301a2e4ff9a70246d38"
698                 "ab1d2416325bf3eb726a0267bab4bd950c7291f05ea5f17197ece56992af3eb8"
699             ),
700             key.l1_key,
701         )
702         self.assertEqual(
703             bytes.fromhex(
704                 "ec1c65634b5694818e1d341da9996db8f2a1ef6a2c776a7126a7ebd18b37a073"
705                 "afdac44c41b167b14e4b872d485bbb6d7b70964215d0e84a2ff142a9d943f205"
706             ),
707             key.l2_key,
708         )
709
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"),
716             data=bytes.fromhex(
717                 "489f3531c537774d432d6b97e3bc1f43d2e8c6dc17eb0e4fd9a0870d2f1ebf92"
718                 "e2496668a8b5bd11aea2d32d0aab716f48fe569f5c9b50ff3f9bf5deaea572fb"
719             ),
720         )
721
722         gkid = Gkid(333, 22, 11)
723         key = self.get_key_exact(
724             self.get_samdb(),
725             self.gmsa_sd,
726             root_key_id,
727             gkid,
728             current_gkid=self.current_gkid(self.get_samdb()),
729         )
730
731         self.assertEqual(gkid, key.gkid)
732         self.assertEqual(root_key_id, key.root_key_id)
733         self.assertEqual(Algorithm.SHA512, key.hash_algorithm)
734         self.assertEqual(
735             bytes.fromhex(
736                 "d6ab3b14f4f4c8908aa3464011b39f10a8bfadb9974af90f7d9a9fede2fdc6e5"
737                 "f68a628ec00f9994a3abd8a52ae9e2db4f68e83648311e9d7765f2535515b5e2"
738             ),
739             key.key,
740         )
741
742
743 if __name__ == "__main__":
744     import unittest
745
746     unittest.main()