mirror of
https://github.com/samba-team/samba.git
synced 2024-12-22 13:34:15 +03:00
7b7fdfbce3
Signed-off-by: Jo Sutton <josutton@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
269 lines
6.9 KiB
C
269 lines
6.9 KiB
C
/*
|
||
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 <https://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include "includes.h"
|
||
#include <gnutls/gnutls.h>
|
||
#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;
|
||
}
|