Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 : Group Managed Service Account functions
4 :
5 : Copyright (C) Catalyst.Net Ltd 2024
6 :
7 : This program is free software: you can redistribute it and/or modify
8 : it under the terms of the GNU General Public License as published by
9 : the Free Software Foundation, either version 3 of the License, or
10 : (at your option) any later version.
11 :
12 : This program is distributed in the hope that it will be useful,
13 : but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : GNU General Public License for more details.
16 :
17 : You should have received a copy of the GNU General Public License
18 : along with this program. If not, see <https://www.gnu.org/licenses/>.
19 : */
20 :
21 : #include "includes.h"
22 : #include <gnutls/gnutls.h>
23 : #include "lib/crypto/gnutls_helpers.h"
24 : #include "lib/crypto/gkdi.h"
25 : #include "lib/crypto/gmsa.h"
26 : #include "librpc/gen_ndr/ndr_security.h"
27 :
28 : static const uint8_t gmsa_security_descriptor[] = {
29 : /* O:SYD:(A;;FRFW;;;S-1-5-9) */
30 : 0x01, 0x00, 0x04, 0x80, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
31 : 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x1c, 0x00,
32 : 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x9f, 0x01, 0x12, 0x00,
33 : 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x09, 0x00, 0x00, 0x00,
34 : 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00};
35 :
36 : static const uint8_t gmsa_password_label[] = {
37 : /* GMSA PASSWORD as a NULL‐terminated UTF‐16LE string. */
38 : 'G', 0, 'M', 0, 'S', 0, 'A', 0, ' ', 0, 'P', 0, 'A', 0,
39 : 'S', 0, 'S', 0, 'W', 0, 'O', 0, 'R', 0, 'D', 0, 0, 0,
40 : };
41 :
42 102 : static NTSTATUS generate_gmsa_password(
43 : const uint8_t key[static const GKDI_KEY_LEN],
44 : const struct dom_sid *const account_sid,
45 : const struct KdfAlgorithm kdf_algorithm,
46 : uint8_t password[static const GMSA_PASSWORD_LEN])
47 : {
48 102 : NTSTATUS status = NT_STATUS_OK;
49 1 : gnutls_mac_algorithm_t algorithm;
50 :
51 102 : algorithm = get_sp800_108_mac_algorithm(kdf_algorithm);
52 102 : if (algorithm == GNUTLS_MAC_UNKNOWN) {
53 0 : status = NT_STATUS_NOT_SUPPORTED;
54 0 : goto out;
55 : }
56 :
57 102 : if (account_sid == NULL) {
58 0 : status = NT_STATUS_INVALID_PARAMETER;
59 0 : goto out;
60 : }
61 :
62 102 : {
63 102 : uint8_t encoded_sid[ndr_size_dom_sid(account_sid, 0)];
64 : {
65 102 : struct ndr_push ndr = {
66 : .data = encoded_sid,
67 : .alloc_size = sizeof encoded_sid,
68 : .fixed_buf_size = true,
69 : };
70 1 : enum ndr_err_code ndr_err;
71 :
72 102 : ndr_err = ndr_push_dom_sid(&ndr,
73 : NDR_SCALARS | NDR_BUFFERS,
74 : account_sid);
75 102 : if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
76 0 : status = ndr_map_error2ntstatus(ndr_err);
77 0 : goto out;
78 : }
79 : }
80 :
81 102 : status = samba_gnutls_sp800_108_derive_key(
82 : key,
83 : GKDI_KEY_LEN,
84 : NULL,
85 : 0,
86 : gmsa_password_label,
87 : sizeof gmsa_password_label,
88 : encoded_sid,
89 : sizeof encoded_sid,
90 : algorithm,
91 : password,
92 : GMSA_PASSWORD_LEN);
93 102 : if (!NT_STATUS_IS_OK(status)) {
94 0 : goto out;
95 : }
96 : }
97 :
98 102 : out:
99 102 : return status;
100 : }
101 :
102 102 : static void gmsa_post_process_password_buffer(
103 : uint8_t password[static const GMSA_PASSWORD_NULL_TERMINATED_LEN])
104 : {
105 1 : size_t n;
106 :
107 13158 : for (n = 0; n < GMSA_PASSWORD_LEN; n += 2) {
108 13056 : const uint8_t a = password[n];
109 13056 : const uint8_t b = password[n + 1];
110 13056 : if (!a && !b) {
111 : /*
112 : * There is a 0.2% chance that the generated password
113 : * will contain an embedded null terminator, which will
114 : * need to be converted into U+0001.
115 : */
116 0 : password[n] = 1;
117 : }
118 : }
119 :
120 : /* Null‐terminate the password. */
121 102 : password[GMSA_PASSWORD_LEN] = 0;
122 102 : password[GMSA_PASSWORD_LEN + 1] = 0;
123 102 : }
124 :
125 102 : NTSTATUS gmsa_password_based_on_key_id(
126 : TALLOC_CTX *mem_ctx,
127 : const struct Gkid gkid,
128 : const NTTIME current_time,
129 : const struct ProvRootKey *const root_key,
130 : const struct dom_sid *const account_sid,
131 : uint8_t password[static const GMSA_PASSWORD_NULL_TERMINATED_LEN])
132 : {
133 102 : NTSTATUS status = NT_STATUS_OK;
134 :
135 : /* Ensure that a specific seed key is being requested. */
136 :
137 102 : if (!gkid_is_valid(gkid)) {
138 0 : status = NT_STATUS_INVALID_PARAMETER;
139 0 : goto out;
140 : }
141 :
142 102 : if (gkid_key_type(gkid) != GKID_L2_SEED_KEY) {
143 0 : status = NT_STATUS_INVALID_PARAMETER;
144 0 : goto out;
145 : }
146 :
147 : /* Require the root key ID for the moment. */
148 102 : if (root_key == NULL) {
149 0 : status = NT_STATUS_INVALID_PARAMETER;
150 0 : goto out;
151 : }
152 :
153 : /* Assert that the root key may be used at this time. */
154 102 : if (current_time < root_key->use_start_time) {
155 0 : status = NT_STATUS_INVALID_PARAMETER;
156 0 : goto out;
157 : }
158 :
159 : {
160 : /*
161 : * The key being requested must not be from the future. That
162 : * said, we allow for a little bit of clock skew so that samdb
163 : * can compute the next managed password prior to the expiration
164 : * of the current one.
165 : */
166 102 : const struct Gkid current_gkid = gkdi_get_interval_id(
167 : current_time + gkdi_max_clock_skew);
168 102 : if (!gkid_less_than_or_equal_to(gkid, current_gkid)) {
169 0 : status = NT_STATUS_INVALID_PARAMETER;
170 0 : goto out;
171 : }
172 : }
173 :
174 : /*
175 : * Windows’ GetKey() might return not the specified L2 seed key, but an
176 : * earlier L2 seed key, or an L1 seed key, leaving the client to perform
177 : * the rest of the derivation. We are able to simplify things by always
178 : * deriving the specified L2 seed key, but if we implement a
179 : * client‐accessible GetKey(), we must take care that it match the
180 : * Windows implementation.
181 : */
182 :
183 : /*
184 : * Depending on the GKID that was requested, Windows’ GetKey() might
185 : * return a different L1 or L2 seed key, leaving the client with some
186 : * further derivation to do. Our simpler implementation will return
187 : * either the exact key the caller requested, or an error code if the
188 : * client is not suitably authorized.
189 : */
190 :
191 : {
192 1 : uint8_t key[GKDI_KEY_LEN];
193 :
194 102 : status = compute_seed_key(
195 : mem_ctx,
196 : data_blob_const(gmsa_security_descriptor,
197 : sizeof gmsa_security_descriptor),
198 : root_key,
199 : gkid,
200 : key);
201 102 : if (!NT_STATUS_IS_OK(status)) {
202 0 : goto out;
203 : }
204 :
205 102 : status = generate_gmsa_password(key,
206 : account_sid,
207 : root_key->kdf_algorithm,
208 : password);
209 102 : ZERO_ARRAY(key);
210 102 : if (!NT_STATUS_IS_OK(status)) {
211 0 : goto out;
212 : }
213 : }
214 :
215 102 : gmsa_post_process_password_buffer(password);
216 :
217 102 : out:
218 102 : return status;
219 : }
220 :
221 101 : NTSTATUS gmsa_talloc_password_based_on_key_id(
222 : TALLOC_CTX *mem_ctx,
223 : const struct Gkid gkid,
224 : const NTTIME current_time,
225 : const struct ProvRootKey *const root_key,
226 : const struct dom_sid *const account_sid,
227 : struct gmsa_null_terminated_password **password_out)
228 : {
229 101 : struct gmsa_null_terminated_password *password = NULL;
230 101 : NTSTATUS status = NT_STATUS_OK;
231 :
232 101 : if (password_out == NULL) {
233 0 : return NT_STATUS_INVALID_PARAMETER;
234 : }
235 :
236 101 : password = talloc(mem_ctx, struct gmsa_null_terminated_password);
237 101 : if (password == NULL) {
238 0 : return NT_STATUS_NO_MEMORY;
239 : }
240 :
241 101 : status = gmsa_password_based_on_key_id(mem_ctx,
242 : gkid,
243 : current_time,
244 : root_key,
245 : account_sid,
246 101 : password->buf);
247 101 : if (!NT_STATUS_IS_OK(status)) {
248 0 : talloc_free(password);
249 0 : return status;
250 : }
251 :
252 101 : *password_out = password;
253 101 : return status;
254 : }
255 :
256 63 : bool gmsa_current_time(NTTIME *current_time_out)
257 : {
258 0 : struct timespec current_timespec;
259 0 : int ret;
260 :
261 63 : ret = clock_gettime(CLOCK_REALTIME, ¤t_timespec);
262 63 : if (ret) {
263 0 : return false;
264 : }
265 :
266 63 : *current_time_out = full_timespec_to_nt_time(¤t_timespec);
267 63 : return true;
268 : }
|