/*
Unix SMB/CIFS implementation.
Group Managed Service Account functions
Copyright (C) Catalyst.Net Ltd 2024
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 "lib/crypto/gnutls_helpers.h"
#include "lib/crypto/gkdi.h"
#include "lib/crypto/gmsa.h"
#include "librpc/gen_ndr/ndr_security.h"
static const uint8_t gmsa_security_descriptor[] = {
/* O:SYD:(A;;FRFW;;;S-1-5-9) */
0x01, 0x00, 0x04, 0x80, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x1c, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x9f, 0x01, 0x12, 0x00,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x09, 0x00, 0x00, 0x00,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00};
static const uint8_t gmsa_password_label[] = {
/* GMSA PASSWORD as a NULL‐terminated UTF‐16LE string. */
'G', 0, 'M', 0, 'S', 0, 'A', 0, ' ', 0, 'P', 0, 'A', 0,
'S', 0, 'S', 0, 'W', 0, 'O', 0, 'R', 0, 'D', 0, 0, 0,
};
static NTSTATUS generate_gmsa_password(
const uint8_t key[static const GKDI_KEY_LEN],
const struct dom_sid *const account_sid,
const struct KdfAlgorithm kdf_algorithm,
uint8_t password[static const GMSA_PASSWORD_LEN])
{
NTSTATUS status = NT_STATUS_OK;
gnutls_mac_algorithm_t algorithm;
algorithm = get_sp800_108_mac_algorithm(kdf_algorithm);
if (algorithm == GNUTLS_MAC_UNKNOWN) {
status = NT_STATUS_NOT_SUPPORTED;
goto out;
}
if (account_sid == NULL) {
status = NT_STATUS_INVALID_PARAMETER;
goto out;
}
{
uint8_t encoded_sid[ndr_size_dom_sid(account_sid, 0)];
{
struct ndr_push ndr = {
.data = encoded_sid,
.alloc_size = sizeof encoded_sid,
.fixed_buf_size = true,
};
enum ndr_err_code ndr_err;
ndr_err = ndr_push_dom_sid(&ndr,
NDR_SCALARS | NDR_BUFFERS,
account_sid);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
status = ndr_map_error2ntstatus(ndr_err);
goto out;
}
}
status = samba_gnutls_sp800_108_derive_key(
key,
GKDI_KEY_LEN,
NULL,
0,
gmsa_password_label,
sizeof gmsa_password_label,
encoded_sid,
sizeof encoded_sid,
algorithm,
password,
GMSA_PASSWORD_LEN);
if (!NT_STATUS_IS_OK(status)) {
goto out;
}
}
out:
return status;
}
static void gmsa_post_process_password_buffer(
uint8_t password[static const GMSA_PASSWORD_NULL_TERMINATED_LEN])
{
size_t n;
for (n = 0; n < GMSA_PASSWORD_LEN; n += 2) {
const uint8_t a = password[n];
const uint8_t b = password[n + 1];
if (!a && !b) {
/*
* There is a 0.2% chance that the generated password
* will contain an embedded null terminator, which will
* need to be converted into U+0001.
*/
password[n] = 1;
}
}
/* Null‐terminate the password. */
password[GMSA_PASSWORD_LEN] = 0;
password[GMSA_PASSWORD_LEN + 1] = 0;
}
NTSTATUS gmsa_password_based_on_key_id(
TALLOC_CTX *mem_ctx,
const struct Gkid gkid,
const NTTIME current_time,
const struct ProvRootKey *const root_key,
const struct dom_sid *const account_sid,
uint8_t password[static const GMSA_PASSWORD_NULL_TERMINATED_LEN])
{
NTSTATUS status = NT_STATUS_OK;
/* Ensure that a specific seed key is being requested. */
if (!gkid_is_valid(gkid)) {
status = NT_STATUS_INVALID_PARAMETER;
goto out;
}
if (gkid_key_type(gkid) != GKID_L2_SEED_KEY) {
status = NT_STATUS_INVALID_PARAMETER;
goto out;
}
/* Require the root key ID for the moment. */
if (root_key == NULL) {
status = NT_STATUS_INVALID_PARAMETER;
goto out;
}
/* Assert that the root key may be used at this time. */
if (current_time < root_key->use_start_time) {
status = NT_STATUS_INVALID_PARAMETER;
goto out;
}
{
/*
* The key being requested must not be from the future. That
* said, we allow for a little bit of clock skew so that samdb
* can compute the next managed password prior to the expiration
* of the current one.
*/
const struct Gkid current_gkid = gkdi_get_interval_id(
current_time + gkdi_max_clock_skew);
if (!gkid_less_than_or_equal_to(gkid, current_gkid)) {
status = NT_STATUS_INVALID_PARAMETER;
goto out;
}
}
/*
* Windows’ GetKey() might return not the specified L2 seed key, but an
* earlier L2 seed key, or an L1 seed key, leaving the client to perform
* the rest of the derivation. We are able to simplify things by always
* deriving the specified L2 seed key, but if we implement a
* client‐accessible GetKey(), we must take care that it match the
* Windows implementation.
*/
/*
* Depending on the GKID that was requested, Windows’ GetKey() might
* return a different L1 or L2 seed key, leaving the client with some
* further derivation to do. Our simpler implementation will return
* either the exact key the caller requested, or an error code if the
* client is not suitably authorized.
*/
{
uint8_t key[GKDI_KEY_LEN];
status = compute_seed_key(
mem_ctx,
data_blob_const(gmsa_security_descriptor,
sizeof gmsa_security_descriptor),
root_key,
gkid,
key);
if (!NT_STATUS_IS_OK(status)) {
goto out;
}
status = generate_gmsa_password(key,
account_sid,
root_key->kdf_algorithm,
password);
ZERO_ARRAY(key);
if (!NT_STATUS_IS_OK(status)) {
goto out;
}
}
gmsa_post_process_password_buffer(password);
out:
return status;
}
NTSTATUS gmsa_talloc_password_based_on_key_id(
TALLOC_CTX *mem_ctx,
const struct Gkid gkid,
const NTTIME current_time,
const struct ProvRootKey *const root_key,
const struct dom_sid *const account_sid,
struct gmsa_null_terminated_password **password_out)
{
struct gmsa_null_terminated_password *password = NULL;
NTSTATUS status = NT_STATUS_OK;
if (password_out == NULL) {
return NT_STATUS_INVALID_PARAMETER;
}
password = talloc(mem_ctx, struct gmsa_null_terminated_password);
if (password == NULL) {
return NT_STATUS_NO_MEMORY;
}
status = gmsa_password_based_on_key_id(mem_ctx,
gkid,
current_time,
root_key,
account_sid,
password->buf);
if (!NT_STATUS_IS_OK(status)) {
talloc_free(password);
return status;
}
*password_out = password;
return status;
}
bool gmsa_current_time(NTTIME *current_time_out)
{
struct timespec current_timespec;
int ret;
ret = clock_gettime(CLOCK_REALTIME, ¤t_timespec);
if (ret) {
return false;
}
*current_time_out = full_timespec_to_nt_time(¤t_timespec);
return true;
}