diff mbox series

[08/18] crypto/krb5: Implement crypto self-testing

Message ID 160518593050.2277919.4004451170398397487.stgit@warthog.procyon.org.uk (mailing list archive)
State New, archived
Headers show
Series crypto: Add generic Kerberos library | expand

Commit Message

David Howells Nov. 12, 2020, 12:58 p.m. UTC
Implement self-testing infrastructure to test the pseudo-random function,
key derivation, encryption and checksumming.

Signed-off-by: David Howells <dhowells@redhat.com>
---

 crypto/krb5/Kconfig         |    4 
 crypto/krb5/Makefile        |    4 
 crypto/krb5/internal.h      |   48 ++++
 crypto/krb5/main.c          |   12 +
 crypto/krb5/selftest.c      |  543 +++++++++++++++++++++++++++++++++++++++++++
 crypto/krb5/selftest_data.c |   38 +++
 6 files changed, 649 insertions(+)
 create mode 100644 crypto/krb5/selftest.c
 create mode 100644 crypto/krb5/selftest_data.c
diff mbox series

Patch

diff --git a/crypto/krb5/Kconfig b/crypto/krb5/Kconfig
index 881754500732..e2eba1d689ab 100644
--- a/crypto/krb5/Kconfig
+++ b/crypto/krb5/Kconfig
@@ -9,3 +9,7 @@  config CRYPTO_KRB5
 	select CRYPTO_AES
 	help
 	  Provide Kerberos-5-based security.
+
+config CRYPTO_KRB5_SELFTESTS
+	bool "Kerberos 5 crypto selftests"
+	depends on CRYPTO_KRB5
diff --git a/crypto/krb5/Makefile b/crypto/krb5/Makefile
index b81e2efac3c8..b7da03cae6d1 100644
--- a/crypto/krb5/Makefile
+++ b/crypto/krb5/Makefile
@@ -9,4 +9,8 @@  krb5-y += \
 	rfc3961_simplified.o \
 	rfc3962_aes.o
 
+krb5-$(CONFIG_CRYPTO_KRB5_SELFTESTS) += \
+	selftest.o \
+	selftest_data.o
+
 obj-$(CONFIG_CRYPTO_KRB5) += krb5.o
