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/ndr/libndr.h"
30 : #include "librpc/gen_ndr/ndr_security.h"
31 : #include "librpc/gen_ndr/gkdi.h"
32 : #include "librpc/gen_ndr/ndr_gkdi.h"
33 :
34 : #include "lib/crypto/gkdi.h"
35 : #include "lib/util/data_blob.h"
36 :
37 : static const uint8_t kds_service[] = {
38 : /* “KDS service” as a NULL‐terminated UTF‐16LE string. */
39 : 'K', 0, 'D', 0, 'S', 0, ' ', 0, 's', 0, 'e', 0,
40 : 'r', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, 0, 0,
41 : };
42 :
43 0 : static struct Gkid gkid_from_u32_indices(const uint32_t l0_idx,
44 : const uint32_t l1_idx,
45 : const uint32_t l2_idx)
46 : {
47 : /* Catch out‐of‐range indices. */
48 0 : if (l0_idx > INT32_MAX || l1_idx > INT8_MAX || l2_idx > INT8_MAX) {
49 0 : return invalid_gkid;
50 : }
51 :
52 0 : return Gkid(l0_idx, l1_idx, l2_idx);
53 : }
54 :
55 0 : NTSTATUS gkdi_pull_KeyEnvelope(TALLOC_CTX *mem_ctx,
56 : const DATA_BLOB *key_env_blob,
57 : struct KeyEnvelope *key_env_out)
58 : {
59 0 : NTSTATUS status = NT_STATUS_OK;
60 0 : enum ndr_err_code err;
61 :
62 0 : if (key_env_blob == NULL) {
63 0 : return NT_STATUS_INVALID_PARAMETER;
64 : }
65 :
66 0 : if (key_env_out == NULL) {
67 0 : return NT_STATUS_INVALID_PARAMETER;
68 : }
69 :
70 0 : err = ndr_pull_struct_blob(key_env_blob,
71 : mem_ctx,
72 : key_env_out,
73 : (ndr_pull_flags_fn_t)ndr_pull_KeyEnvelope);
74 0 : status = ndr_map_error2ntstatus(err);
75 0 : if (!NT_STATUS_IS_OK(status)) {
76 0 : return status;
77 : }
78 :
79 : /* If we felt so inclined, we could check the version field here. */
80 :
81 0 : return status;
82 : }
83 :
84 : /*
85 : * Retrieve the GKID and root key ID from a KeyEnvelope blob. The returned
86 : * structure is guaranteed to have a valid GKID.
87 : */
88 0 : const struct KeyEnvelopeId *gkdi_pull_KeyEnvelopeId(
89 : const DATA_BLOB key_env_blob,
90 : struct KeyEnvelopeId *key_env_out)
91 : {
92 0 : TALLOC_CTX *tmp_ctx = NULL;
93 0 : struct KeyEnvelope key_env;
94 0 : const struct KeyEnvelopeId *key_env_ret = NULL;
95 0 : NTSTATUS status;
96 :
97 0 : if (key_env_out == NULL) {
98 0 : goto out;
99 : }
100 :
101 0 : tmp_ctx = talloc_new(NULL);
102 0 : if (tmp_ctx == NULL) {
103 0 : goto out;
104 : }
105 :
106 0 : status = gkdi_pull_KeyEnvelope(tmp_ctx, &key_env_blob, &key_env);
107 0 : if (!NT_STATUS_IS_OK(status)) {
108 0 : goto out;
109 : }
110 :
111 : {
112 0 : const struct Gkid gkid = gkid_from_u32_indices(
113 : key_env.l0_index, key_env.l1_index, key_env.l2_index);
114 0 : if (!gkid_is_valid(gkid)) {
115 : /* The KeyId is not valid: we can’t use it. */
116 0 : goto out;
117 : }
118 :
119 0 : *key_env_out = (struct KeyEnvelopeId){
120 : .root_key_id = key_env.root_key_id, .gkid = gkid};
121 : }
122 :
123 : /* Return a pointer to the buffer passed in by the caller. */
124 0 : key_env_ret = key_env_out;
125 :
126 0 : out:
127 0 : TALLOC_FREE(tmp_ctx);
128 0 : return key_env_ret;
129 : }
130 :
131 0 : NTSTATUS ProvRootKey(TALLOC_CTX *mem_ctx,
132 : const struct GUID root_key_id,
133 : const int32_t version,
134 : const DATA_BLOB root_key_data,
135 : const NTTIME create_time,
136 : const NTTIME use_start_time,
137 : const char *const domain_id,
138 : const struct KdfAlgorithm kdf_algorithm,
139 : const struct ProvRootKey **const root_key_out)
140 : {
141 0 : NTSTATUS status = NT_STATUS_OK;
142 0 : struct ProvRootKey *root_key = NULL;
143 :
144 0 : if (root_key_out == NULL) {
145 0 : return NT_STATUS_INVALID_PARAMETER;
146 : }
147 0 : *root_key_out = NULL;
148 :
149 0 : root_key = talloc(mem_ctx, struct ProvRootKey);
150 0 : if (root_key == NULL) {
151 0 : return NT_STATUS_NO_MEMORY;
152 : }
153 :
154 0 : *root_key = (struct ProvRootKey){
155 : .id = root_key_id,
156 0 : .data = {.data = talloc_steal(root_key, root_key_data.data),
157 0 : .length = root_key_data.length},
158 : .create_time = create_time,
159 : .use_start_time = use_start_time,
160 0 : .domain_id = talloc_steal(root_key, domain_id),
161 : .kdf_algorithm = kdf_algorithm,
162 : .version = version,
163 : };
164 :
165 0 : *root_key_out = root_key;
166 0 : return status;
167 : }
168 :
169 1 : struct Gkid gkdi_get_interval_id(const NTTIME time)
170 : {
171 1 : return Gkid(time / (gkdi_l1_key_iteration * gkdi_l2_key_iteration *
172 : gkdi_key_cycle_duration),
173 1 : time / (gkdi_l2_key_iteration * gkdi_key_cycle_duration) %
174 : gkdi_l1_key_iteration,
175 1 : time / gkdi_key_cycle_duration % gkdi_l2_key_iteration);
176 : }
177 :
178 0 : NTTIME gkdi_get_key_start_time(const struct Gkid gkid)
179 : {
180 0 : return (gkid.l0_idx * gkdi_l1_key_iteration * gkdi_l2_key_iteration +
181 0 : gkid.l1_idx * gkdi_l2_key_iteration + gkid.l2_idx) *
182 : gkdi_key_cycle_duration;
183 : }
184 :
185 : /*
186 : * This returns the equivalent of
187 : * gkdi_get_key_start_time(gkdi_get_interval_id(time)).
188 : */
189 0 : NTTIME gkdi_get_interval_start_time(const NTTIME time)
190 : {
191 0 : return time % gkdi_key_cycle_duration;
192 : }
193 :
194 1 : bool gkid_less_than_or_equal_to(const struct Gkid g1, const struct Gkid g2)
195 : {
196 1 : if (g1.l0_idx != g2.l0_idx) {
197 0 : return g1.l0_idx < g2.l0_idx;
198 : }
199 :
200 1 : if (g1.l1_idx != g2.l1_idx) {
201 1 : return g1.l1_idx < g2.l1_idx;
202 : }
203 :
204 0 : return g1.l2_idx <= g2.l2_idx;
205 : }
206 :
207 0 : bool gkdi_rollover_interval(const int64_t managed_password_interval,
208 : NTTIME *result)
209 : {
210 0 : if (managed_password_interval < 0) {
211 0 : return false;
212 : }
213 :
214 0 : *result = (uint64_t)managed_password_interval * 24 / 10 *
215 : gkdi_key_cycle_duration;
216 0 : return true;
217 : }
218 :
219 : struct GkdiContextShort {
220 : uint8_t buf[sizeof((struct GUID_ndr_buf){}.buf) + sizeof(int32_t) +
221 : sizeof(int32_t) + sizeof(int32_t)];
222 : };
223 :
224 323 : static NTSTATUS make_gkdi_context(const struct GkdiDerivationCtx *ctx,
225 : struct GkdiContextShort *out_ctx)
226 : {
227 323 : enum ndr_err_code ndr_err;
228 323 : DATA_BLOB b = {.data = out_ctx->buf, .length = sizeof out_ctx->buf};
229 :
230 323 : if (ctx->target_security_descriptor.length) {
231 0 : return NT_STATUS_INVALID_PARAMETER;
232 : }
233 :
234 323 : ndr_err = ndr_push_struct_into_fixed_blob(
235 : &b, ctx, (ndr_push_flags_fn_t)ndr_push_GkdiDerivationCtx);
236 323 : if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
237 0 : return ndr_map_error2ntstatus(ndr_err);
238 : }
239 :
240 323 : return NT_STATUS_OK;
241 : }
242 :
243 13 : static NTSTATUS make_gkdi_context_security_descriptor(
244 : TALLOC_CTX *mem_ctx,
245 : const struct GkdiDerivationCtx *ctx,
246 : const DATA_BLOB security_descriptor,
247 : DATA_BLOB *out_ctx)
248 : {
249 13 : enum ndr_err_code ndr_err;
250 13 : struct GkdiDerivationCtx ctx_with_sd = *ctx;
251 :
252 13 : if (ctx_with_sd.target_security_descriptor.length != 0) {
253 0 : return NT_STATUS_INVALID_PARAMETER;
254 : }
255 :
256 13 : ctx_with_sd.target_security_descriptor = security_descriptor;
257 :
258 13 : ndr_err = ndr_push_struct_blob(out_ctx,
259 : mem_ctx,
260 : &ctx_with_sd,
261 : (ndr_push_flags_fn_t)
262 : ndr_push_GkdiDerivationCtx);
263 13 : if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
264 0 : return ndr_map_error2ntstatus(ndr_err);
265 : }
266 :
267 13 : return NT_STATUS_OK;
268 : }
269 :
270 : struct GkdiContext {
271 : struct GkdiDerivationCtx ctx;
272 : gnutls_mac_algorithm_t algorithm;
273 : };
274 :
275 15 : gnutls_mac_algorithm_t get_sp800_108_mac_algorithm(
276 : const struct KdfAlgorithm kdf_algorithm)
277 : {
278 15 : switch (kdf_algorithm.id) {
279 15 : case KDF_ALGORITHM_SP800_108_CTR_HMAC:
280 15 : switch (kdf_algorithm.param.sp800_108) {
281 0 : case KDF_PARAM_SHA1:
282 0 : return GNUTLS_MAC_SHA1;
283 0 : case KDF_PARAM_SHA256:
284 0 : return GNUTLS_MAC_SHA256;
285 0 : case KDF_PARAM_SHA384:
286 0 : return GNUTLS_MAC_SHA384;
287 0 : case KDF_PARAM_SHA512:
288 0 : return GNUTLS_MAC_SHA512;
289 : }
290 0 : break;
291 : }
292 :
293 0 : return GNUTLS_MAC_UNKNOWN;
294 : }
295 :
296 17 : static NTSTATUS GkdiContext(const struct ProvRootKey *const root_key,
297 : struct GkdiContext *const ctx)
298 : {
299 17 : NTSTATUS status = NT_STATUS_OK;
300 17 : gnutls_mac_algorithm_t algorithm = GNUTLS_MAC_UNKNOWN;
301 :
302 17 : if (ctx == NULL) {
303 0 : status = NT_STATUS_INVALID_PARAMETER;
304 0 : goto out;
305 : }
306 :
307 17 : if (root_key == NULL) {
308 0 : status = NT_STATUS_INVALID_PARAMETER;
309 0 : goto out;
310 : }
311 :
312 17 : if (root_key->version != root_key_version_1) {
313 2 : status = NT_STATUS_NOT_SUPPORTED;
314 2 : goto out;
315 : }
316 :
317 15 : if (root_key->data.length != GKDI_KEY_LEN) {
318 1 : status = NT_STATUS_NOT_SUPPORTED;
319 1 : goto out;
320 : }
321 :
322 14 : algorithm = get_sp800_108_mac_algorithm(root_key->kdf_algorithm);
323 14 : if (algorithm == GNUTLS_MAC_UNKNOWN) {
324 1 : status = NT_STATUS_NOT_SUPPORTED;
325 1 : goto out;
326 : }
327 :
328 : /*
329 : * The context comprises the GUID corresponding to the root key, the
330 : * GKID (which we shall initialize to zero), and the encoded target
331 : * security descriptor (which will initially be empty).
332 : */
333 13 : *ctx = (struct GkdiContext){
334 0 : .ctx = {.guid = root_key->id,
335 : .l0_idx = 0,
336 : .l1_idx = 0,
337 : .l2_idx = 0,
338 : .target_security_descriptor = {}},
339 : .algorithm = algorithm,
340 : };
341 17 : out:
342 17 : return status;
343 : }
344 :
345 13 : static NTSTATUS compute_l1_seed_key(TALLOC_CTX *mem_ctx,
346 : struct GkdiContext *ctx,
347 : const DATA_BLOB security_descriptor,
348 : const struct ProvRootKey *const root_key,
349 : const struct Gkid gkid,
350 : uint8_t key[static const GKDI_KEY_LEN])
351 : {
352 13 : NTSTATUS status = NT_STATUS_OK;
353 13 : struct GkdiContextShort short_ctx;
354 13 : int8_t n;
355 :
356 13 : ctx->ctx.l0_idx = gkid.l0_idx;
357 13 : ctx->ctx.l1_idx = -1;
358 13 : ctx->ctx.l2_idx = -1;
359 :
360 13 : status = make_gkdi_context(&ctx->ctx, &short_ctx);
361 13 : if (!NT_STATUS_IS_OK(status)) {
362 0 : goto out;
363 : }
364 :
365 : /* Derive an L0 seed key with GKID = (L0, −1, −1). */
366 :
367 26 : status = samba_gnutls_sp800_108_derive_key(root_key->data.data,
368 13 : root_key->data.length,
369 : NULL,
370 : 0,
371 : kds_service,
372 : sizeof kds_service,
373 : short_ctx.buf,
374 : sizeof short_ctx.buf,
375 : ctx->algorithm,
376 : key,
377 : GKDI_KEY_LEN);
378 13 : if (!NT_STATUS_IS_OK(status)) {
379 0 : goto out;
380 : }
381 :
382 : /* Derive an L1 seed key with GKID = (L0, 31, −1). */
383 :
384 13 : ctx->ctx.l1_idx = 31;
385 :
386 : {
387 13 : DATA_BLOB security_descriptor_ctx;
388 :
389 13 : status = make_gkdi_context_security_descriptor(
390 : mem_ctx,
391 0 : &ctx->ctx,
392 : security_descriptor,
393 : &security_descriptor_ctx);
394 13 : if (!NT_STATUS_IS_OK(status)) {
395 0 : goto out;
396 : }
397 :
398 26 : status = samba_gnutls_sp800_108_derive_key(
399 : key,
400 : GKDI_KEY_LEN,
401 : NULL,
402 : 0,
403 : kds_service,
404 : sizeof kds_service,
405 13 : security_descriptor_ctx.data,
406 : security_descriptor_ctx.length,
407 : ctx->algorithm,
408 : key,
409 : GKDI_KEY_LEN);
410 13 : data_blob_free(&security_descriptor_ctx);
411 13 : if (!NT_STATUS_IS_OK(status)) {
412 0 : goto out;
413 : }
414 : }
415 :
416 197 : for (n = 30; n >= gkid.l1_idx; --n) {
417 : /* Derive an L1 seed key with GKID = (L0, n, −1). */
418 :
419 184 : ctx->ctx.l1_idx = n;
420 :
421 184 : status = make_gkdi_context(&ctx->ctx, &short_ctx);
422 184 : if (!NT_STATUS_IS_OK(status)) {
423 0 : goto out;
424 : }
425 :
426 184 : status = samba_gnutls_sp800_108_derive_key(key,
427 : GKDI_KEY_LEN,
428 : NULL,
429 : 0,
430 : kds_service,
431 : sizeof kds_service,
432 : short_ctx.buf,
433 : sizeof short_ctx.buf,
434 : ctx->algorithm,
435 : key,
436 : GKDI_KEY_LEN);
437 184 : if (!NT_STATUS_IS_OK(status)) {
438 0 : goto out;
439 : }
440 : }
441 :
442 13 : out:
443 13 : return status;
444 : }
445 :
446 7 : static NTSTATUS derive_l2_seed_key(struct GkdiContext *ctx,
447 : const struct Gkid gkid,
448 : uint8_t key[static const GKDI_KEY_LEN])
449 : {
450 7 : NTSTATUS status = NT_STATUS_OK;
451 7 : int8_t n;
452 :
453 7 : ctx->ctx.l0_idx = gkid.l0_idx;
454 7 : ctx->ctx.l1_idx = gkid.l1_idx;
455 :
456 133 : for (n = 31; n >= gkid.l2_idx; --n) {
457 126 : struct GkdiContextShort short_ctx;
458 :
459 : /* Derive an L2 seed key with GKID = (L0, L1, n). */
460 :
461 126 : ctx->ctx.l2_idx = n;
462 :
463 126 : status = make_gkdi_context(&ctx->ctx, &short_ctx);
464 126 : if (!NT_STATUS_IS_OK(status)) {
465 0 : goto out;
466 : }
467 :
468 126 : status = samba_gnutls_sp800_108_derive_key(key,
469 : GKDI_KEY_LEN,
470 : NULL,
471 : 0,
472 : kds_service,
473 : sizeof kds_service,
474 : short_ctx.buf,
475 : sizeof short_ctx.buf,
476 : ctx->algorithm,
477 : key,
478 : GKDI_KEY_LEN);
479 126 : if (!NT_STATUS_IS_OK(status)) {
480 0 : goto out;
481 : }
482 : }
483 :
484 7 : out:
485 7 : return status;
486 : }
487 :
488 20 : enum GkidType gkid_key_type(const struct Gkid gkid)
489 : {
490 20 : if (gkid.l0_idx == -1) {
491 0 : return GKID_DEFAULT;
492 : }
493 :
494 19 : if (gkid.l1_idx == -1) {
495 0 : return GKID_L0_SEED_KEY;
496 : }
497 :
498 18 : if (gkid.l2_idx == -1) {
499 6 : return GKID_L1_SEED_KEY;
500 : }
501 :
502 0 : return GKID_L2_SEED_KEY;
503 : }
504 :
505 27 : bool gkid_is_valid(const struct Gkid gkid)
506 : {
507 27 : if (gkid.l0_idx < -1) {
508 0 : return false;
509 : }
510 :
511 26 : if (gkid.l1_idx < -1 || gkid.l1_idx >= gkdi_l1_key_iteration) {
512 0 : return false;
513 : }
514 :
515 24 : if (gkid.l2_idx < -1 || gkid.l2_idx >= gkdi_l2_key_iteration) {
516 0 : return false;
517 : }
518 :
519 22 : if (gkid.l0_idx == -1 && gkid.l1_idx != -1) {
520 0 : return false;
521 : }
522 :
523 21 : if (gkid.l1_idx == -1 && gkid.l2_idx != -1) {
524 1 : return false;
525 : }
526 :
527 0 : return true;
528 : }
529 :
530 26 : NTSTATUS compute_seed_key(TALLOC_CTX *mem_ctx,
531 : const DATA_BLOB target_security_descriptor,
532 : const struct ProvRootKey *const root_key,
533 : const struct Gkid gkid,
534 : uint8_t key[static const GKDI_KEY_LEN])
535 : {
536 26 : NTSTATUS status = NT_STATUS_OK;
537 26 : enum GkidType gkid_type;
538 26 : struct GkdiContext ctx;
539 :
540 26 : if (!gkid_is_valid(gkid)) {
541 7 : status = NT_STATUS_INVALID_PARAMETER;
542 7 : goto out;
543 : }
544 :
545 19 : gkid_type = gkid_key_type(gkid);
546 19 : if (gkid_type < GKID_L1_SEED_KEY) {
547 : /* Don’t allow derivation of L0 seed keys. */
548 2 : status = NT_STATUS_INVALID_PARAMETER;
549 2 : goto out;
550 : }
551 :
552 17 : status = GkdiContext(root_key, &ctx);
553 17 : if (!NT_STATUS_IS_OK(status)) {
554 4 : goto out;
555 : }
556 :
557 13 : status = compute_l1_seed_key(
558 : mem_ctx, &ctx, target_security_descriptor, root_key, gkid, key);
559 13 : if (!NT_STATUS_IS_OK(status)) {
560 0 : goto out;
561 : }
562 :
563 13 : if (gkid_type == GKID_L2_SEED_KEY) {
564 7 : status = derive_l2_seed_key(&ctx, gkid, key);
565 7 : if (!NT_STATUS_IS_OK(status)) {
566 0 : goto out;
567 : }
568 : }
569 :
570 13 : out:
571 26 : return status;
572 : }
573 :
574 0 : NTSTATUS kdf_sp_800_108_from_params(
575 : const DATA_BLOB *const kdf_param,
576 : struct KdfAlgorithm *const kdf_algorithm_out)
577 : {
578 0 : TALLOC_CTX *tmp_ctx = NULL;
579 0 : NTSTATUS status = NT_STATUS_OK;
580 0 : enum ndr_err_code err;
581 0 : enum KdfSp800_108Param sp800_108_param = KDF_PARAM_SHA256;
582 0 : struct KdfParameters kdf_parameters;
583 :
584 0 : if (kdf_param != NULL) {
585 0 : tmp_ctx = talloc_new(NULL);
586 0 : if (tmp_ctx == NULL) {
587 0 : status = NT_STATUS_NO_MEMORY;
588 0 : goto out;
589 : }
590 :
591 0 : err = ndr_pull_struct_blob(kdf_param,
592 : tmp_ctx,
593 : &kdf_parameters,
594 : (ndr_pull_flags_fn_t)
595 : ndr_pull_KdfParameters);
596 0 : if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
597 0 : status = ndr_map_error2ntstatus(err);
598 0 : DBG_WARNING("KdfParameters pull failed: %s\n",
599 : nt_errstr(status));
600 0 : goto out;
601 : }
602 :
603 0 : if (kdf_parameters.hash_algorithm == NULL) {
604 0 : status = NT_STATUS_NOT_SUPPORTED;
605 0 : goto out;
606 : }
607 :
608 : /* These string comparisons are case‐sensitive. */
609 0 : if (strcmp(kdf_parameters.hash_algorithm, "SHA1") == 0) {
610 0 : sp800_108_param = KDF_PARAM_SHA1;
611 0 : } else if (strcmp(kdf_parameters.hash_algorithm, "SHA256") == 0)
612 : {
613 0 : sp800_108_param = KDF_PARAM_SHA256;
614 0 : } else if (strcmp(kdf_parameters.hash_algorithm, "SHA384") == 0)
615 : {
616 0 : sp800_108_param = KDF_PARAM_SHA384;
617 0 : } else if (strcmp(kdf_parameters.hash_algorithm, "SHA512") == 0)
618 : {
619 0 : sp800_108_param = KDF_PARAM_SHA512;
620 : } else {
621 0 : status = NT_STATUS_NOT_SUPPORTED;
622 0 : goto out;
623 : }
624 : }
625 :
626 0 : *kdf_algorithm_out = (struct KdfAlgorithm){
627 : .id = KDF_ALGORITHM_SP800_108_CTR_HMAC,
628 : .param.sp800_108 = sp800_108_param,
629 : };
630 0 : out:
631 0 : talloc_free(tmp_ctx);
632 0 : return status;
633 : }
634 :
635 0 : NTSTATUS kdf_algorithm_from_params(const char *const kdf_algorithm_id,
636 : const DATA_BLOB *const kdf_param,
637 : struct KdfAlgorithm *const kdf_algorithm_out)
638 : {
639 0 : if (kdf_algorithm_id == NULL) {
640 0 : return NT_STATUS_INVALID_PARAMETER;
641 : }
642 :
643 : /* This string comparison is case‐sensitive. */
644 0 : if (strcmp(kdf_algorithm_id, "SP800_108_CTR_HMAC") == 0) {
645 0 : return kdf_sp_800_108_from_params(kdf_param, kdf_algorithm_out);
646 : }
647 :
648 : /* Unknown algorithm. */
649 0 : return NT_STATUS_NOT_SUPPORTED;
650 : }
|