From fe2dc161607ad035d805c035e7c090f7b4b13483 Mon Sep 17 00:00:00 2001 From: Jo Sutton Date: Tue, 13 Feb 2024 13:04:06 +1300 Subject: [PATCH] lib:crypto: Add functions for deriving gMSA passwords Signed-off-by: Jo Sutton Reviewed-by: Andrew Bartlett --- lib/crypto/gmsa.c | 264 +++++++++++++++++++++++++++++++++++++++++++++ lib/crypto/gmsa.h | 54 ++++++++++ lib/crypto/wscript | 6 ++ 3 files changed, 324 insertions(+) create mode 100644 lib/crypto/gmsa.c create mode 100644 lib/crypto/gmsa.h diff --git a/lib/crypto/gmsa.c b/lib/crypto/gmsa.c new file mode 100644 index 00000000000..1cd7a0e6973 --- /dev/null +++ b/lib/crypto/gmsa.c @@ -0,0 +1,264 @@ +/* + 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; +} diff --git a/lib/crypto/gmsa.h b/lib/crypto/gmsa.h new file mode 100644 index 00000000000..e9937621517 --- /dev/null +++ b/lib/crypto/gmsa.h @@ -0,0 +1,54 @@ +/* + 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 . +*/ + +#ifndef LIB_CRYPTO_GMSA_H +#define LIB_CRYPTO_GMSA_H + +#include "lib/crypto/gkdi.h" + +enum { + GMSA_PASSWORD_LEN = 256, + GMSA_PASSWORD_NULL_TERMINATED_LEN = GMSA_PASSWORD_LEN + 2, +}; + +struct gmsa_null_terminated_password { + uint8_t buf[GMSA_PASSWORD_NULL_TERMINATED_LEN]; +}; + +struct dom_sid; +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 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); + +bool gmsa_current_time(NTTIME *current_time_out); + +#endif /* LIB_CRYPTO_GMSA_H */ diff --git a/lib/crypto/wscript b/lib/crypto/wscript index eaa18c55257..d9bd893879b 100644 --- a/lib/crypto/wscript +++ b/lib/crypto/wscript @@ -35,6 +35,12 @@ def build(bld): NDR_GKDI ''') + bld.SAMBA_SUBSYSTEM('gmsa', + source='gmsa.c', + deps=''' + gkdi + ''') + bld.SAMBA_PYTHON('python_crypto', source='py_crypto.c', deps='gnutls talloc LIBCLI_AUTH',