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/>.
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
) {
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
,
73 (ndr_pull_flags_fn_t
)ndr_pull_KeyEnvelope
);
74 status
= ndr_map_error2ntstatus(err
);
75 if (!NT_STATUS_IS_OK(status
)) {
79 /* If we felt so inclined, we could check the version field here. */
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
;
97 if (key_env_out
== NULL
) {
101 tmp_ctx
= talloc_new(NULL
);
102 if (tmp_ctx
== NULL
) {
106 status
= gkdi_pull_KeyEnvelope(tmp_ctx
, &key_env_blob
, &key_env
);
107 if (!NT_STATUS_IS_OK(status
)) {
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. */
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
;
127 TALLOC_FREE(tmp_ctx
);
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
){
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
,
165 *root_key_out
= root_key
;
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
)) {
185 enum GkidType key_type
= gkid_key_type(gkid
);
186 if (key_type
!= GKID_L2_SEED_KEY
) {
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
= {
198 (gkdi_l1_key_iteration
* gkdi_l2_key_iteration
*
199 gkdi_key_cycle_duration
),
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
)) {
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
;
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
,
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
*
253 if (managed_password_interval
< 0 ||
254 managed_password_interval
> maximum_interval
)
259 *result
= (uint64_t)managed_password_interval
* 24 / 10 *
260 gkdi_key_cycle_duration
;
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
);
288 static NTSTATUS
make_gkdi_context_security_descriptor(
290 const struct GkdiDerivationCtx
*ctx
,
291 const DATA_BLOB security_descriptor
,
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
,
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
);
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
) {
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
;
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
;
348 status
= NT_STATUS_INVALID_PARAMETER
;
352 if (root_key
== NULL
) {
353 status
= NT_STATUS_INVALID_PARAMETER
;
357 if (root_key
->version
!= root_key_version_1
) {
358 status
= NT_STATUS_NOT_SUPPORTED
;
362 if (root_key
->data
.length
!= GKDI_KEY_LEN
) {
363 status
= NT_STATUS_NOT_SUPPORTED
;
367 algorithm
= get_sp800_108_mac_algorithm(root_key
->kdf_algorithm
);
368 if (algorithm
== GNUTLS_MAC_UNKNOWN
) {
369 status
= NT_STATUS_NOT_SUPPORTED
;
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
,
383 .target_security_descriptor
= {}},
384 .algorithm
= algorithm
,
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
;
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
)) {
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
,
419 sizeof short_ctx
.buf
,
423 if (!NT_STATUS_IS_OK(status
)) {
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(
438 &security_descriptor_ctx
);
439 if (!NT_STATUS_IS_OK(status
)) {
443 status
= samba_gnutls_sp800_108_derive_key(
450 security_descriptor_ctx
.data
,
451 security_descriptor_ctx
.length
,
455 data_blob_free(&security_descriptor_ctx
);
456 if (!NT_STATUS_IS_OK(status
)) {
461 for (n
= 30; n
>= gkid
.l1_idx
; --n
) {
462 /* Derive an L1 seed key with GKID = (L0, n, −1). */
466 status
= make_gkdi_context(&ctx
->ctx
, &short_ctx
);
467 if (!NT_STATUS_IS_OK(status
)) {
471 status
= samba_gnutls_sp800_108_derive_key(key
,
478 sizeof short_ctx
.buf
,
482 if (!NT_STATUS_IS_OK(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
;
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). */
508 status
= make_gkdi_context(&ctx
->ctx
, &short_ctx
);
509 if (!NT_STATUS_IS_OK(status
)) {
513 status
= samba_gnutls_sp800_108_derive_key(key
,
520 sizeof short_ctx
.buf
,
524 if (!NT_STATUS_IS_OK(status
)) {
533 enum GkidType
gkid_key_type(const struct Gkid gkid
)
535 if (gkid
.l0_idx
== -1) {
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) {
556 if (gkid
.l1_idx
< -1 || gkid
.l1_idx
>= gkdi_l1_key_iteration
) {
560 if (gkid
.l2_idx
< -1 || gkid
.l2_idx
>= gkdi_l2_key_iteration
) {
564 if (gkid
.l0_idx
== -1 && gkid
.l1_idx
!= -1) {
568 if (gkid
.l1_idx
== -1 && gkid
.l2_idx
!= -1) {
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
;
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
;
597 status
= GkdiContext(root_key
, &ctx
);
598 if (!NT_STATUS_IS_OK(status
)) {
602 status
= compute_l1_seed_key(
603 mem_ctx
, &ctx
, target_security_descriptor
, root_key
, gkid
, key
);
604 if (!NT_STATUS_IS_OK(status
)) {
608 if (gkid_type
== GKID_L2_SEED_KEY
) {
609 status
= derive_l2_seed_key(&ctx
, gkid
, key
);
610 if (!NT_STATUS_IS_OK(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
;
636 err
= ndr_pull_struct_blob(kdf_param
,
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",
648 if (kdf_parameters
.hash_algorithm
== NULL
) {
649 status
= NT_STATUS_NOT_SUPPORTED
;
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
;
666 status
= NT_STATUS_NOT_SUPPORTED
;
671 *kdf_algorithm_out
= (struct KdfAlgorithm
){
672 .id
= KDF_ALGORITHM_SP800_108_CTR_HMAC
,
673 .param
.sp800_108
= sp800_108_param
,
676 talloc_free(tmp_ctx
);
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
;