diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /lib/crypto/gkdi.c | |
parent | Initial commit. (diff) | |
download | samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip |
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/crypto/gkdi.c')
-rw-r--r-- | lib/crypto/gkdi.c | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/lib/crypto/gkdi.c b/lib/crypto/gkdi.c new file mode 100644 index 0000000..6799dcf --- /dev/null +++ b/lib/crypto/gkdi.c @@ -0,0 +1,396 @@ +/* + 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 <https://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#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; +} |