/* Unix SMB/CIFS implementation. Group Key Distribution Protocol functions Copyright (C) Catalyst.Net Ltd 2023 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include #include #include "lib/crypto/gnutls_helpers.h" #include "lib/util/bytearray.h" #include "librpc/gen_ndr/ndr_security.h" #include "librpc/gen_ndr/gkdi.h" #include "librpc/gen_ndr/ndr_gkdi.h" #include "lib/crypto/gkdi.h" static const uint8_t kds_service[] = { /* “KDS service” as a NULL‐terminated UTF‐16LE string. */ 'K', 0, 'D', 0, 'S', 0, ' ', 0, 's', 0, 'e', 0, 'r', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, 0, 0, }; struct GkdiContextShort { uint8_t buf[sizeof((struct GUID_ndr_buf){}.buf) + sizeof(int32_t) + sizeof(int32_t) + sizeof(int32_t)]; }; static NTSTATUS make_gkdi_context(const struct GkdiDerivationCtx *ctx, struct GkdiContextShort *out_ctx) { enum ndr_err_code ndr_err; DATA_BLOB b = {.data = out_ctx->buf, .length = sizeof out_ctx->buf}; if (ctx->target_security_descriptor.length) { return NT_STATUS_INVALID_PARAMETER; } ndr_err = ndr_push_struct_into_fixed_blob( &b, ctx, (ndr_push_flags_fn_t)ndr_push_GkdiDerivationCtx); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return ndr_map_error2ntstatus(ndr_err); } return NT_STATUS_OK; } static NTSTATUS make_gkdi_context_security_descriptor( TALLOC_CTX *mem_ctx, const struct GkdiDerivationCtx *ctx, const DATA_BLOB security_descriptor, DATA_BLOB *out_ctx) { enum ndr_err_code ndr_err; struct GkdiDerivationCtx ctx_with_sd = *ctx; if (ctx_with_sd.target_security_descriptor.length) { return NT_STATUS_INVALID_PARAMETER; } ctx_with_sd.target_security_descriptor = security_descriptor; ndr_err = ndr_push_struct_blob(out_ctx, mem_ctx, &ctx_with_sd, (ndr_push_flags_fn_t) ndr_push_GkdiDerivationCtx); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return ndr_map_error2ntstatus(ndr_err); } return NT_STATUS_OK; } struct GkdiContext { struct GkdiDerivationCtx ctx; gnutls_mac_algorithm_t algorithm; }; gnutls_mac_algorithm_t get_sp800_108_mac_algorithm( const struct KdfAlgorithm kdf_algorithm) { switch (kdf_algorithm.id) { case KDF_ALGORITHM_SP800_108_CTR_HMAC: switch (kdf_algorithm.param.sp800_108) { case KDF_PARAM_SHA1: return GNUTLS_MAC_SHA1; case KDF_PARAM_SHA256: return GNUTLS_MAC_SHA256; case KDF_PARAM_SHA384: return GNUTLS_MAC_SHA384; case KDF_PARAM_SHA512: return GNUTLS_MAC_SHA512; } break; } return GNUTLS_MAC_UNKNOWN; } static NTSTATUS GkdiContext(const struct ProvRootKey *const root_key, struct GkdiContext *const ctx) { NTSTATUS status = NT_STATUS_OK; gnutls_mac_algorithm_t algorithm = GNUTLS_MAC_UNKNOWN; if (ctx == NULL) { status = NT_STATUS_INVALID_PARAMETER; goto out; } if (root_key == NULL) { status = NT_STATUS_INVALID_PARAMETER; goto out; } if (root_key->version != root_key_version_1) { status = NT_STATUS_NOT_SUPPORTED; goto out; } if (root_key->data.length != GKDI_KEY_LEN) { status = NT_STATUS_NOT_SUPPORTED; goto out; } algorithm = get_sp800_108_mac_algorithm(root_key->kdf_algorithm); if (algorithm == GNUTLS_MAC_UNKNOWN) { status = NT_STATUS_NOT_SUPPORTED; goto out; } /* * The context comprises the GUID corresponding to the root key, the * GKID (which we shall initialize to zero), and the encoded target * security descriptor (which will initially be empty). */ *ctx = (struct GkdiContext){ .ctx = {.guid = root_key->id, .l0_idx = 0, .l1_idx = 0, .l2_idx = 0, .target_security_descriptor = {}}, .algorithm = algorithm, }; out: return status; } static NTSTATUS compute_l1_seed_key( TALLOC_CTX *mem_ctx, struct GkdiContext *ctx, const DATA_BLOB security_descriptor, const struct ProvRootKey *const root_key, const struct Gkid gkid, uint8_t key[static const GKDI_KEY_LEN]) { NTSTATUS status = NT_STATUS_OK; struct GkdiContextShort short_ctx; int8_t n; ctx->ctx.l0_idx = gkid.l0_idx; ctx->ctx.l1_idx = -1; ctx->ctx.l2_idx = -1; status = make_gkdi_context(&ctx->ctx, &short_ctx); if (!NT_STATUS_IS_OK(status)) { goto out; } /* Derive an L0 seed key with GKID = (L0, −1, −1). */ status = samba_gnutls_sp800_108_derive_key(root_key->data.data, root_key->data.length, NULL, 0, kds_service, sizeof kds_service, short_ctx.buf, sizeof short_ctx.buf, ctx->algorithm, key, GKDI_KEY_LEN); if (!NT_STATUS_IS_OK(status)) { goto out; } /* Derive an L1 seed key with GKID = (L0, 31, −1). */ ctx->ctx.l1_idx = 31; { DATA_BLOB security_descriptor_ctx; status = make_gkdi_context_security_descriptor( mem_ctx, &ctx->ctx, security_descriptor, &security_descriptor_ctx); if (!NT_STATUS_IS_OK(status)) { goto out; } status = samba_gnutls_sp800_108_derive_key( key, GKDI_KEY_LEN, NULL, 0, kds_service, sizeof kds_service, security_descriptor_ctx.data, security_descriptor_ctx.length, ctx->algorithm, key, GKDI_KEY_LEN); data_blob_free(&security_descriptor_ctx); if (!NT_STATUS_IS_OK(status)) { goto out; } } for (n = 30; n >= gkid.l1_idx; --n) { /* Derive an L1 seed key with GKID = (L0, n, −1). */ ctx->ctx.l1_idx = n; status = make_gkdi_context(&ctx->ctx, &short_ctx); if (!NT_STATUS_IS_OK(status)) { goto out; } status = samba_gnutls_sp800_108_derive_key(key, GKDI_KEY_LEN, NULL, 0, kds_service, sizeof kds_service, short_ctx.buf, sizeof short_ctx.buf, ctx->algorithm, key, GKDI_KEY_LEN); if (!NT_STATUS_IS_OK(status)) { goto out; } } out: return status; } static NTSTATUS derive_l2_seed_key(struct GkdiContext *ctx, const struct Gkid gkid, uint8_t key[static const GKDI_KEY_LEN]) { NTSTATUS status = NT_STATUS_OK; int8_t n; ctx->ctx.l0_idx = gkid.l0_idx; ctx->ctx.l1_idx = gkid.l1_idx; for (n = 31; n >= gkid.l2_idx; --n) { struct GkdiContextShort short_ctx; /* Derive an L2 seed key with GKID = (L0, L1, n). */ ctx->ctx.l2_idx = n; status = make_gkdi_context(&ctx->ctx, &short_ctx); if (!NT_STATUS_IS_OK(status)) { goto out; } status = samba_gnutls_sp800_108_derive_key(key, GKDI_KEY_LEN, NULL, 0, kds_service, sizeof kds_service, short_ctx.buf, sizeof short_ctx.buf, ctx->algorithm, key, GKDI_KEY_LEN); if (!NT_STATUS_IS_OK(status)) { goto out; } } out: return status; } static enum GkidType gkid_key_type(const struct Gkid gkid) { if (gkid.l0_idx == -1) { return GKID_DEFAULT; } if (gkid.l1_idx == -1) { return GKID_L0_SEED_KEY; } if (gkid.l2_idx == -1) { return GKID_L1_SEED_KEY; } return GKID_L2_SEED_KEY; } static bool gkid_is_valid(const struct Gkid gkid) { if (gkid.l0_idx < -1) { return false; } if (gkid.l1_idx < -1 || gkid.l1_idx >= gkdi_l1_key_iteration) { return false; } if (gkid.l2_idx < -1 || gkid.l2_idx >= gkdi_l2_key_iteration) { return false; } if (gkid.l0_idx == -1 && gkid.l1_idx != -1) { return false; } if (gkid.l1_idx == -1 && gkid.l2_idx != -1) { return false; } return true; } NTSTATUS compute_seed_key( TALLOC_CTX *mem_ctx, const DATA_BLOB target_security_descriptor, const struct ProvRootKey *const root_key, const struct Gkid gkid, uint8_t key[static const GKDI_KEY_LEN]) { NTSTATUS status = NT_STATUS_OK; enum GkidType gkid_type; struct GkdiContext ctx; if (!gkid_is_valid(gkid)) { status = NT_STATUS_INVALID_PARAMETER; goto out; } gkid_type = gkid_key_type(gkid); if (gkid_type < GKID_L1_SEED_KEY) { /* Don’t allow derivation of L0 seed keys. */ status = NT_STATUS_INVALID_PARAMETER; goto out; } status = GkdiContext(root_key, &ctx); if (!NT_STATUS_IS_OK(status)) { goto out; } status = compute_l1_seed_key( mem_ctx, &ctx, target_security_descriptor, root_key, gkid, key); if (!NT_STATUS_IS_OK(status)) { goto out; } if (gkid_type == GKID_L2_SEED_KEY) { status = derive_l2_seed_key(&ctx, gkid, key); if (!NT_STATUS_IS_OK(status)) { goto out; } } out: return status; }