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

From: David Howells
Date: Thu Nov 12 2020 - 07:59:09 EST


Implement self-testing infrastructure to test the pseudo-random function,
key derivation, encryption and checksumming.

Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
---

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 --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@xxxxxxxxxx)
+ */
+
+#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@xxxxxxxxxx)
+ */
+
+#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 */}
+};