2 Unix SMB/CIFS implementation.
3 Group Key Distribution Protocol functions
5 Copyright (C) Catalyst.Net Ltd 2024
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <https://www.gnu.org/licenses/>.
23 #include <ldb_errors.h>
24 #include <ldb_module.h>
25 #include "lib/crypto/gkdi.h"
26 #include "lib/util/data_blob.h"
27 #include "lib/util/samba_util.h"
28 #include "lib/util/util_str_hex.h"
29 #include "librpc/ndr/libndr.h"
30 #include "dsdb/gmsa/gkdi.h"
31 #include "dsdb/samdb/ldb_modules/util.h"
32 #include "dsdb/samdb/samdb.h"
33 #include "dsdb/common/proto.h"
34 #include "librpc/gen_ndr/gkdi.h"
35 #include "librpc/gen_ndr/ndr_gkdi.h"
37 NTSTATUS
gkdi_root_key_from_msg(TALLOC_CTX
*mem_ctx
,
38 const struct GUID root_key_id
,
39 const struct ldb_message
*const msg
,
40 const struct ProvRootKey
**const root_key_out
)
42 NTSTATUS status
= NT_STATUS_OK
;
43 struct ldb_val root_key_data
= {};
44 struct KdfAlgorithm kdf_algorithm
= {};
46 const int version
= ldb_msg_find_attr_as_int(msg
, "msKds-Version", 0);
47 const NTTIME create_time
= samdb_result_nttime(msg
,
50 const NTTIME use_start_time
= samdb_result_nttime(msg
,
53 const char *domain_id
= ldb_msg_find_attr_as_string(msg
,
58 const struct ldb_val
*root_key_val
= ldb_msg_find_ldb_val(
59 msg
, "msKds-RootKeyData");
60 if (root_key_val
!= NULL
) {
61 root_key_data
= *root_key_val
;
66 const char *algorithm_id
= ldb_msg_find_attr_as_string(
67 msg
, "msKds-KDFAlgorithmID", NULL
);
68 const struct ldb_val
*kdf_param_val
= ldb_msg_find_ldb_val(
69 msg
, "msKds-KDFParam");
70 status
= kdf_algorithm_from_params(algorithm_id
,
73 if (!NT_STATUS_IS_OK(status
)) {
78 status
= ProvRootKey(mem_ctx
,
87 if (!NT_STATUS_IS_OK(status
)) {
96 * Calculate an appropriate useStartTime for a root key created at
99 * This function goes unused.
101 NTTIME
gkdi_root_key_use_start_time(const NTTIME current_time
)
103 const NTTIME start_time
= gkdi_get_interval_start_time(current_time
);
105 return start_time
+ gkdi_key_cycle_duration
+ gkdi_max_clock_skew
;
108 static int gkdi_create_root_key(TALLOC_CTX
*mem_ctx
,
109 struct ldb_context
*const ldb
,
110 const NTTIME current_time
,
111 const NTTIME use_start_time
,
112 struct GUID
*const root_key_id_out
,
113 struct ldb_dn
**const root_key_dn_out
)
115 TALLOC_CTX
*tmp_ctx
= NULL
;
116 struct GUID root_key_id
;
117 struct ldb_dn
*server_config_dn
= NULL
;
118 struct ldb_result
*server_config_res
= NULL
;
119 struct ldb_message
*server_config_msg
= NULL
;
120 uint64_t server_config_version
;
121 const struct ldb_val
*server_config_version_val
= NULL
;
122 const char *server_config_KDFAlgorithmID
= NULL
;
123 const struct ldb_val
*server_config_KDFParam
= NULL
;
124 const char *server_config_SecretAgreementAlgorithmID
= NULL
;
125 const struct ldb_val
*server_config_SecretAgreementParam
= NULL
;
126 uint64_t server_config_PublicKeyLength
;
127 uint64_t server_config_PrivateKeyLength
;
128 struct KdfAlgorithm kdf_algorithm
;
129 DATA_BLOB kdf_parameters_blob
= data_blob_null
;
130 struct ldb_message
*add_msg
= NULL
;
131 uint8_t root_key_data
[GKDI_KEY_LEN
];
132 NTSTATUS status
= NT_STATUS_OK
;
133 int ret
= LDB_SUCCESS
;
135 static const char *server_config_attrs
[] = {
137 "msKds-KDFAlgorithmID",
138 "msKds-SecretAgreementAlgorithmID",
139 "msKds-SecretAgreementParam",
140 "msKds-PublicKeyLength",
141 "msKds-PrivateKeyLength",
146 *root_key_dn_out
= NULL
;
148 tmp_ctx
= talloc_new(mem_ctx
);
149 if (tmp_ctx
== NULL
) {
154 server_config_dn
= samdb_configuration_dn(ldb
,
156 "CN=Group Key Distribution Service Server Configuration,"
157 "CN=Server Configuration,"
158 "CN=Group Key Distribution Service,"
160 if (server_config_dn
== NULL
) {
165 ret
= dsdb_search_dn(ldb
,
172 if (ret
== LDB_ERR_NO_SUCH_OBJECT
) {
173 ldb_asprintf_errstring(ldb
, "Unable to create new GKDI root key as we do not have a GKDI server configuration at %s",
174 ldb_dn_get_linearized(server_config_dn
));
178 if (ret
!= LDB_SUCCESS
) {
182 server_config_msg
= server_config_res
->msgs
[0];
184 server_config_version_val
185 = ldb_msg_find_ldb_val(server_config_msg
,
187 server_config_version
188 = ldb_msg_find_attr_as_uint64(server_config_msg
,
192 /* These values we assert on, so we don't create keys we can't use */
193 if (server_config_version_val
== NULL
) {
195 * The systemMustContain msKds-Version attribute
196 * cannot be read, so if absent we just fail with
197 * permission denied, as that is all that this can
200 ldb_asprintf_errstring(ldb
,
201 "Unwilling to create new GKDI root key as "
202 "msKds-Version is not readable on %s\n",
203 ldb_dn_get_linearized(server_config_dn
));
204 ret
= LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS
;
206 } else if (server_config_version
!= 1) {
207 ldb_asprintf_errstring(ldb
,
208 "Unwilling to create new GKDI root key as "
209 "%s has msKds-Version = %s "
210 "and we only support version 1\n",
211 ldb_dn_get_linearized(server_config_dn
),
212 ldb_msg_find_attr_as_string(server_config_msg
, "msKds-Version", "(missing)"));
213 ret
= LDB_ERR_CONSTRAINT_VIOLATION
;
217 server_config_KDFAlgorithmID
218 = ldb_msg_find_attr_as_string(server_config_msg
,
219 "msKds-KDFAlgorithmID",
223 server_config_KDFParam
224 = ldb_msg_find_ldb_val(server_config_msg
,
226 if (server_config_KDFParam
== NULL
) {
227 struct KdfParameters kdf_parameters
= {
228 .hash_algorithm
= "SHA512"
230 enum ndr_err_code err
;
232 err
= ndr_push_struct_blob(&kdf_parameters_blob
,
235 (ndr_push_flags_fn_t
)
236 ndr_push_KdfParameters
);
238 if (!NDR_ERR_CODE_IS_SUCCESS(err
)) {
239 status
= ndr_map_error2ntstatus(err
);
240 ldb_asprintf_errstring(ldb
,
241 "KdfParameters pull failed: %s\n",
243 ret
= LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE
;
247 server_config_KDFParam
= &kdf_parameters_blob
;
250 status
= kdf_algorithm_from_params(server_config_KDFAlgorithmID
,
251 server_config_KDFParam
,
253 if (!NT_STATUS_IS_OK(status
)) {
254 ldb_asprintf_errstring(ldb
,
255 "Unwilling to create new GKDI root key as "
256 "%s has an unsupported msKds-KDFAlgorithmID / msKds-KDFParam combination set: %s\n",
257 ldb_dn_get_linearized(server_config_dn
),
259 ret
= LDB_ERR_CONSTRAINT_VIOLATION
;
263 server_config_SecretAgreementAlgorithmID
264 = ldb_msg_find_attr_as_string(server_config_msg
,
265 "msKds-SecretAgreementAlgorithmID",
268 /* Optional in msKds-ProvRootKey */
269 server_config_SecretAgreementParam
270 = ldb_msg_find_ldb_val(server_config_msg
,
271 "msKds-SecretAgreementParam");
272 if (server_config_SecretAgreementParam
== NULL
) {
273 static const uint8_t ffc_dh_parameters
[] = {
274 12, 2, 0, 0, 68, 72, 80, 77, 0, 1, 0,
275 0, 135, 168, 230, 29, 180, 182, 102, 60, 255, 187,
276 209, 156, 101, 25, 89, 153, 140, 238, 246, 8, 102,
277 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0,
278 224, 13, 248, 241, 214, 25, 87, 212, 250, 247, 223,
279 69, 97, 178, 170, 48, 22, 195, 217, 17, 52, 9,
280 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, 32,
281 158, 12, 100, 151, 81, 122, 189, 90, 138, 157, 48,
282 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88,
283 192, 34, 224, 177, 239, 66, 117, 191, 123, 108, 91,
284 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177,
285 229, 155, 184, 188, 57, 160, 191, 18, 48, 127, 92,
286 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202,
287 225, 202, 166, 183, 144, 45, 82, 82, 103, 53, 72,
288 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58,
289 216, 52, 119, 150, 82, 77, 142, 246, 161, 103, 181,
290 164, 24, 37, 217, 103, 225, 68, 229, 20, 5, 100,
291 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, 202,
292 63, 121, 113, 80, 96, 38, 192, 184, 87, 246, 137,
293 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33,
294 195, 163, 150, 10, 84, 231, 16, 195, 117, 242, 99,
295 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152,
296 175, 18, 97, 22, 210, 39, 110, 17, 113, 95, 105,
297 56, 119, 250, 215, 239, 9, 202, 219, 9, 74, 233,
298 30, 26, 21, 151, 63, 179, 44, 155, 115, 19, 77,
299 11, 46, 119, 80, 102, 96, 237, 189, 72, 76, 167,
300 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, 26,
301 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, 63,
302 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108,
303 27, 12, 107, 71, 177, 188, 55, 115, 191, 126, 140,
304 111, 98, 144, 18, 40, 248, 194, 140, 187, 24, 165,
305 90, 227, 19, 65, 0, 10, 101, 1, 150, 249, 49,
306 199, 122, 87, 242, 221, 244, 99, 229, 233, 236, 20,
307 75, 119, 125, 230, 42, 170, 184, 168, 98, 138, 195,
308 118, 210, 130, 214, 237, 56, 100, 230, 121, 130, 66,
309 142, 188, 131, 29, 20, 52, 143, 111, 47, 145, 147,
310 181, 4, 90, 242, 118, 113, 100, 225, 223, 201, 103,
311 193, 251, 63, 46, 85, 164, 189, 27, 255, 232, 59,
312 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219,
313 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30,
314 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97,
315 153, 236, 208, 110, 21, 87, 205, 9, 21, 179, 53,
316 59, 187, 100, 224, 236, 55, 127, 208, 40, 55, 13,
317 249, 43, 82, 199, 137, 20, 40, 205, 198, 126, 182,
318 24, 75, 82, 61, 29, 178, 70, 195, 47, 99, 7,
319 132, 144, 240, 14, 248, 214, 71, 209, 72, 212, 121,
320 84, 81, 94, 35, 39, 207, 239, 152, 197, 130, 102,
321 75, 76, 15, 108, 196, 22, 89};
322 static const DATA_BLOB ffc_dh_parameters_blob
= {
323 discard_const_p(uint8_t, ffc_dh_parameters
),
324 sizeof ffc_dh_parameters
};
325 server_config_SecretAgreementParam
= &ffc_dh_parameters_blob
;
328 server_config_PublicKeyLength
329 = ldb_msg_find_attr_as_uint64(server_config_msg
,
330 "msKds-PublicKeyLength",
333 server_config_PrivateKeyLength
334 = ldb_msg_find_attr_as_uint64(server_config_msg
,
335 "msKds-PrivateKeyLength",
338 add_msg
= ldb_msg_new(tmp_ctx
);
339 if (add_msg
== NULL
) {
344 ret
= ldb_msg_append_string(add_msg
,
353 const DATA_BLOB root_key_data_blob
= {
354 .data
= root_key_data
, .length
= sizeof root_key_data
};
356 generate_secret_buffer(root_key_data
, sizeof root_key_data
);
358 ret
= ldb_msg_append_value(add_msg
,
367 ret
= samdb_msg_append_uint64(ldb
,
377 ret
= samdb_msg_append_uint64(ldb
,
380 "msKds-UseStartTime",
388 struct ldb_dn
*domain_dn
= NULL
;
390 ret
= samdb_server_reference_dn(ldb
, tmp_ctx
, &domain_dn
);
395 ret
= ldb_msg_append_linearized_dn(add_msg
,
404 ret
= samdb_msg_append_uint64(ldb
,
408 server_config_version
,
414 ret
= ldb_msg_append_string(add_msg
,
415 "msKds-KDFAlgorithmID",
416 server_config_KDFAlgorithmID
,
422 ret
= ldb_msg_append_string(add_msg
,
423 "msKds-SecretAgreementAlgorithmID",
424 server_config_SecretAgreementAlgorithmID
,
430 if (server_config_SecretAgreementParam
!= NULL
) {
431 ret
= ldb_msg_append_value(add_msg
,
432 "msKds-SecretAgreementParam",
433 server_config_SecretAgreementParam
,
440 ret
= samdb_msg_append_uint64(ldb
,
443 "msKds-PublicKeyLength",
444 server_config_PublicKeyLength
,
450 ret
= samdb_msg_append_uint64(ldb
,
453 "msKds-PrivateKeyLength",
454 server_config_PrivateKeyLength
,
457 ret
= ldb_msg_append_value(add_msg
,
459 server_config_KDFParam
,
466 uint8_t guid_buf
[sizeof((struct GUID_ndr_buf
){}.buf
)];
467 const DATA_BLOB guid_blob
= {.data
= guid_buf
,
468 .length
= sizeof guid_buf
};
470 generate_secret_buffer(guid_buf
, sizeof guid_buf
);
472 status
= GUID_from_ndr_blob(&guid_blob
, &root_key_id
);
473 if (!NT_STATUS_IS_OK(status
)) {
474 ret
= ldb_operr(ldb
);
480 struct ldb_dn
*root_key_dn
= NULL
;
482 root_key_dn
= samdb_gkdi_root_key_dn(ldb
,
485 if (root_key_dn
== NULL
) {
486 ret
= ldb_operr(ldb
);
490 add_msg
->dn
= root_key_dn
;
493 ret
= dsdb_add(ldb
, add_msg
, 0);
498 *root_key_id_out
= root_key_id
;
499 *root_key_dn_out
= talloc_steal(mem_ctx
, add_msg
->dn
);
502 talloc_free(tmp_ctx
);
507 * The PrivateKey, PublicKey, and SecretAgreement attributes are related to the
508 * public‐key functionality in GKDI. Samba doesn’t try to implement any of that,
509 * so we don’t bother looking at these attributes.
511 static const char *const root_key_attrs
[] = {
514 "msKds-KDFAlgorithmID",
516 /* "msKds-PrivateKeyLength", */
517 /* "msKds-PublicKeyLength", */
519 /* "msKds-SecretAgreementAlgorithmID", */
520 /* "msKds-SecretAgreementParam", */
521 "msKds-UseStartTime",
527 * Create and return a new GKDI root key.
529 * This function goes unused.
531 int gkdi_new_root_key(TALLOC_CTX
*mem_ctx
,
532 struct ldb_context
*const ldb
,
533 const NTTIME current_time
,
534 const NTTIME use_start_time
,
535 struct GUID
*const root_key_id_out
,
536 const struct ldb_message
**const root_key_out
)
538 TALLOC_CTX
*tmp_ctx
= NULL
;
539 struct ldb_dn
*root_key_dn
= NULL
;
540 struct ldb_result
*res
= NULL
;
541 int ret
= LDB_SUCCESS
;
543 *root_key_out
= NULL
;
545 tmp_ctx
= talloc_new(mem_ctx
);
546 if (tmp_ctx
== NULL
) {
551 ret
= gkdi_create_root_key(tmp_ctx
,
561 ret
= dsdb_search_dn(
562 ldb
, tmp_ctx
, &res
, root_key_dn
, root_key_attrs
, 0);
567 if (res
->count
!= 1) {
568 ret
= LDB_ERR_NO_SUCH_OBJECT
;
572 *root_key_out
= talloc_steal(mem_ctx
, res
->msgs
[0]);
575 talloc_free(tmp_ctx
);
579 int gkdi_root_key_from_id(TALLOC_CTX
*mem_ctx
,
580 struct ldb_context
*const ldb
,
581 const struct GUID
*const root_key_id
,
582 const struct ldb_message
**const root_key_out
)
584 TALLOC_CTX
*tmp_ctx
= NULL
;
585 struct ldb_dn
*root_key_dn
= NULL
;
586 struct ldb_result
*res
= NULL
;
587 int ret
= LDB_SUCCESS
;
589 *root_key_out
= NULL
;
591 tmp_ctx
= talloc_new(mem_ctx
);
592 if (tmp_ctx
== NULL
) {
597 root_key_dn
= samdb_gkdi_root_key_dn(ldb
, tmp_ctx
, root_key_id
);
598 if (root_key_dn
== NULL
) {
599 ret
= ldb_operr(ldb
);
603 ret
= dsdb_search_dn(
604 ldb
, tmp_ctx
, &res
, root_key_dn
, root_key_attrs
, 0);
609 if (res
->count
!= 1) {
610 ret
= dsdb_werror(ldb
,
611 LDB_ERR_NO_SUCH_OBJECT
,
612 W_ERROR(HRES_ERROR_V(HRES_NTE_NO_KEY
)),
613 "failed to find root key");
617 *root_key_out
= talloc_steal(mem_ctx
, res
->msgs
[0]);
620 talloc_free(tmp_ctx
);
624 int gkdi_most_recently_created_root_key(
626 struct ldb_context
*const ldb
,
627 _UNUSED_
const NTTIME current_time
,
628 const NTTIME not_after
,
629 struct GUID
*const root_key_id_out
,
630 const struct ldb_message
**const root_key_out
)
632 TALLOC_CTX
*tmp_ctx
= NULL
;
633 struct ldb_result
*res
= NULL
;
634 int ret
= LDB_SUCCESS
;
636 *root_key_out
= NULL
;
638 tmp_ctx
= talloc_new(mem_ctx
);
639 if (tmp_ctx
== NULL
) {
645 struct ldb_dn
*root_key_container_dn
= NULL
;
647 root_key_container_dn
= samdb_gkdi_root_key_container_dn(
649 if (root_key_container_dn
== NULL
) {
650 ret
= ldb_operr(ldb
);
654 ret
= dsdb_search(ldb
,
657 root_key_container_dn
,
661 "(msKds-UseStartTime<=%" PRIu64
")",
669 * Windows just gives up if there are more than 1000 root keys in the
674 struct root_key_candidate
{
676 const struct ldb_message
*key
;
678 } most_recent_key
= {
683 for (i
= 0; i
< res
->count
; ++i
) {
684 struct root_key_candidate key
= {
687 const struct ldb_val
*rdn_val
= NULL
;
690 key
.create_time
= samdb_result_nttime(
691 key
.key
, "msKds-CreateTime", 0);
692 if (key
.create_time
< most_recent_key
.create_time
) {
693 /* We already have a more recent key. */
697 rdn_val
= ldb_dn_get_rdn_val(key
.key
->dn
);
698 if (rdn_val
== NULL
) {
702 if (rdn_val
->length
!= 36) {
704 * Check the RDN is the right length — 36 is the
710 ok
= parse_guid_string((const char *)rdn_val
->data
,
713 /* The RDN is not a correctly formatted GUID. */
718 * We’ve found a new candidate for the most recent root
721 most_recent_key
= key
;
724 if (most_recent_key
.key
== NULL
) {
726 * We were not able to find a suitable root key, but
727 * there is a possibility that a key we create now will
728 * do: if gkdi_root_key_use_start_time(current_time) ≤
729 * not_after, then a newly‐created key will satisfy our
730 * caller’s requirements.
732 * Unfortunately, with gMSAs this (I believe) will never
733 * be the case. It’s too late to call
734 * gkdi_new_root_key() — the new key will be a bit *too*
735 * new to be usable for a gMSA.
738 ret
= dsdb_werror(ldb
,
739 LDB_ERR_NO_SUCH_OBJECT
,
740 W_ERROR(HRES_ERROR_V(
742 "failed to find a suitable root key");
746 /* Return the root key that we found. */
747 *root_key_id_out
= most_recent_key
.id
;
748 *root_key_out
= talloc_steal(mem_ctx
, most_recent_key
.key
);
752 talloc_free(tmp_ctx
);