1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-22 13:34:15 +03:00
samba-mirror/lib/crypto/gkdi.c
Joseph Sutton 9f16157f10 lib:crypto: Add implementation of GKDI key derivation
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2023-12-22 05:33:31 +00:00

397 lines
8.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
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 NULLterminated UTF16LE 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) {
/* Dont 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;
}