s4:kdc: also provide cross-realm keys via samba_kdc_seq()
[Samba.git] / lib / crypto / gkdi.c
blobae269d64a3e1c9fc7c65470427180e095e2838b3
1 /*
2 Unix SMB/CIFS implementation.
3 Group Key Distribution Protocol functions
5 Copyright (C) Catalyst.Net Ltd 2023
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/>.
21 #include "includes.h"
22 #include <gnutls/gnutls.h>
23 #include <gnutls/crypto.h>
25 #include "lib/crypto/gnutls_helpers.h"
27 #include "lib/util/bytearray.h"
29 #include "librpc/ndr/libndr.h"
30 #include "librpc/gen_ndr/ndr_security.h"
31 #include "librpc/gen_ndr/gkdi.h"
32 #include "librpc/gen_ndr/ndr_gkdi.h"
34 #include "lib/crypto/gkdi.h"
35 #include "lib/util/data_blob.h"
37 static const uint8_t kds_service[] = {
38 /* “KDS service” as a NULL‐terminated UTF‐16LE string. */
39 'K', 0, 'D', 0, 'S', 0, ' ', 0, 's', 0, 'e', 0,
40 'r', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, 0, 0,
43 static struct Gkid gkid_from_u32_indices(const uint32_t l0_idx,
44 const uint32_t l1_idx,
45 const uint32_t l2_idx)
47 /* Catch out‐of‐range indices. */
48 if (l0_idx > INT32_MAX || l1_idx > INT8_MAX || l2_idx > INT8_MAX) {
49 return invalid_gkid;
52 return Gkid(l0_idx, l1_idx, l2_idx);
55 NTSTATUS gkdi_pull_KeyEnvelope(TALLOC_CTX *mem_ctx,
56 const DATA_BLOB *key_env_blob,
57 struct KeyEnvelope *key_env_out)
59 NTSTATUS status = NT_STATUS_OK;
60 enum ndr_err_code err;
62 if (key_env_blob == NULL) {
63 return NT_STATUS_INVALID_PARAMETER;
66 if (key_env_out == NULL) {
67 return NT_STATUS_INVALID_PARAMETER;
70 err = ndr_pull_struct_blob(key_env_blob,
71 mem_ctx,
72 key_env_out,
73 (ndr_pull_flags_fn_t)ndr_pull_KeyEnvelope);
74 status = ndr_map_error2ntstatus(err);
75 if (!NT_STATUS_IS_OK(status)) {
76 return status;
79 /* If we felt so inclined, we could check the version field here. */
81 return status;
85 * Retrieve the GKID and root key ID from a KeyEnvelope blob. The returned
86 * structure is guaranteed to have a valid GKID.
88 const struct KeyEnvelopeId *gkdi_pull_KeyEnvelopeId(
89 const DATA_BLOB key_env_blob,
90 struct KeyEnvelopeId *key_env_out)
92 TALLOC_CTX *tmp_ctx = NULL;
93 struct KeyEnvelope key_env;
94 const struct KeyEnvelopeId *key_env_ret = NULL;
95 NTSTATUS status;
97 if (key_env_out == NULL) {
98 goto out;
101 tmp_ctx = talloc_new(NULL);
102 if (tmp_ctx == NULL) {
103 goto out;
106 status = gkdi_pull_KeyEnvelope(tmp_ctx, &key_env_blob, &key_env);
107 if (!NT_STATUS_IS_OK(status)) {
108 goto out;
112 const struct Gkid gkid = gkid_from_u32_indices(
113 key_env.l0_index, key_env.l1_index, key_env.l2_index);
114 if (!gkid_is_valid(gkid)) {
115 /* The KeyId is not valid: we can’t use it. */
116 goto out;
119 *key_env_out = (struct KeyEnvelopeId){
120 .root_key_id = key_env.root_key_id, .gkid = gkid};
123 /* Return a pointer to the buffer passed in by the caller. */
124 key_env_ret = key_env_out;
126 out:
127 TALLOC_FREE(tmp_ctx);
128 return key_env_ret;
131 NTSTATUS ProvRootKey(TALLOC_CTX *mem_ctx,
132 const struct GUID root_key_id,
133 const int32_t version,
134 const DATA_BLOB root_key_data,
135 const NTTIME create_time,
136 const NTTIME use_start_time,
137 const char *const domain_id,
138 const struct KdfAlgorithm kdf_algorithm,
139 const struct ProvRootKey **const root_key_out)
141 NTSTATUS status = NT_STATUS_OK;
142 struct ProvRootKey *root_key = NULL;
144 if (root_key_out == NULL) {
145 return NT_STATUS_INVALID_PARAMETER;
147 *root_key_out = NULL;
149 root_key = talloc(mem_ctx, struct ProvRootKey);
150 if (root_key == NULL) {
151 return NT_STATUS_NO_MEMORY;
154 *root_key = (struct ProvRootKey){
155 .id = root_key_id,
156 .data = {.data = talloc_steal(root_key, root_key_data.data),
157 .length = root_key_data.length},
158 .create_time = create_time,
159 .use_start_time = use_start_time,
160 .domain_id = talloc_steal(root_key, domain_id),
161 .kdf_algorithm = kdf_algorithm,
162 .version = version,
165 *root_key_out = root_key;
166 return status;
169 struct Gkid gkdi_get_interval_id(const NTTIME time)
171 return Gkid(time / (gkdi_l1_key_iteration * gkdi_l2_key_iteration *
172 gkdi_key_cycle_duration),
173 time / (gkdi_l2_key_iteration * gkdi_key_cycle_duration) %
174 gkdi_l1_key_iteration,
175 time / gkdi_key_cycle_duration % gkdi_l2_key_iteration);
178 bool gkdi_get_key_start_time(const struct Gkid gkid, NTTIME *start_time_out)
180 if (!gkid_is_valid(gkid)) {
181 return false;
185 enum GkidType key_type = gkid_key_type(gkid);
186 if (key_type != GKID_L2_SEED_KEY) {
187 return false;
193 * Make sure that the GKID is not so large its start time can’t
194 * be represented in NTTIME.
196 const struct Gkid max_gkid = {
197 UINT64_MAX /
198 (gkdi_l1_key_iteration * gkdi_l2_key_iteration *
199 gkdi_key_cycle_duration),
200 UINT64_MAX /
201 (gkdi_l2_key_iteration *
202 gkdi_key_cycle_duration) %
203 gkdi_l1_key_iteration,
204 UINT64_MAX / gkdi_key_cycle_duration %
205 gkdi_l2_key_iteration};
206 if (!gkid_less_than_or_equal_to(gkid, max_gkid)) {
207 return false;
211 *start_time_out = ((uint64_t)gkid.l0_idx * gkdi_l1_key_iteration *
212 gkdi_l2_key_iteration +
213 (uint64_t)gkid.l1_idx * gkdi_l2_key_iteration +
214 (uint64_t)gkid.l2_idx) *
215 gkdi_key_cycle_duration;
216 return true;
220 * This returns the equivalent of
221 * gkdi_get_key_start_time(gkdi_get_interval_id(time)).
223 NTTIME gkdi_get_interval_start_time(const NTTIME time)
225 return time / gkdi_key_cycle_duration * gkdi_key_cycle_duration;
228 bool gkid_less_than_or_equal_to(const struct Gkid g1, const struct Gkid g2)
230 if (g1.l0_idx != g2.l0_idx) {
231 return g1.l0_idx < g2.l0_idx;
234 if (g1.l1_idx != g2.l1_idx) {
235 return g1.l1_idx < g2.l1_idx;
238 return g1.l2_idx <= g2.l2_idx;
241 bool gkdi_rollover_interval(const int64_t managed_password_interval,
242 NTTIME *result)
245 * This is actually a conservative reckoning. The interval could be one
246 * higher than this maximum and not overflow. But there’s no reason to
247 * support intervals that high (and Windows will start producing strange
248 * results for intervals beyond that).
250 const int64_t maximum_interval = UINT64_MAX / gkdi_key_cycle_duration *
251 10 / 24;
253 if (managed_password_interval < 0 ||
254 managed_password_interval > maximum_interval)
256 return false;
259 *result = (uint64_t)managed_password_interval * 24 / 10 *
260 gkdi_key_cycle_duration;
261 return true;
264 struct GkdiContextShort {
265 uint8_t buf[sizeof((struct GUID_ndr_buf){}.buf) + sizeof(int32_t) +
266 sizeof(int32_t) + sizeof(int32_t)];
269 static NTSTATUS make_gkdi_context(const struct GkdiDerivationCtx *ctx,
270 struct GkdiContextShort *out_ctx)
272 enum ndr_err_code ndr_err;
273 DATA_BLOB b = {.data = out_ctx->buf, .length = sizeof out_ctx->buf};
275 if (ctx->target_security_descriptor.length) {
276 return NT_STATUS_INVALID_PARAMETER;
279 ndr_err = ndr_push_struct_into_fixed_blob(
280 &b, ctx, (ndr_push_flags_fn_t)ndr_push_GkdiDerivationCtx);
281 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
282 return ndr_map_error2ntstatus(ndr_err);
285 return NT_STATUS_OK;
288 static NTSTATUS make_gkdi_context_security_descriptor(
289 TALLOC_CTX *mem_ctx,
290 const struct GkdiDerivationCtx *ctx,
291 const DATA_BLOB security_descriptor,
292 DATA_BLOB *out_ctx)
294 enum ndr_err_code ndr_err;
295 struct GkdiDerivationCtx ctx_with_sd = *ctx;
297 if (ctx_with_sd.target_security_descriptor.length != 0) {
298 return NT_STATUS_INVALID_PARAMETER;
301 ctx_with_sd.target_security_descriptor = security_descriptor;
303 ndr_err = ndr_push_struct_blob(out_ctx,
304 mem_ctx,
305 &ctx_with_sd,
306 (ndr_push_flags_fn_t)
307 ndr_push_GkdiDerivationCtx);
308 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
309 return ndr_map_error2ntstatus(ndr_err);
312 return NT_STATUS_OK;
315 struct GkdiContext {
316 struct GkdiDerivationCtx ctx;
317 gnutls_mac_algorithm_t algorithm;
320 gnutls_mac_algorithm_t get_sp800_108_mac_algorithm(
321 const struct KdfAlgorithm kdf_algorithm)
323 switch (kdf_algorithm.id) {
324 case KDF_ALGORITHM_SP800_108_CTR_HMAC:
325 switch (kdf_algorithm.param.sp800_108) {
326 case KDF_PARAM_SHA1:
327 return GNUTLS_MAC_SHA1;
328 case KDF_PARAM_SHA256:
329 return GNUTLS_MAC_SHA256;
330 case KDF_PARAM_SHA384:
331 return GNUTLS_MAC_SHA384;
332 case KDF_PARAM_SHA512:
333 return GNUTLS_MAC_SHA512;
335 break;
338 return GNUTLS_MAC_UNKNOWN;
341 static NTSTATUS GkdiContext(const struct ProvRootKey *const root_key,
342 struct GkdiContext *const ctx)
344 NTSTATUS status = NT_STATUS_OK;
345 gnutls_mac_algorithm_t algorithm = GNUTLS_MAC_UNKNOWN;
347 if (ctx == NULL) {
348 status = NT_STATUS_INVALID_PARAMETER;
349 goto out;
352 if (root_key == NULL) {
353 status = NT_STATUS_INVALID_PARAMETER;
354 goto out;
357 if (root_key->version != root_key_version_1) {
358 status = NT_STATUS_NOT_SUPPORTED;
359 goto out;
362 if (root_key->data.length != GKDI_KEY_LEN) {
363 status = NT_STATUS_NOT_SUPPORTED;
364 goto out;
367 algorithm = get_sp800_108_mac_algorithm(root_key->kdf_algorithm);
368 if (algorithm == GNUTLS_MAC_UNKNOWN) {
369 status = NT_STATUS_NOT_SUPPORTED;
370 goto out;
374 * The context comprises the GUID corresponding to the root key, the
375 * GKID (which we shall initialize to zero), and the encoded target
376 * security descriptor (which will initially be empty).
378 *ctx = (struct GkdiContext){
379 .ctx = {.guid = root_key->id,
380 .l0_idx = 0,
381 .l1_idx = 0,
382 .l2_idx = 0,
383 .target_security_descriptor = {}},
384 .algorithm = algorithm,
386 out:
387 return status;
390 static NTSTATUS compute_l1_seed_key(TALLOC_CTX *mem_ctx,
391 struct GkdiContext *ctx,
392 const DATA_BLOB security_descriptor,
393 const struct ProvRootKey *const root_key,
394 const struct Gkid gkid,
395 uint8_t key[static const GKDI_KEY_LEN])
397 NTSTATUS status = NT_STATUS_OK;
398 struct GkdiContextShort short_ctx;
399 int8_t n;
401 ctx->ctx.l0_idx = gkid.l0_idx;
402 ctx->ctx.l1_idx = -1;
403 ctx->ctx.l2_idx = -1;
405 status = make_gkdi_context(&ctx->ctx, &short_ctx);
406 if (!NT_STATUS_IS_OK(status)) {
407 goto out;
410 /* Derive an L0 seed key with GKID = (L0, −1, −1). */
412 status = samba_gnutls_sp800_108_derive_key(root_key->data.data,
413 root_key->data.length,
414 NULL,
416 kds_service,
417 sizeof kds_service,
418 short_ctx.buf,
419 sizeof short_ctx.buf,
420 ctx->algorithm,
421 key,
422 GKDI_KEY_LEN);
423 if (!NT_STATUS_IS_OK(status)) {
424 goto out;
427 /* Derive an L1 seed key with GKID = (L0, 31, −1). */
429 ctx->ctx.l1_idx = 31;
432 DATA_BLOB security_descriptor_ctx;
434 status = make_gkdi_context_security_descriptor(
435 mem_ctx,
436 &ctx->ctx,
437 security_descriptor,
438 &security_descriptor_ctx);
439 if (!NT_STATUS_IS_OK(status)) {
440 goto out;
443 status = samba_gnutls_sp800_108_derive_key(
444 key,
445 GKDI_KEY_LEN,
446 NULL,
448 kds_service,
449 sizeof kds_service,
450 security_descriptor_ctx.data,
451 security_descriptor_ctx.length,
452 ctx->algorithm,
453 key,
454 GKDI_KEY_LEN);
455 data_blob_free(&security_descriptor_ctx);
456 if (!NT_STATUS_IS_OK(status)) {
457 goto out;
461 for (n = 30; n >= gkid.l1_idx; --n) {
462 /* Derive an L1 seed key with GKID = (L0, n, −1). */
464 ctx->ctx.l1_idx = n;
466 status = make_gkdi_context(&ctx->ctx, &short_ctx);
467 if (!NT_STATUS_IS_OK(status)) {
468 goto out;
471 status = samba_gnutls_sp800_108_derive_key(key,
472 GKDI_KEY_LEN,
473 NULL,
475 kds_service,
476 sizeof kds_service,
477 short_ctx.buf,
478 sizeof short_ctx.buf,
479 ctx->algorithm,
480 key,
481 GKDI_KEY_LEN);
482 if (!NT_STATUS_IS_OK(status)) {
483 goto out;
487 out:
488 return status;
491 static NTSTATUS derive_l2_seed_key(struct GkdiContext *ctx,
492 const struct Gkid gkid,
493 uint8_t key[static const GKDI_KEY_LEN])
495 NTSTATUS status = NT_STATUS_OK;
496 int8_t n;
498 ctx->ctx.l0_idx = gkid.l0_idx;
499 ctx->ctx.l1_idx = gkid.l1_idx;
501 for (n = 31; n >= gkid.l2_idx; --n) {
502 struct GkdiContextShort short_ctx;
504 /* Derive an L2 seed key with GKID = (L0, L1, n). */
506 ctx->ctx.l2_idx = n;
508 status = make_gkdi_context(&ctx->ctx, &short_ctx);
509 if (!NT_STATUS_IS_OK(status)) {
510 goto out;
513 status = samba_gnutls_sp800_108_derive_key(key,
514 GKDI_KEY_LEN,
515 NULL,
517 kds_service,
518 sizeof kds_service,
519 short_ctx.buf,
520 sizeof short_ctx.buf,
521 ctx->algorithm,
522 key,
523 GKDI_KEY_LEN);
524 if (!NT_STATUS_IS_OK(status)) {
525 goto out;
529 out:
530 return status;
533 enum GkidType gkid_key_type(const struct Gkid gkid)
535 if (gkid.l0_idx == -1) {
536 return GKID_DEFAULT;
539 if (gkid.l1_idx == -1) {
540 return GKID_L0_SEED_KEY;
543 if (gkid.l2_idx == -1) {
544 return GKID_L1_SEED_KEY;
547 return GKID_L2_SEED_KEY;
550 bool gkid_is_valid(const struct Gkid gkid)
552 if (gkid.l0_idx < -1) {
553 return false;
556 if (gkid.l1_idx < -1 || gkid.l1_idx >= gkdi_l1_key_iteration) {
557 return false;
560 if (gkid.l2_idx < -1 || gkid.l2_idx >= gkdi_l2_key_iteration) {
561 return false;
564 if (gkid.l0_idx == -1 && gkid.l1_idx != -1) {
565 return false;
568 if (gkid.l1_idx == -1 && gkid.l2_idx != -1) {
569 return false;
572 return true;
575 NTSTATUS compute_seed_key(TALLOC_CTX *mem_ctx,
576 const DATA_BLOB target_security_descriptor,
577 const struct ProvRootKey *const root_key,
578 const struct Gkid gkid,
579 uint8_t key[static const GKDI_KEY_LEN])
581 NTSTATUS status = NT_STATUS_OK;
582 enum GkidType gkid_type;
583 struct GkdiContext ctx;
585 if (!gkid_is_valid(gkid)) {
586 status = NT_STATUS_INVALID_PARAMETER;
587 goto out;
590 gkid_type = gkid_key_type(gkid);
591 if (gkid_type < GKID_L1_SEED_KEY) {
592 /* Don’t allow derivation of L0 seed keys. */
593 status = NT_STATUS_INVALID_PARAMETER;
594 goto out;
597 status = GkdiContext(root_key, &ctx);
598 if (!NT_STATUS_IS_OK(status)) {
599 goto out;
602 status = compute_l1_seed_key(
603 mem_ctx, &ctx, target_security_descriptor, root_key, gkid, key);
604 if (!NT_STATUS_IS_OK(status)) {
605 goto out;
608 if (gkid_type == GKID_L2_SEED_KEY) {
609 status = derive_l2_seed_key(&ctx, gkid, key);
610 if (!NT_STATUS_IS_OK(status)) {
611 goto out;
615 out:
616 return status;
619 NTSTATUS kdf_sp_800_108_from_params(
620 const DATA_BLOB *const kdf_param,
621 struct KdfAlgorithm *const kdf_algorithm_out)
623 TALLOC_CTX *tmp_ctx = NULL;
624 NTSTATUS status = NT_STATUS_OK;
625 enum ndr_err_code err;
626 enum KdfSp800_108Param sp800_108_param = KDF_PARAM_SHA256;
627 struct KdfParameters kdf_parameters;
629 if (kdf_param != NULL) {
630 tmp_ctx = talloc_new(NULL);
631 if (tmp_ctx == NULL) {
632 status = NT_STATUS_NO_MEMORY;
633 goto out;
636 err = ndr_pull_struct_blob(kdf_param,
637 tmp_ctx,
638 &kdf_parameters,
639 (ndr_pull_flags_fn_t)
640 ndr_pull_KdfParameters);
641 if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
642 status = ndr_map_error2ntstatus(err);
643 DBG_WARNING("KdfParameters pull failed: %s\n",
644 nt_errstr(status));
645 goto out;
648 if (kdf_parameters.hash_algorithm == NULL) {
649 status = NT_STATUS_NOT_SUPPORTED;
650 goto out;
653 /* These string comparisons are case‐sensitive. */
654 if (strcmp(kdf_parameters.hash_algorithm, "SHA1") == 0) {
655 sp800_108_param = KDF_PARAM_SHA1;
656 } else if (strcmp(kdf_parameters.hash_algorithm, "SHA256") == 0)
658 sp800_108_param = KDF_PARAM_SHA256;
659 } else if (strcmp(kdf_parameters.hash_algorithm, "SHA384") == 0)
661 sp800_108_param = KDF_PARAM_SHA384;
662 } else if (strcmp(kdf_parameters.hash_algorithm, "SHA512") == 0)
664 sp800_108_param = KDF_PARAM_SHA512;
665 } else {
666 status = NT_STATUS_NOT_SUPPORTED;
667 goto out;
671 *kdf_algorithm_out = (struct KdfAlgorithm){
672 .id = KDF_ALGORITHM_SP800_108_CTR_HMAC,
673 .param.sp800_108 = sp800_108_param,
675 out:
676 talloc_free(tmp_ctx);
677 return status;
680 NTSTATUS kdf_algorithm_from_params(const char *const kdf_algorithm_id,
681 const DATA_BLOB *const kdf_param,
682 struct KdfAlgorithm *const kdf_algorithm_out)
684 if (kdf_algorithm_id == NULL) {
685 return NT_STATUS_INVALID_PARAMETER;
688 /* This string comparison is case‐sensitive. */
689 if (strcmp(kdf_algorithm_id, "SP800_108_CTR_HMAC") == 0) {
690 return kdf_sp_800_108_from_params(kdf_param, kdf_algorithm_out);
693 /* Unknown algorithm. */
694 return NT_STATUS_NOT_SUPPORTED;