xref: /linux/crypto/krb5/selftest.c (revision ca220141fa8ebae09765a242076b2b77338106b0)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* Kerberos library self-testing
3  *
4  * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved.
5  * Written by David Howells (dhowells@redhat.com)
6  */
7 
8 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9 
10 #include <linux/hex.h>
11 #include <linux/slab.h>
12 #include <crypto/skcipher.h>
13 #include <crypto/hash.h>
14 #include "internal.h"
15 
16 #define VALID(X) \
17 	({								\
18 		bool __x = (X);						\
19 		if (__x) {						\
20 			pr_warn("!!! TESTINVAL %s:%u\n", __FILE__, __LINE__); \
21 			ret = -EBADMSG;					\
22 		}							\
23 		__x;							\
24 	})
25 
26 #define CHECK(X) \
27 	({								\
28 		bool __x = (X);						\
29 		if (__x) {						\
30 			pr_warn("!!! TESTFAIL %s:%u\n", __FILE__, __LINE__); \
31 			ret = -EBADMSG;					\
32 		}							\
33 		__x;							\
34 	})
35 
36 enum which_key {
37 	TEST_KC, TEST_KE, TEST_KI,
38 };
39 
40 #if 0
41 static void dump_sg(struct scatterlist *sg, unsigned int limit)
42 {
43 	unsigned int index = 0, n = 0;
44 
45 	for (; sg && limit > 0; sg = sg_next(sg)) {
46 		unsigned int off = sg->offset, len = umin(sg->length, limit);
47 		const void *p = kmap_local_page(sg_page(sg));
48 
49 		limit -= len;
50 		while (len > 0) {
51 			unsigned int part = umin(len, 32);
52 
53 			pr_notice("[%x] %04x: %*phN\n", n, index, part, p + off);
54 			index += part;
55 			off += part;
56 			len -= part;
57 		}
58 
59 		kunmap_local(p);
60 		n++;
61 	}
62 }
63 #endif
64 
65 static int prep_buf(struct krb5_buffer *buf)
66 {
67 	buf->data = kmalloc(buf->len, GFP_KERNEL);
68 	if (!buf->data)
69 		return -ENOMEM;
70 	return 0;
71 }
72 
73 #define PREP_BUF(BUF, LEN)					\
74 	do {							\
75 		(BUF)->len = (LEN);				\
76 		ret = prep_buf((BUF));				\
77 		if (ret < 0)					\
78 			goto out;				\
79 	} while (0)
80 
81 static int load_buf(struct krb5_buffer *buf, const char *from)
82 {
83 	size_t len = strlen(from);
84 	int ret;
85 
86 	if (len > 1 && from[0] == '\'') {
87 		PREP_BUF(buf, len - 1);
88 		memcpy(buf->data, from + 1, len - 1);
89 		ret = 0;
90 		goto out;
91 	}
92 
93 	if (VALID(len & 1))
94 		return -EINVAL;
95 
96 	PREP_BUF(buf, len / 2);
97 	ret = hex2bin(buf->data, from, buf->len);
98 	if (ret < 0) {
99 		VALID(1);
100 		goto out;
101 	}
102 out:
103 	return ret;
104 }
105 
106 #define LOAD_BUF(BUF, FROM) do { ret = load_buf(BUF, FROM); if (ret < 0) goto out; } while (0)
107 
108 static void clear_buf(struct krb5_buffer *buf)
109 {
110 	kfree(buf->data);
111 	buf->len = 0;
112 	buf->data = NULL;
113 }
114 
115 /*
116  * Perform a pseudo-random function check.
117  */
118 static int krb5_test_one_prf(const struct krb5_prf_test *test)
119 {
120 	const struct krb5_enctype *krb5 = crypto_krb5_find_enctype(test->etype);
121 	struct krb5_buffer key = {}, octet = {}, result = {}, prf = {};
122 	int ret;
123 
124 	if (!krb5)
125 		return -EOPNOTSUPP;
126 
127 	pr_notice("Running %s %s\n", krb5->name, test->name);
128 
129 	LOAD_BUF(&key,   test->key);
130 	LOAD_BUF(&octet, test->octet);
131 	LOAD_BUF(&prf,   test->prf);
132 	PREP_BUF(&result, krb5->prf_len);
133 
134 	if (VALID(result.len != prf.len)) {
135 		ret = -EINVAL;
136 		goto out;
137 	}
138 
139 	ret = krb5->profile->calc_PRF(krb5, &key, &octet, &result, GFP_KERNEL);
140 	if (ret < 0) {
141 		CHECK(1);
142 		pr_warn("PRF calculation failed %d\n", ret);
143 		goto out;
144 	}
145 
146 	if (memcmp(result.data, prf.data, result.len) != 0) {
147 		CHECK(1);
148 		ret = -EKEYREJECTED;
149 		goto out;
150 	}
151 
152 	ret = 0;
153 
154 out:
155 	clear_buf(&result);
156 	clear_buf(&prf);
157 	clear_buf(&octet);
158 	clear_buf(&key);
159 	return ret;
160 }
161 
162 /*
163  * Perform a key derivation check.
164  */
165 static int krb5_test_key(const struct krb5_enctype *krb5,
166 			 const struct krb5_buffer *base_key,
167 			 const struct krb5_key_test_one *test,
168 			 enum which_key which)
169 {
170 	struct krb5_buffer key = {}, result = {};
171 	int ret;
172 
173 	LOAD_BUF(&key,   test->key);
174 	PREP_BUF(&result, key.len);
175 
176 	switch (which) {
177 	case TEST_KC:
178 		ret = krb5_derive_Kc(krb5, base_key, test->use, &result, GFP_KERNEL);
179 		break;
180 	case TEST_KE:
181 		ret = krb5_derive_Ke(krb5, base_key, test->use, &result, GFP_KERNEL);
182 		break;
183 	case TEST_KI:
184 		ret = krb5_derive_Ki(krb5, base_key, test->use, &result, GFP_KERNEL);
185 		break;
186 	default:
187 		VALID(1);
188 		ret = -EINVAL;
189 		goto out;
190 	}
191 
192 	if (ret < 0) {
193 		CHECK(1);
194 		pr_warn("Key derivation failed %d\n", ret);
195 		goto out;
196 	}
197 
198 	if (memcmp(result.data, key.data, result.len) != 0) {
199 		CHECK(1);
200 		ret = -EKEYREJECTED;
201 		goto out;
202 	}
203 
204 out:
205 	clear_buf(&key);
206 	clear_buf(&result);
207 	return ret;
208 }
209 
210 static int krb5_test_one_key(const struct krb5_key_test *test)
211 {
212 	const struct krb5_enctype *krb5 = crypto_krb5_find_enctype(test->etype);
213 	struct krb5_buffer base_key = {};
214 	int ret;
215 
216 	if (!krb5)
217 		return -EOPNOTSUPP;
218 
219 	pr_notice("Running %s %s\n", krb5->name, test->name);
220 
221 	LOAD_BUF(&base_key, test->key);
222 
223 	ret = krb5_test_key(krb5, &base_key, &test->Kc, TEST_KC);
224 	if (ret < 0)
225 		goto out;
226 	ret = krb5_test_key(krb5, &base_key, &test->Ke, TEST_KE);
227 	if (ret < 0)
228 		goto out;
229 	ret = krb5_test_key(krb5, &base_key, &test->Ki, TEST_KI);
230 	if (ret < 0)
231 		goto out;
232 
233 out:
234 	clear_buf(&base_key);
235 	return ret;
236 }
237 
238 /*
239  * Perform an encryption test.
240  */
241 static int krb5_test_one_enc(const struct krb5_enc_test *test, void *buf)
242 {
243 	const struct krb5_enctype *krb5 = crypto_krb5_find_enctype(test->etype);
244 	struct crypto_aead *ci = NULL;
245 	struct krb5_buffer K0 = {}, Ke = {}, Ki = {}, keys = {};
246 	struct krb5_buffer conf = {}, plain = {}, ct = {};
247 	struct scatterlist sg[1];
248 	size_t data_len, data_offset, message_len;
249 	int ret;
250 
251 	if (!krb5)
252 		return -EOPNOTSUPP;
253 
254 	pr_notice("Running %s %s\n", krb5->name, test->name);
255 
256 	/* Load the test data into binary buffers. */
257 	LOAD_BUF(&conf, test->conf);
258 	LOAD_BUF(&plain, test->plain);
259 	LOAD_BUF(&ct, test->ct);
260 
261 	if (test->K0) {
262 		LOAD_BUF(&K0, test->K0);
263 	} else {
264 		LOAD_BUF(&Ke, test->Ke);
265 		LOAD_BUF(&Ki, test->Ki);
266 
267 		ret = krb5->profile->load_encrypt_keys(krb5, &Ke, &Ki, &keys, GFP_KERNEL);
268 		if (ret < 0)
269 			goto out;
270 	}
271 
272 	if (VALID(conf.len != krb5->conf_len) ||
273 	    VALID(ct.len != krb5->conf_len + plain.len + krb5->cksum_len))
274 		goto out;
275 
276 	data_len = plain.len;
277 	message_len = crypto_krb5_how_much_buffer(krb5, KRB5_ENCRYPT_MODE,
278 						  data_len, &data_offset);
279 
280 	if (CHECK(message_len != ct.len)) {
281 		pr_warn("Encrypted length mismatch %zu != %u\n", message_len, ct.len);
282 		goto out;
283 	}
284 	if (CHECK(data_offset != conf.len)) {
285 		pr_warn("Data offset mismatch %zu != %u\n", data_offset, conf.len);
286 		goto out;
287 	}
288 
289 	memcpy(buf, conf.data, conf.len);
290 	memcpy(buf + data_offset, plain.data, plain.len);
291 
292 	/* Allocate a crypto object and set its key. */
293 	if (test->K0)
294 		ci = crypto_krb5_prepare_encryption(krb5, &K0, test->usage, GFP_KERNEL);
295 	else
296 		ci = krb5_prepare_encryption(krb5, &keys, GFP_KERNEL);
297 
298 	if (IS_ERR(ci)) {
299 		ret = PTR_ERR(ci);
300 		ci = NULL;
301 		pr_err("Couldn't alloc AEAD %s: %d\n", krb5->encrypt_name, ret);
302 		goto out;
303 	}
304 
305 	/* Encrypt the message. */
306 	sg_init_one(sg, buf, message_len);
307 	ret = crypto_krb5_encrypt(krb5, ci, sg, 1, message_len,
308 				  data_offset, data_len, true);
309 	if (ret < 0) {
310 		CHECK(1);
311 		pr_warn("Encryption failed %d\n", ret);
312 		goto out;
313 	}
314 	if (ret != message_len) {
315 		CHECK(1);
316 		pr_warn("Encrypted message wrong size %x != %zx\n", ret, message_len);
317 		goto out;
318 	}
319 
320 	if (memcmp(buf, ct.data, ct.len) != 0) {
321 		CHECK(1);
322 		pr_warn("Ciphertext mismatch\n");
323 		pr_warn("BUF %*phN\n", ct.len, buf);
324 		pr_warn("CT  %*phN\n", ct.len, ct.data);
325 		pr_warn("PT  %*phN%*phN\n", conf.len, conf.data, plain.len, plain.data);
326 		ret = -EKEYREJECTED;
327 		goto out;
328 	}
329 
330 	/* Decrypt the encrypted message. */
331 	data_offset = 0;
332 	data_len = message_len;
333 	ret = crypto_krb5_decrypt(krb5, ci, sg, 1, &data_offset, &data_len);
334 	if (ret < 0) {
335 		CHECK(1);
336 		pr_warn("Decryption failed %d\n", ret);
337 		goto out;
338 	}
339 
340 	if (CHECK(data_offset != conf.len) ||
341 	    CHECK(data_len != plain.len))
342 		goto out;
343 
344 	if (memcmp(buf, conf.data, conf.len) != 0) {
345 		CHECK(1);
346 		pr_warn("Confounder mismatch\n");
347 		pr_warn("ENC %*phN\n", conf.len, buf);
348 		pr_warn("DEC %*phN\n", conf.len, conf.data);
349 		ret = -EKEYREJECTED;
350 		goto out;
351 	}
352 
353 	if (memcmp(buf + conf.len, plain.data, plain.len) != 0) {
354 		CHECK(1);
355 		pr_warn("Plaintext mismatch\n");
356 		pr_warn("BUF %*phN\n", plain.len, buf + conf.len);
357 		pr_warn("PT  %*phN\n", plain.len, plain.data);
358 		ret = -EKEYREJECTED;
359 		goto out;
360 	}
361 
362 	ret = 0;
363 
364 out:
365 	clear_buf(&ct);
366 	clear_buf(&plain);
367 	clear_buf(&conf);
368 	clear_buf(&keys);
369 	clear_buf(&Ki);
370 	clear_buf(&Ke);
371 	clear_buf(&K0);
372 	if (ci)
373 		crypto_free_aead(ci);
374 	return ret;
375 }
376 
377 /*
378  * Perform a checksum test.
379  */
380 static int krb5_test_one_mic(const struct krb5_mic_test *test, void *buf)
381 {
382 	const struct krb5_enctype *krb5 = crypto_krb5_find_enctype(test->etype);
383 	struct crypto_shash *ci = NULL;
384 	struct scatterlist sg[1];
385 	struct krb5_buffer K0 = {}, Kc = {}, keys = {}, plain = {}, mic = {};
386 	size_t offset, len, message_len;
387 	int ret;
388 
389 	if (!krb5)
390 		return -EOPNOTSUPP;
391 
392 	pr_notice("Running %s %s\n", krb5->name, test->name);
393 
394 	/* Allocate a crypto object and set its key. */
395 	if (test->K0) {
396 		LOAD_BUF(&K0, test->K0);
397 		ci = crypto_krb5_prepare_checksum(krb5, &K0, test->usage, GFP_KERNEL);
398 	} else {
399 		LOAD_BUF(&Kc, test->Kc);
400 
401 		ret = krb5->profile->load_checksum_key(krb5, &Kc, &keys, GFP_KERNEL);
402 		if (ret < 0)
403 			goto out;
404 
405 		ci = krb5_prepare_checksum(krb5, &Kc, GFP_KERNEL);
406 	}
407 	if (IS_ERR(ci)) {
408 		ret = PTR_ERR(ci);
409 		ci = NULL;
410 		pr_err("Couldn't alloc shash %s: %d\n", krb5->cksum_name, ret);
411 		goto out;
412 	}
413 
414 	/* Load the test data into binary buffers. */
415 	LOAD_BUF(&plain, test->plain);
416 	LOAD_BUF(&mic, test->mic);
417 
418 	len = plain.len;
419 	message_len = crypto_krb5_how_much_buffer(krb5, KRB5_CHECKSUM_MODE,
420 						  len, &offset);
421 
422 	if (CHECK(message_len != mic.len + plain.len)) {
423 		pr_warn("MIC length mismatch %zu != %u\n",
424 			message_len, mic.len + plain.len);
425 		goto out;
426 	}
427 
428 	memcpy(buf + offset, plain.data, plain.len);
429 
430 	/* Generate a MIC generation request. */
431 	sg_init_one(sg, buf, 1024);
432 
433 	ret = crypto_krb5_get_mic(krb5, ci, NULL, sg, 1, 1024,
434 				  krb5->cksum_len, plain.len);
435 	if (ret < 0) {
436 		CHECK(1);
437 		pr_warn("Get MIC failed %d\n", ret);
438 		goto out;
439 	}
440 	len = ret;
441 
442 	if (CHECK(len != plain.len + mic.len)) {
443 		pr_warn("MIC length mismatch %zu != %u\n", len, plain.len + mic.len);
444 		goto out;
445 	}
446 
447 	if (memcmp(buf, mic.data, mic.len) != 0) {
448 		CHECK(1);
449 		pr_warn("MIC mismatch\n");
450 		pr_warn("BUF %*phN\n", mic.len, buf);
451 		pr_warn("MIC %*phN\n", mic.len, mic.data);
452 		ret = -EKEYREJECTED;
453 		goto out;
454 	}
455 
456 	/* Generate a verification request. */
457 	offset = 0;
458 	ret = crypto_krb5_verify_mic(krb5, ci, NULL, sg, 1, &offset, &len);
459 	if (ret < 0) {
460 		CHECK(1);
461 		pr_warn("Verify MIC failed %d\n", ret);
462 		goto out;
463 	}
464 
465 	if (CHECK(offset != mic.len) ||
466 	    CHECK(len != plain.len))
467 		goto out;
468 
469 	if (memcmp(buf + offset, plain.data, plain.len) != 0) {
470 		CHECK(1);
471 		pr_warn("Plaintext mismatch\n");
472 		pr_warn("BUF %*phN\n", plain.len, buf + offset);
473 		pr_warn("PT  %*phN\n", plain.len, plain.data);
474 		ret = -EKEYREJECTED;
475 		goto out;
476 	}
477 
478 	ret = 0;
479 
480 out:
481 	clear_buf(&mic);
482 	clear_buf(&plain);
483 	clear_buf(&keys);
484 	clear_buf(&K0);
485 	clear_buf(&Kc);
486 	if (ci)
487 		crypto_free_shash(ci);
488 	return ret;
489 }
490 
491 int krb5_selftest(void)
492 {
493 	void *buf;
494 	int ret = 0, i;
495 
496 	buf = kmalloc(4096, GFP_KERNEL);
497 	if (!buf)
498 		return -ENOMEM;
499 
500 	pr_notice("\n");
501 	pr_notice("Running selftests\n");
502 
503 	for (i = 0; krb5_prf_tests[i].name; i++) {
504 		ret = krb5_test_one_prf(&krb5_prf_tests[i]);
505 		if (ret < 0) {
506 			if (ret != -EOPNOTSUPP)
507 				goto out;
508 			pr_notice("Skipping %s\n", krb5_prf_tests[i].name);
509 		}
510 	}
511 
512 	for (i = 0; krb5_key_tests[i].name; i++) {
513 		ret = krb5_test_one_key(&krb5_key_tests[i]);
514 		if (ret < 0) {
515 			if (ret != -EOPNOTSUPP)
516 				goto out;
517 			pr_notice("Skipping %s\n", krb5_key_tests[i].name);
518 		}
519 	}
520 
521 	for (i = 0; krb5_enc_tests[i].name; i++) {
522 		memset(buf, 0x5a, 4096);
523 		ret = krb5_test_one_enc(&krb5_enc_tests[i], buf);
524 		if (ret < 0) {
525 			if (ret != -EOPNOTSUPP)
526 				goto out;
527 			pr_notice("Skipping %s\n", krb5_enc_tests[i].name);
528 		}
529 	}
530 
531 	for (i = 0; krb5_mic_tests[i].name; i++) {
532 		memset(buf, 0x5a, 4096);
533 		ret = krb5_test_one_mic(&krb5_mic_tests[i], buf);
534 		if (ret < 0) {
535 			if (ret != -EOPNOTSUPP)
536 				goto out;
537 			pr_notice("Skipping %s\n", krb5_mic_tests[i].name);
538 		}
539 	}
540 
541 	ret = 0;
542 out:
543 	pr_notice("Selftests %s\n", ret == 0 ? "succeeded" : "failed");
544 	kfree(buf);
545 	return ret;
546 }
547