Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 : Group Key Distribution Protocol functions
4 :
5 : Copyright (C) Catalyst.Net Ltd 2023
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 <gnutls/crypto.h>
24 :
25 : #include "lib/crypto/gnutls_helpers.h"
26 :
27 : #include "lib/util/bytearray.h"
28 :
29 : #include "librpc/gen_ndr/ndr_security.h"
30 : #include "librpc/gen_ndr/gkdi.h"
31 : #include "librpc/gen_ndr/ndr_gkdi.h"
32 :
33 : #include "lib/crypto/gkdi.h"
34 :
35 : static const uint8_t kds_service[] = {
36 : /* “KDS service” as a NULL‐terminated UTF‐16LE string. */
37 : 'K', 0, 'D', 0, 'S', 0, ' ', 0, 's', 0, 'e', 0,
38 : 'r', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, 0, 0,
39 : };
40 :
41 : struct GkdiContextShort {
42 : uint8_t buf[sizeof((struct GUID_ndr_buf){}.buf) + sizeof(int32_t) +
43 : sizeof(int32_t) + sizeof(int32_t)];
44 : };
45 :
46 282 : static NTSTATUS make_gkdi_context(const struct GkdiDerivationCtx *ctx,
47 : struct GkdiContextShort *out_ctx)
48 : {
49 282 : enum ndr_err_code ndr_err;
50 282 : DATA_BLOB b = {.data = out_ctx->buf, .length = sizeof out_ctx->buf};
51 :
52 282 : if (ctx->target_security_descriptor.length) {
53 0 : return NT_STATUS_INVALID_PARAMETER;
54 : }
55 :
56 282 : ndr_err = ndr_push_struct_into_fixed_blob(
57 : &b, ctx, (ndr_push_flags_fn_t)ndr_push_GkdiDerivationCtx);
58 282 : if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
59 0 : return ndr_map_error2ntstatus(ndr_err);
60 : }
61 :
62 282 : return NT_STATUS_OK;
63 : }
64 :
65 12 : static NTSTATUS make_gkdi_context_security_descriptor(
66 : TALLOC_CTX *mem_ctx,
67 : const struct GkdiDerivationCtx *ctx,
68 : const DATA_BLOB security_descriptor,
69 : DATA_BLOB *out_ctx)
70 : {
71 12 : enum ndr_err_code ndr_err;
72 12 : struct GkdiDerivationCtx ctx_with_sd = *ctx;
73 :
74 12 : if (ctx_with_sd.target_security_descriptor.length) {
75 0 : return NT_STATUS_INVALID_PARAMETER;
76 : }
77 :
78 12 : ctx_with_sd.target_security_descriptor = security_descriptor;
79 :
80 12 : ndr_err = ndr_push_struct_blob(out_ctx,
81 : mem_ctx,
82 : &ctx_with_sd,
83 : (ndr_push_flags_fn_t)
84 : ndr_push_GkdiDerivationCtx);
85 12 : if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
86 0 : return ndr_map_error2ntstatus(ndr_err);
87 : }
88 :
89 12 : return NT_STATUS_OK;
90 : }
91 :
92 : struct GkdiContext {
93 : struct GkdiDerivationCtx ctx;
94 : gnutls_mac_algorithm_t algorithm;
95 : };
96 :
97 13 : gnutls_mac_algorithm_t get_sp800_108_mac_algorithm(
98 : const struct KdfAlgorithm kdf_algorithm)
99 : {
100 13 : switch (kdf_algorithm.id) {
101 13 : case KDF_ALGORITHM_SP800_108_CTR_HMAC:
102 13 : switch (kdf_algorithm.param.sp800_108) {
103 0 : case KDF_PARAM_SHA1:
104 0 : return GNUTLS_MAC_SHA1;
105 0 : case KDF_PARAM_SHA256:
106 0 : return GNUTLS_MAC_SHA256;
107 0 : case KDF_PARAM_SHA384:
108 0 : return GNUTLS_MAC_SHA384;
109 0 : case KDF_PARAM_SHA512:
110 0 : return GNUTLS_MAC_SHA512;
111 : }
112 0 : break;
113 : }
114 :
115 0 : return GNUTLS_MAC_UNKNOWN;
116 : }
117 :
118 16 : static NTSTATUS GkdiContext(const struct ProvRootKey *const root_key,
119 : struct GkdiContext *const ctx)
120 : {
121 16 : NTSTATUS status = NT_STATUS_OK;
122 16 : gnutls_mac_algorithm_t algorithm = GNUTLS_MAC_UNKNOWN;
123 :
124 16 : if (ctx == NULL) {
125 0 : status = NT_STATUS_INVALID_PARAMETER;
126 0 : goto out;
127 : }
128 :
129 16 : if (root_key == NULL) {
130 0 : status = NT_STATUS_INVALID_PARAMETER;
131 0 : goto out;
132 : }
133 :
134 16 : if (root_key->version != root_key_version_1) {
135 2 : status = NT_STATUS_NOT_SUPPORTED;
136 2 : goto out;
137 : }
138 :
139 14 : if (root_key->data.length != GKDI_KEY_LEN) {
140 1 : status = NT_STATUS_NOT_SUPPORTED;
141 1 : goto out;
142 : }
143 :
144 13 : algorithm = get_sp800_108_mac_algorithm(root_key->kdf_algorithm);
145 13 : if (algorithm == GNUTLS_MAC_UNKNOWN) {
146 1 : status = NT_STATUS_NOT_SUPPORTED;
147 1 : goto out;
148 : }
149 :
150 : /*
151 : * The context comprises the GUID corresponding to the root key, the
152 : * GKID (which we shall initialize to zero), and the encoded target
153 : * security descriptor (which will initially be empty).
154 : */
155 12 : *ctx = (struct GkdiContext){
156 0 : .ctx = {.guid = root_key->id,
157 : .l0_idx = 0,
158 : .l1_idx = 0,
159 : .l2_idx = 0,
160 : .target_security_descriptor = {}},
161 : .algorithm = algorithm,
162 : };
163 16 : out:
164 16 : return status;
165 : }
166 :
167 12 : static NTSTATUS compute_l1_seed_key(
168 : TALLOC_CTX *mem_ctx,
169 : struct GkdiContext *ctx,
170 : const DATA_BLOB security_descriptor,
171 : const struct ProvRootKey *const root_key,
172 : const struct Gkid gkid,
173 : uint8_t key[static const GKDI_KEY_LEN])
174 : {
175 12 : NTSTATUS status = NT_STATUS_OK;
176 12 : struct GkdiContextShort short_ctx;
177 12 : int8_t n;
178 :
179 12 : ctx->ctx.l0_idx = gkid.l0_idx;
180 12 : ctx->ctx.l1_idx = -1;
181 12 : ctx->ctx.l2_idx = -1;
182 :
183 12 : status = make_gkdi_context(&ctx->ctx, &short_ctx);
184 12 : if (!NT_STATUS_IS_OK(status)) {
185 0 : goto out;
186 : }
187 :
188 : /* Derive an L0 seed key with GKID = (L0, −1, −1). */
189 :
190 24 : status = samba_gnutls_sp800_108_derive_key(root_key->data.data,
191 12 : root_key->data.length,
192 : NULL,
193 : 0,
194 : kds_service,
195 : sizeof kds_service,
196 : short_ctx.buf,
197 : sizeof short_ctx.buf,
198 : ctx->algorithm,
199 : key,
200 : GKDI_KEY_LEN);
201 12 : if (!NT_STATUS_IS_OK(status)) {
202 0 : goto out;
203 : }
204 :
205 : /* Derive an L1 seed key with GKID = (L0, 31, −1). */
206 :
207 12 : ctx->ctx.l1_idx = 31;
208 :
209 : {
210 12 : DATA_BLOB security_descriptor_ctx;
211 :
212 12 : status = make_gkdi_context_security_descriptor(
213 : mem_ctx,
214 0 : &ctx->ctx,
215 : security_descriptor,
216 : &security_descriptor_ctx);
217 12 : if (!NT_STATUS_IS_OK(status)) {
218 0 : goto out;
219 : }
220 :
221 24 : status = samba_gnutls_sp800_108_derive_key(
222 : key,
223 : GKDI_KEY_LEN,
224 : NULL,
225 : 0,
226 : kds_service,
227 : sizeof kds_service,
228 12 : security_descriptor_ctx.data,
229 : security_descriptor_ctx.length,
230 : ctx->algorithm,
231 : key,
232 : GKDI_KEY_LEN);
233 12 : data_blob_free(&security_descriptor_ctx);
234 12 : if (!NT_STATUS_IS_OK(status)) {
235 0 : goto out;
236 : }
237 : }
238 :
239 165 : for (n = 30; n >= gkid.l1_idx; --n) {
240 : /* Derive an L1 seed key with GKID = (L0, n, −1). */
241 :
242 153 : ctx->ctx.l1_idx = n;
243 :
244 153 : status = make_gkdi_context(&ctx->ctx, &short_ctx);
245 153 : if (!NT_STATUS_IS_OK(status)) {
246 0 : goto out;
247 : }
248 :
249 153 : status = samba_gnutls_sp800_108_derive_key(key,
250 : GKDI_KEY_LEN,
251 : NULL,
252 : 0,
253 : kds_service,
254 : sizeof kds_service,
255 : short_ctx.buf,
256 : sizeof short_ctx.buf,
257 : ctx->algorithm,
258 : key,
259 : GKDI_KEY_LEN);
260 153 : if (!NT_STATUS_IS_OK(status)) {
261 0 : goto out;
262 : }
263 : }
264 :
265 12 : out:
266 12 : return status;
267 : }
268 :
269 6 : static NTSTATUS derive_l2_seed_key(struct GkdiContext *ctx,
270 : const struct Gkid gkid,
271 : uint8_t key[static const GKDI_KEY_LEN])
272 : {
273 6 : NTSTATUS status = NT_STATUS_OK;
274 6 : int8_t n;
275 :
276 6 : ctx->ctx.l0_idx = gkid.l0_idx;
277 6 : ctx->ctx.l1_idx = gkid.l1_idx;
278 :
279 123 : for (n = 31; n >= gkid.l2_idx; --n) {
280 117 : struct GkdiContextShort short_ctx;
281 :
282 : /* Derive an L2 seed key with GKID = (L0, L1, n). */
283 :
284 117 : ctx->ctx.l2_idx = n;
285 :
286 117 : status = make_gkdi_context(&ctx->ctx, &short_ctx);
287 117 : if (!NT_STATUS_IS_OK(status)) {
288 0 : goto out;
289 : }
290 :
291 117 : status = samba_gnutls_sp800_108_derive_key(key,
292 : GKDI_KEY_LEN,
293 : NULL,
294 : 0,
295 : kds_service,
296 : sizeof kds_service,
297 : short_ctx.buf,
298 : sizeof short_ctx.buf,
299 : ctx->algorithm,
300 : key,
301 : GKDI_KEY_LEN);
302 117 : if (!NT_STATUS_IS_OK(status)) {
303 0 : goto out;
304 : }
305 : }
306 :
307 6 : out:
308 6 : return status;
309 : }
310 :
311 18 : static enum GkidType gkid_key_type(const struct Gkid gkid)
312 : {
313 18 : if (gkid.l0_idx == -1) {
314 0 : return GKID_DEFAULT;
315 : }
316 :
317 17 : if (gkid.l1_idx == -1) {
318 0 : return GKID_L0_SEED_KEY;
319 : }
320 :
321 16 : if (gkid.l2_idx == -1) {
322 0 : return GKID_L1_SEED_KEY;
323 : }
324 :
325 0 : return GKID_L2_SEED_KEY;
326 : }
327 :
328 25 : static bool gkid_is_valid(const struct Gkid gkid)
329 : {
330 25 : if (gkid.l0_idx < -1) {
331 0 : return false;
332 : }
333 :
334 24 : if (gkid.l1_idx < -1 || gkid.l1_idx >= gkdi_l1_key_iteration) {
335 0 : return false;
336 : }
337 :
338 22 : if (gkid.l2_idx < -1 || gkid.l2_idx >= gkdi_l2_key_iteration) {
339 0 : return false;
340 : }
341 :
342 20 : if (gkid.l0_idx == -1 && gkid.l1_idx != -1) {
343 0 : return false;
344 : }
345 :
346 19 : if (gkid.l1_idx == -1 && gkid.l2_idx != -1) {
347 0 : return false;
348 : }
349 :
350 0 : return true;
351 : }
352 :
353 25 : NTSTATUS compute_seed_key(
354 : TALLOC_CTX *mem_ctx,
355 : const DATA_BLOB target_security_descriptor,
356 : const struct ProvRootKey *const root_key,
357 : const struct Gkid gkid,
358 : uint8_t key[static const GKDI_KEY_LEN])
359 : {
360 25 : NTSTATUS status = NT_STATUS_OK;
361 25 : enum GkidType gkid_type;
362 25 : struct GkdiContext ctx;
363 :
364 25 : if (!gkid_is_valid(gkid)) {
365 7 : status = NT_STATUS_INVALID_PARAMETER;
366 7 : goto out;
367 : }
368 :
369 18 : gkid_type = gkid_key_type(gkid);
370 10 : if (gkid_type < GKID_L1_SEED_KEY) {
371 : /* Don’t allow derivation of L0 seed keys. */
372 2 : status = NT_STATUS_INVALID_PARAMETER;
373 2 : goto out;
374 : }
375 :
376 16 : status = GkdiContext(root_key, &ctx);
377 16 : if (!NT_STATUS_IS_OK(status)) {
378 4 : goto out;
379 : }
380 :
381 12 : status = compute_l1_seed_key(
382 : mem_ctx, &ctx, target_security_descriptor, root_key, gkid, key);
383 12 : if (!NT_STATUS_IS_OK(status)) {
384 0 : goto out;
385 : }
386 :
387 12 : if (gkid_type == GKID_L2_SEED_KEY) {
388 6 : status = derive_l2_seed_key(&ctx, gkid, key);
389 6 : if (!NT_STATUS_IS_OK(status)) {
390 0 : goto out;
391 : }
392 : }
393 :
394 12 : out:
395 25 : return status;
396 : }
|