diff --git a/crypto/krb5/internal.h b/crypto/krb5/internal.h
index 5d55a574536e..47424b433778 100644
--- a/crypto/krb5/internal.h
+++ b/crypto/krb5/internal.h
@@ -88,6 +88,37 @@  struct krb5_crypto_profile {
 	crypto_roundup(crypto_sync_skcipher_ivsize(TFM))
 #define round16(x) (((x) + 15) & ~15)
 
+/*
+ * Self-testing data.
+ */
+struct krb5_prf_test {
+	const struct krb5_enctype *krb5;
+	const char *name, *key, *octet, *prf;
+};
+
+struct krb5_key_test_one {
+	u32 use;
+	const char *key;
+};
+
+struct krb5_key_test {
+	const struct krb5_enctype *krb5;
+	const char *name, *key;
+	struct krb5_key_test_one Kc, Ke, Ki;
+};
+
+struct krb5_enc_test {
+	const struct krb5_enctype *krb5;
+	const char *name, *plain, *conf, *K0, *Ke, *Ki, *ct;
+	__be32 usage;
+};
+
+struct krb5_mic_test {
+	const struct krb5_enctype *krb5;
+	const char *name, *plain, *K0, *Kc, *mic;
+	__be32 usage;
+};
+
 /*
  * main.c
  */
@@ -126,3 +157,20 @@  int rfc3961_verify_mic(const struct krb5_enctype *krb5,
  */
 extern const struct krb5_enctype krb5_aes128_cts_hmac_sha1_96;
 extern const struct krb5_enctype krb5_aes256_cts_hmac_sha1_96;
+
+/*
+ * selftest.c
+ */
+#ifdef CONFIG_CRYPTO_KRB5_SELFTESTS
+void krb5_selftest(void);
+#else
+static inline void krb5_selftest(void) {}
+#endif
+
+/*
+ * selftest_data.c
+ */
+extern const struct krb5_prf_test krb5_prf_tests[];
+extern const struct krb5_key_test krb5_key_tests[];
+extern const struct krb5_enc_test krb5_enc_tests[];
+extern const struct krb5_mic_test krb5_mic_tests[];
diff --git a/crypto/krb5/main.c b/crypto/krb5/main.c
index bce47580c33f..b79127027551 100644
--- a/crypto/krb5/main.c
+++ b/crypto/krb5/main.c
@@ -214,3 +214,15 @@  int crypto_krb5_verify_mic(const struct krb5_enctype *krb5,
 					 _offset, _len, _error_code);
 }
 EXPORT_SYMBOL(crypto_krb5_verify_mic);
+
+static int __init crypto_krb5_init(void)
+{
+	krb5_selftest();
+	return 0;
+}
+module_init(crypto_krb5_init);
+
+static void __exit crypto_krb5_exit(void)
+{
+}
+module_exit(crypto_krb5_exit);
diff --git a/crypto/krb5/selftest.c b/crypto/krb5/selftest.c
new file mode 100644
index 000000000000..df57ab24cc6e
--- /dev/null
+++ b/crypto/krb5/selftest.c
@@ -0,0 +1,543 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* RxGK self-testing
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <crypto/skcipher.h>
+#include <crypto/hash.h>
+#include "internal.h"
+
+#define VALID(X) \
+	({								\
+		bool __x = (X);						\
+		if (__x) {						\
+			pr_warn("!!! TESTINVAL %s:%u\n", __FILE__, __LINE__); \
+		}							\
+		__x;							\
+	})
+
+#define CHECK(X) \
+	({								\
+		bool __x = (X);						\
+		if (__x) {						\
+			pr_warn("!!! TESTFAIL %s:%u\n", __FILE__, __LINE__); \
+		}							\
+		__x;							\
+	})
+
+enum which_key {
+	TEST_KC, TEST_KE, TEST_KI,
+};
+
+static int prep_buf(struct krb5_buffer *buf)
+{
+	buf->data = kmalloc(buf->len, GFP_KERNEL);
+	if (!buf->data)
+		return -ENOMEM;
+	return 0;
+}
+
+#define PREP_BUF(BUF, LEN)					\
+	do {							\
+		(BUF)->len = (LEN);				\
+		if ((ret = prep_buf((BUF))) < 0)		\
+			goto out;				\
+	} while(0)
+
+static int load_buf(struct krb5_buffer *buf, const char *from)
+{
+	size_t len = strlen(from);
+	int ret;
+
+	if (len > 1 && from[0] == '\'') {
+		PREP_BUF(buf, len - 1);
+		memcpy(buf->data, from + 1, len - 1);
+		ret = 0;
+		goto out;
+	}
+
+	if (VALID(len & 1))
+		return -EINVAL;
+
+	PREP_BUF(buf, len / 2);
+	if ((ret = hex2bin(buf->data, from, buf->len)) < 0) {
+		VALID(1);
+		goto out;
+	}
+out:
+	return ret;
+}
+
+#define LOAD_BUF(BUF, FROM) do { if ((ret = load_buf(BUF, FROM)) < 0) goto out; } while(0)
+
+static void clear_buf(struct krb5_buffer *buf)
+{
+	kfree(buf->data);
+	buf->len = 0;
+	buf->data = NULL;
+}
+
+/*
+ * Perform a pseudo-random function check.
+ */
+static int krb5_test_one_prf(const struct krb5_prf_test *test)
+{
+	const struct krb5_enctype *krb5 = test->krb5;
+	struct krb5_buffer key = {}, octet = {}, result = {}, prf = {};
+	int ret;
+
+	pr_notice("Running %s %s\n", krb5->name, test->name);
+
+	LOAD_BUF(&key,   test->key);
+	LOAD_BUF(&octet, test->octet);
+	LOAD_BUF(&prf,   test->prf);
+	PREP_BUF(&result, krb5->prf_len);
+
+	if (VALID(result.len != prf.len)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if ((ret = krb5->profile->calc_PRF(krb5, &key, &octet, &result, GFP_KERNEL)) < 0) {
+		CHECK(1);
+		pr_warn("PRF calculation failed %d\n", ret);
+		goto out;
+	}
+
+	if (memcmp(result.data, prf.data, result.len) != 0) {
+		CHECK(1);
+		ret = -EKEYREJECTED;
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	clear_buf(&result);
+	clear_buf(&octet);
+	clear_buf(&key);
+	return ret;
+}
+
+/*
+ * Perform a key derivation check.
+ */
+static int krb5_test_key(const struct krb5_enctype *krb5,
+			 const struct krb5_buffer *base_key,
+			 const struct krb5_key_test_one *test,
+			 enum which_key which)
+{
+	struct krb5_buffer key = {}, result = {};
+	int ret;
+
+	LOAD_BUF(&key,   test->key);
+	PREP_BUF(&result, key.len);
+
+	switch (which) {
+	case TEST_KC:
+		ret = crypto_krb5_get_Kc(krb5, base_key, test->use, &result,
+					 NULL, GFP_KERNEL);
+		break;
+	case TEST_KE:
+		ret = crypto_krb5_get_Ke(krb5, base_key, test->use, &result,
+					 NULL, GFP_KERNEL);
+		break;
+	case TEST_KI:
+		ret = crypto_krb5_get_Ki(krb5, base_key, test->use, &result,
+					 NULL, GFP_KERNEL);
+		break;
+	default:
+		VALID(1);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (ret < 0) {
+		CHECK(1);
+		pr_warn("Key derivation failed %d\n", ret);
+		goto out;
+	}
+
+	if (memcmp(result.data, key.data, result.len) != 0) {
+		CHECK(1);
+		ret = -EKEYREJECTED;
+		goto out;
+	}
+
+out:
+	clear_buf(&key);
+	clear_buf(&result);
+	return ret;
+}
+
+static int krb5_test_one_key(const struct krb5_key_test *test)
+{
+	const struct krb5_enctype *krb5 = test->krb5;
+	struct krb5_buffer base_key = {};
+	int ret;
+
+	pr_notice("Running %s %s\n", krb5->name, test->name);
+
+	LOAD_BUF(&base_key, test->key);
+
+	if ((ret = krb5_test_key(krb5, &base_key, &test->Kc, TEST_KC)) < 0)
+		goto out;
+	if ((ret = krb5_test_key(krb5, &base_key, &test->Ke, TEST_KE)) < 0)
+		goto out;
+	if ((ret = krb5_test_key(krb5, &base_key, &test->Ki, TEST_KI)) < 0)
+		goto out;
+
+out:
+	clear_buf(&base_key);
+	return ret;
+}
+
+static int krb5_test_get_Kc(const struct krb5_mic_test *test,
+			    struct crypto_shash **_Kc)
+{
+	const struct krb5_enctype *krb5 = test->krb5;
+	struct crypto_shash *shash;
+	struct krb5_buffer K0 = {}, key = {};
+	int ret;
+
+	shash = crypto_alloc_shash(krb5->cksum_name, 0, 0);
+	if (IS_ERR(shash))
+		return (PTR_ERR(shash) == -ENOENT) ? -ENOPKG : PTR_ERR(shash);
+	*_Kc = shash;
+
+	if (test->Kc) {
+		LOAD_BUF(&key, test->Kc);
+	} else {
+		char usage_data[5];
+		struct krb5_buffer usage = { .len = 5, .data = usage_data };
+		memcpy(usage_data, &test->usage, 4);
+		usage_data[4] = 0x99;
+		LOAD_BUF(&K0, test->K0);
+		PREP_BUF(&key, krb5->Kc_len);
+		ret = krb5->profile->calc_Kc(krb5, &K0, &usage, &key, GFP_KERNEL);
+	}
+
+	ret = crypto_shash_setkey(shash, key.data, key.len);
+out:
+	clear_buf(&key);
+	clear_buf(&K0);
+	return ret;
+}
+
+static int krb5_test_get_Ke(const struct krb5_enc_test *test,
+			    struct krb5_enc_keys *keys)
+{
+	const struct krb5_enctype *krb5 = test->krb5;
+	struct crypto_sync_skcipher *ci;
+	struct krb5_buffer K0 = {}, key = {};
+	int ret;
+
+	ci = crypto_alloc_sync_skcipher(krb5->encrypt_name, 0, 0);
+	if (IS_ERR(ci))
+		return (PTR_ERR(ci) == -ENOENT) ? -ENOPKG : PTR_ERR(ci);
+	keys->Ke = ci;
+
+	if (test->Ke) {
+		LOAD_BUF(&key, test->Ke);
+	} else {
+		char usage_data[5];
+		struct krb5_buffer usage = { .len = 5, .data = usage_data };
+		memcpy(usage_data, &test->usage, 4);
+		usage_data[4] = 0xAA;
+		LOAD_BUF(&K0, test->K0);
+		PREP_BUF(&key, krb5->Ke_len);
+		ret = krb5->profile->calc_Ke(krb5, &K0, &usage, &key, GFP_KERNEL);
+	}
+
+	ret = crypto_sync_skcipher_setkey(ci, key.data, key.len);
+out:
+	clear_buf(&key);
+	clear_buf(&K0);
+	return ret;
+}
+
+static int krb5_test_get_Ki(const struct krb5_enc_test *test,
+			    struct krb5_enc_keys *keys)
+{
+	const struct krb5_enctype *krb5 = test->krb5;
+	struct crypto_shash *shash;
+	struct krb5_buffer K0 = {}, key = {};
+	int ret;
+
+	shash = crypto_alloc_shash(krb5->cksum_name, 0, 0);
+	if (IS_ERR(shash))
+		return (PTR_ERR(shash) == -ENOENT) ? -ENOPKG : PTR_ERR(shash);
+	keys->Ki = shash;
+
+	if (test->Ki) {
+		LOAD_BUF(&key, test->Ki);
+	} else {
+		char usage_data[5];
+		struct krb5_buffer usage = { .len = 5, .data = usage_data };
+		memcpy(usage_data, &test->usage, 4);
+		usage_data[4] = 0x55;
+		LOAD_BUF(&K0, test->K0);
+		PREP_BUF(&key, krb5->Ki_len);
+		ret = krb5->profile->calc_Ki(krb5, &K0, &usage, &key, GFP_KERNEL);
+	}
+
+	ret = crypto_shash_setkey(shash, key.data, key.len);
+out:
+	clear_buf(&key);
+	clear_buf(&K0);
+	return ret;
+}
+
+/*
+ * Generate a buffer containing encryption test data.
+ */
+static int krb5_load_enc_buf(const struct krb5_enc_test *test,
+			     const struct krb5_buffer *plain,
+			     void *buf)
+{
+	const struct krb5_enctype *krb5 = test->krb5;
+	unsigned int conf_len, pad_len, enc_len, ct_len;
+	int ret;
+
+	conf_len = strlen(test->conf);
+	if (VALID((conf_len & 1) || conf_len / 2 != krb5->conf_len))
+		return -EINVAL;
+
+	if (krb5->pad) {
+		enc_len = round_up(krb5->conf_len + plain->len, krb5->block_len);
+		pad_len = enc_len - (krb5->conf_len + plain->len);
+	} else {
+		enc_len = krb5->conf_len + plain->len;
+		pad_len = 0;
+	}
+
+	ct_len = strlen(test->ct);
+	if (VALID((ct_len & 1) || ct_len / 2 != enc_len + krb5->cksum_len))
+		return -EINVAL;
+	ct_len = enc_len + krb5->cksum_len;
+
+	if ((ret = hex2bin(buf, test->conf, krb5->conf_len)) < 0)
+		return ret;
+	buf += krb5->conf_len;
+	memcpy(buf, plain->data, plain->len);
+	return 0;
+}
+
+/*
+ * Load checksum test data into a buffer.
+ */
+static int krb5_load_mic_buf(const struct krb5_mic_test *test,
+			     const struct krb5_buffer *plain,
+			     void *buf)
+{
+	const struct krb5_enctype *krb5 = test->krb5;
+
+	memcpy(buf + krb5->cksum_len, plain->data, plain->len);
+	return 0;
+}
+
+/*
+ * Perform an encryption test.
+ */
+static int krb5_test_one_enc(const struct krb5_enc_test *test, void *buf)
+{
+	const struct krb5_enctype *krb5 = test->krb5;
+	struct krb5_enc_keys keys = {};
+	struct krb5_buffer plain = {}, ct = {};
+	struct scatterlist sg[1];
+	size_t offset, len;
+	int ret, error_code;
+
+	pr_notice("Running %s %s\n", krb5->name, test->name);
+
+	if ((ret = krb5_test_get_Ke(test, &keys)) < 0 ||
+	    (ret = krb5_test_get_Ki(test, &keys)) < 0)
+		goto out;
+
+	LOAD_BUF(&plain, test->plain);
+	LOAD_BUF(&ct, test->ct);
+
+	ret = krb5_load_enc_buf(test, &plain, buf);
+	if (ret < 0)
+		goto out;
+
+	sg_init_one(sg, buf, 1024);
+	ret = crypto_krb5_encrypt(krb5, &keys, sg, 1, 1024,
+				  krb5->conf_len, plain.len, true);
+	if (ret < 0) {
+		CHECK(1);
+		pr_warn("Encryption failed %d\n", ret);
+		goto out;
+	}
+	len = ret;
+
+	if (CHECK(len != ct.len)) {
+		pr_warn("Encrypted length mismatch %zu != %u\n", len, ct.len);
+		goto out;
+	}
+
+	if (memcmp(buf, ct.data, ct.len) != 0) {
+		CHECK(1);
+		pr_warn("Ciphertext mismatch\n");
+		pr_warn("BUF %*phN\n", ct.len, buf);
+		pr_warn("CT  %*phN\n", ct.len, ct.data);
+		ret = -EKEYREJECTED;
+		goto out;
+	}
+
+	offset = 0;
+	ret = crypto_krb5_decrypt(krb5, &keys, sg, 1, &offset, &len, &error_code);
+	if (ret < 0) {
+		CHECK(1);
+		pr_warn("Decryption failed %d\n", ret);
+		goto out;
+	}
+
+	if (CHECK(len != plain.len))
+		goto out;
+
+	if (memcmp(buf + offset, plain.data, plain.len) != 0) {
+		CHECK(1);
+		pr_warn("Plaintext mismatch\n");
+		pr_warn("BUF %*phN\n", plain.len, buf + offset);
+		pr_warn("PT  %*phN\n", plain.len, plain.data);
+		ret = -EKEYREJECTED;
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	clear_buf(&ct);
+	clear_buf(&plain);
+	crypto_krb5_free_enc_keys(&keys);
+	return ret;
+}
+
+static int krb5_test_one_mic(const struct krb5_mic_test *test, void *buf)
+{
+	const struct krb5_enctype *krb5 = test->krb5;
+	struct crypto_shash *Kc = NULL;
+	struct scatterlist sg[1];
+	struct krb5_buffer plain = {}, mic = {};
+	size_t offset, len;
+	int ret, error_code;
+
+	pr_notice("Running %s %s\n", krb5->name, test->name);
+
+	if ((ret = krb5_test_get_Kc(test, &Kc)) < 0)
+		goto out;
+
+	LOAD_BUF(&plain, test->plain);
+	LOAD_BUF(&mic, test->mic);
+
+	ret = krb5_load_mic_buf(test, &plain, buf);
+	if (ret < 0)
+		goto out;
+
+	sg_init_one(sg, buf, 1024);
+
+	ret = crypto_krb5_get_mic(krb5, Kc, NULL, sg, 1, 1024,
+				  krb5->cksum_len, plain.len);
+	if (ret < 0) {
+		CHECK(1);
+		pr_warn("Get MIC failed %d\n", ret);
+		goto out;
+	}
+	len = ret;
+
+	if (CHECK(len != plain.len + mic.len)) {
+		pr_warn("MIC length mismatch %zu != %u\n", len, plain.len + mic.len);
+		goto out;
+	}
+
+	if (memcmp(buf, mic.data, mic.len) != 0) {
+		CHECK(1);
+		pr_warn("MIC mismatch\n");
+		pr_warn("BUF %*phN\n", mic.len, buf);
+		pr_warn("MIC %*phN\n", mic.len, mic.data);
+		ret = -EKEYREJECTED;
+		goto out;
+	}
+
+	offset = 0;
+	ret = crypto_krb5_verify_mic(krb5, Kc, NULL, sg, 1,
+				     &offset, &len, &error_code);
+	if (ret < 0) {
+		CHECK(1);
+		pr_warn("Verify MIC failed %d\n", ret);
+		goto out;
+	}
+
+	if (CHECK(len != plain.len))
+		goto out;
+	if (CHECK(offset != mic.len))
+		goto out;
+
+	if (memcmp(buf + offset, plain.data, plain.len) != 0) {
+		CHECK(1);
+		pr_warn("Plaintext mismatch\n");
+		pr_warn("BUF %*phN\n", plain.len, buf + offset);
+		pr_warn("PT  %*phN\n", plain.len, plain.data);
+		ret = -EKEYREJECTED;
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	clear_buf(&mic);
+	clear_buf(&plain);
+	if (Kc)
+		crypto_free_shash(Kc);
+	return ret;
+}
+
+void krb5_selftest(void)
+{
+	void *buf;
+	bool fail = false;
+	int i;
+
+	buf = kmalloc(1024, GFP_KERNEL);
+	if (!buf)
+		return;
+
+	printk("\n");
+	pr_notice("Running selftests\n");
+
+	for (i = 0; krb5_prf_tests[i].krb5; i++) {
+		fail |= krb5_test_one_prf(&krb5_prf_tests[i]) < 0;
+		if (fail)
+			goto out;
+	}
+
+	for (i = 0; krb5_key_tests[i].krb5; i++) {
+		fail |= krb5_test_one_key(&krb5_key_tests[i]) < 0;
+		if (fail)
+			goto out;
+	}
+
+	for (i = 0; krb5_enc_tests[i].krb5; i++) {
+		memset(buf, 0x5a, 1024);
+		fail |= krb5_test_one_enc(&krb5_enc_tests[i], buf) < 0;
+		if (fail)
+			goto out;
+	}
+
+	for (i = 0; krb5_mic_tests[i].krb5; i++) {
+		memset(buf, 0x5a, 1024);
+		fail |= krb5_test_one_mic(&krb5_mic_tests[i], buf) < 0;
+		if (fail)
+			goto out;
+	}
+
+out:
+	pr_notice("Selftests %s\n", fail ? "failed" : "succeeded");
+	kfree(buf);
+}
diff --git a/crypto/krb5/selftest_data.c b/crypto/krb5/selftest_data.c
new file mode 100644
index 000000000000..9085723b730b
--- /dev/null
+++ b/crypto/krb5/selftest_data.c
@@ -0,0 +1,38 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Data for RxGK self-testing
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "internal.h"
+
+/*
+ * Pseudo-random function tests.
+ */
+const struct krb5_prf_test krb5_prf_tests[] = {
+	{/* END */}
+};
+
+/*
+ * Key derivation tests.
+ */
+const struct krb5_key_test krb5_key_tests[] = {
+	{/* END */}
+};
+
+/*
+ * Encryption tests.
+ */
+const struct krb5_enc_test krb5_enc_tests[] = {
+	{/* END */}
+};
+
+/*
+ * Checksum generation tests.
+ */
+const struct krb5_mic_test krb5_mic_tests[] = {
+	{/* END */}
+};