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

Signed-off-by: David Howells <dhowe...@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 --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 (dhowe...@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 (dhowe...@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 */}
+};


Reply via email to