[PATCH v25 10/12] LRNG - add TRNG support
From: Stephan Müller
Date: Sat Nov 16 2019 - 04:42:25 EST
The True Random Number Generator (TRNG) provides a random number
generator with prediction resistance (SP800-90A terminology) or an NTG.1
(AIS 31 terminology).
When enabled, it obtains random numbers from the entropy pool and
maintains the information with how much entropy it was seeded with. The
TRNG only generates as much output data as it has as entropy.
The secondary DRNGs seed from the TRNG if it is present. In addition,
the /dev/random device accesses the TRNG.
If the TRNG is disabled, the secondary DRNGs seed from the entropy pool
and /dev/random behaves like getrandom(2).
The TRNG benefits from the switchable DRNG support which implies that
data provided via /dev/random is generated by the loaded DRNG.
CC: "Eric W. Biederman" <ebiederm@xxxxxxxxxxxx>
CC: "Alexander E. Patrakov" <patrakov@xxxxxxxxx>
CC: "Ahmed S. Darwish" <darwish.07@xxxxxxxxx>
CC: "Theodore Y. Ts'o" <tytso@xxxxxxx>
CC: Willy Tarreau <w@xxxxxx>
CC: Matthew Garrett <mjg59@xxxxxxxxxxxxx>
CC: Vito Caputo <vcaputo@xxxxxxxxxxx>
CC: Andreas Dilger <adilger.kernel@xxxxxxxxx>
CC: Jan Kara <jack@xxxxxxx>
CC: Ray Strode <rstrode@xxxxxxxxxx>
CC: William Jon McCann <mccann@xxxxxxx>
CC: zhangjs <zachary@xxxxxxxxxxxxxxxx>
CC: Andy Lutomirski <luto@xxxxxxxxxx>
CC: Florian Weimer <fweimer@xxxxxxxxxx>
CC: Lennart Poettering <mzxreary@xxxxxxxxxxx>
CC: Nicolai Stange <nstange@xxxxxxx>
Reviewed-by: Marcelo Henrique Cerri <marcelo.cerri@xxxxxxxxxxxxx>
Reviewed-by: Roman Drahtmueller <draht@xxxxxxxxxxxxxx>
Tested-by: Roman Drahtmüller <draht@xxxxxxxxxxxxxx>
Tested-by: Marcelo Henrique Cerri <marcelo.cerri@xxxxxxxxxxxxx>
Tested-by: Neil Horman <nhorman@xxxxxxxxxx>
Signed-off-by: Stephan Mueller <smueller@xxxxxxxxxx>
---
drivers/char/lrng/Kconfig | 22 +++
drivers/char/lrng/Makefile | 1 +
drivers/char/lrng/lrng_trng.c | 283 ++++++++++++++++++++++++++++++++++
3 files changed, 306 insertions(+)
create mode 100644 drivers/char/lrng/lrng_trng.c
diff --git a/drivers/char/lrng/Kconfig b/drivers/char/lrng/Kconfig
index 80fc723c67d2..122d67ee110e 100644
--- a/drivers/char/lrng/Kconfig
+++ b/drivers/char/lrng/Kconfig
@@ -91,4 +91,26 @@ config LRNG_JENT
time or at runtime with the lrng_base.jitterrng configuration
variable.
+config LRNG_TRNG_SUPPORT
+ bool "Enable True Random Number Generator support"
+ default y
+ help
+ The true random number generator (TRNG) support, also
+ known as DRNG with prediction resistance (SP800-90A
+ terminology) or NTG.1 (AIS 31 terminology), generates
+ random numbers after a successful reseed with entropy.
+ Only when new entropy is provided for a new generation
+ request, random data is provided with an equal amount
+ as entropy was added. The TRNG is available via
+ /dev/random.
+
+ If the support is not enabled, /dev/random ensures that
+ it received sufficient initial entropy and will produce
+ random data without requiring a constant reseed with
+ entropy. Yet it tries to regularly reseed itself with
+ fresh entropy.
+
+ With the TRNG support the /dev/random device will block
+ if insufficient entropy is available.
+
endif # LRNG
diff --git a/drivers/char/lrng/Makefile b/drivers/char/lrng/Makefile
index a87d800c9aae..1c72bc060bce 100644
--- a/drivers/char/lrng/Makefile
+++ b/drivers/char/lrng/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_LRNG_DRNG_SWITCH) += lrng_switch.o
obj-$(CONFIG_LRNG_DRBG) += lrng_drbg.o
obj-$(CONFIG_LRNG_KCAPI) += lrng_kcapi.o
obj-$(CONFIG_LRNG_JENT) += lrng_jent.o
+obj-$(CONFIG_LRNG_TRNG_SUPPORT) += lrng_trng.o
diff --git a/drivers/char/lrng/lrng_trng.c b/drivers/char/lrng/lrng_trng.c
new file mode 100644
index 000000000000..52ea33342a3f
--- /dev/null
+++ b/drivers/char/lrng/lrng_trng.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * LRNG True Random Number Generator (TRNG) processing
+ *
+ * Copyright (C) 2016 - 2019, Stephan Mueller <smueller@xxxxxxxxxx>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/lrng.h>
+
+#include "lrng_internal.h"
+
+/* TRNG state handle */
+struct lrng_trng {
+ void *trng; /* TRNG handle */
+ void *hash; /* Hash handle */
+ u32 trng_entropy_bits; /* TRNG entropy level */
+ const struct lrng_crypto_cb *crypto_cb; /* Crypto callbacks */
+ struct mutex lock;
+};
+
+/* TRNG for /dev/random and seed source for the secondary DRNG(s) */
+static struct lrng_trng lrng_trng = {
+ .trng = &primary_chacha20,
+ .crypto_cb = &lrng_cc20_crypto_cb,
+ .lock = __MUTEX_INITIALIZER(lrng_trng.lock)
+};
+
+/********************************** Helper ************************************/
+
+void lrng_trng_reset(void)
+{
+ lrng_trng.trng_entropy_bits = 0;
+ pr_debug("reset TRNG\n");
+}
+
+void lrng_trng_init(void)
+{
+ mutex_lock(&lrng_trng.lock);
+ lrng_trng_reset();
+ lrng_cc20_init_state(&primary_chacha20);
+ mutex_unlock(&lrng_trng.lock);
+}
+
+/************************* Random Number Generation ***************************/
+
+/* Caller must hold lrng_trng.lock */
+static int lrng_trng_generate(u8 *outbuf, u32 outbuflen)
+{
+ struct lrng_trng *trng = &lrng_trng;
+ const struct lrng_crypto_cb *crypto_cb = trng->crypto_cb;
+ int ret;
+
+ /*
+ * Only deliver as many bytes as the DRNG is seeded with except during
+ * initialization to provide a first seed to the secondary DRNG.
+ */
+ if (lrng_state_min_seeded())
+ outbuflen = min_t(u32, outbuflen, trng->trng_entropy_bits>>3);
+ else
+ outbuflen = min_t(u32, outbuflen,
+ LRNG_MIN_SEED_ENTROPY_BITS>>3);
+ if (!outbuflen)
+ return 0;
+
+ ret = crypto_cb->lrng_drng_generate_helper_full(trng->trng, outbuf,
+ outbuflen);
+ if (ret != outbuflen) {
+ pr_warn("getting random data from TRNG failed (%d)\n",
+ ret);
+ return ret;
+ }
+
+ if (trng->trng_entropy_bits > (u32)(ret<<3))
+ trng->trng_entropy_bits -= ret<<3;
+ else
+ trng->trng_entropy_bits = 0;
+ pr_debug("obtained %d bytes of random data from TRNG\n", ret);
+ pr_debug("TRNG entropy level at %u bits\n",
+ trng->trng_entropy_bits);
+
+ return ret;
+}
+
+/**
+ * Inject data into the TRNG with a given entropy value. The function calls
+ * the DRNG's update function. This function also generates random data if
+ * requested by caller. The caller is only returned the amount of random data
+ * that is at most equal to the amount of entropy that just seeded the DRNG.
+ *
+ * Note, this function seeds the TRNG and generates data in an atomic operation.
+ *
+ * @inbuf: buffer to inject
+ * @inbuflen: length of inbuf
+ * @entropy_bits: entropy value of the data in inbuf in bits
+ * @outbuf: buffer to fill immediately after seeding to get full entropy
+ * @outbuflen: length of outbuf
+ * @return: number of bytes written to outbuf, 0 if outbuf is not supplied,
+ * or < 0 in case of error
+ */
+static int lrng_trng_inject(const u8 *inbuf, u32 inbuflen, u32 entropy_bits,
+ u8 *outbuf, u32 outbuflen)
+{
+ struct lrng_trng *trng = &lrng_trng;
+ int ret;
+
+ /* cap the maximum entropy value to the provided data length */
+ entropy_bits = min_t(u32, entropy_bits, inbuflen<<3);
+
+ mutex_lock(&trng->lock);
+ ret = trng->crypto_cb->lrng_drng_seed_helper(trng->trng, inbuf,
+ inbuflen);
+ if (ret < 0) {
+ pr_warn("(re)seeding of TRNG failed\n");
+ goto unlock;
+ }
+ pr_debug("inject %u bytes with %u bits of entropy into TRNG\n",
+ inbuflen, entropy_bits);
+
+ /* Adjust the fill level indicator to at most the DRNG sec strength */
+ trng->trng_entropy_bits =
+ min_t(u32, trng->trng_entropy_bits + entropy_bits,
+ LRNG_DRNG_SECURITY_STRENGTH_BITS);
+ lrng_init_ops(trng->trng_entropy_bits);
+
+ if (outbuf && outbuflen)
+ ret = lrng_trng_generate(outbuf, outbuflen);
+
+unlock:
+ mutex_unlock(&trng->lock);
+ lrng_reader_wakeup();
+
+ return ret;
+}
+
+/**
+ * Seed the TRNG from the internal noise sources and generate random data. The
+ * seeding and the generation of random data is an atomic operation.
+ *
+ * lrng_pool_trylock() must be invoked successfully by caller.
+ */
+int lrng_trng_seed(u8 *outbuf, u32 outbuflen, bool drain)
+{
+ struct entropy_buf entropy_buf __aligned(LRNG_KCAPI_ALIGN);
+ struct lrng_trng *trng = &lrng_trng;
+ u32 total_entropy_bits;
+ int ret = 0, retrieved = 0;
+
+ /* Get available entropy in primary DRNG */
+ if (trng->trng_entropy_bits>>3) {
+ mutex_lock(&trng->lock);
+ ret = lrng_trng_generate(outbuf, outbuflen);
+ mutex_unlock(&trng->lock);
+ if (ret > 0) {
+ retrieved += ret;
+ if (ret == outbuflen)
+ goto out;
+
+ outbuf += ret;
+ outbuflen -= ret;
+ }
+ /* Disregard error code as another generate request is below. */
+ }
+
+ mutex_lock(&trng->lock);
+ total_entropy_bits = lrng_fill_seed_buffer(trng->crypto_cb, trng->hash,
+ &entropy_buf, drain);
+ mutex_unlock(&trng->lock);
+
+ pr_debug("reseed TRNG from internal noise sources with %u bits "
+ "of entropy\n", total_entropy_bits);
+
+ ret = lrng_trng_inject((u8 *)&entropy_buf, sizeof(entropy_buf),
+ total_entropy_bits,
+ outbuf, outbuflen);
+
+ memzero_explicit(&entropy_buf, sizeof(entropy_buf));
+
+ if (ret > 0)
+ retrieved += ret;
+
+out:
+ /* Allow the seeding operation to be called again */
+ lrng_pool_unlock();
+
+ return (ret >= 0) ? retrieved : ret;
+}
+
+/**
+ * Obtain random data from TRNG with information theoretical entropy by
+ * triggering a reseed. The TRNG will only return as many random bytes as it
+ * was seeded with.
+ *
+ * @outbuf: buffer to store the random data in
+ * @outbuflen: length of outbuf
+ * @return: < 0 on error
+ * >= 0 the number of bytes that were obtained
+ */
+int lrng_trng_get(u8 *outbuf, u32 outbuflen)
+{
+ int ret;
+
+ if (!outbuf || !outbuflen)
+ return 0;
+
+ lrng_drngs_init_cc20();
+
+ if (lrng_pool_trylock())
+ return -EINPROGRESS;
+ ret = lrng_trng_seed(outbuf, outbuflen, true);
+ if (ret >= 0) {
+ pr_debug("read %d bytes of full entropy data from TRNG\n", ret);
+ } else {
+ /* This is no error, but we have not generated anything */
+ if (ret == -EINPROGRESS)
+ return 0;
+ pr_debug("reading data from TRNG failed: %d\n", ret);
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_LRNG_DRNG_SWITCH
+int lrng_trng_switch(const struct lrng_crypto_cb *cb)
+{
+ int ret;
+ u8 seed[LRNG_DRNG_SECURITY_STRENGTH_BYTES];
+ void *trng, *hash;
+
+ trng = cb->lrng_drng_alloc(LRNG_DRNG_SECURITY_STRENGTH_BYTES);
+ if (IS_ERR(trng))
+ return PTR_ERR(trng);
+
+ hash = cb->lrng_hash_alloc(seed, sizeof(seed));
+ if (IS_ERR(hash)) {
+ pr_warn("could not allocate new LRNG pool hash (%ld)\n",
+ PTR_ERR(hash));
+ cb->lrng_drng_dealloc(trng);
+ return PTR_ERR(hash);
+ }
+
+ /* Update primary DRNG */
+ mutex_lock(&lrng_trng.lock);
+ /* pull from existing DRNG to seed new DRNG */
+ ret = lrng_trng.crypto_cb->lrng_drng_generate_helper_full(
+ lrng_trng.trng, seed, sizeof(seed));
+ if (ret < 0) {
+ lrng_trng_reset();
+ pr_warn("getting random data from TRNG failed (%d)\n", ret);
+ } else {
+ /*
+ * No change of the seed status as the old and new DRNG have
+ * same security strength.
+ */
+ ret = cb->lrng_drng_seed_helper(trng, seed, ret);
+ if (ret < 0) {
+ lrng_trng_reset();
+ pr_warn("seeding of new TRNG failed (%d)\n", ret);
+ } else {
+ pr_debug("seeded new TRNG instance from old TRNG "
+ "instance\n");
+ }
+ }
+ memzero_explicit(seed, sizeof(seed));
+
+ if (!lrng_get_available())
+ lrng_trng_reset();
+ lrng_trng.crypto_cb->lrng_drng_dealloc(lrng_trng.trng);
+ lrng_trng.trng = trng;
+
+ lrng_trng.crypto_cb->lrng_hash_dealloc(lrng_trng.hash);
+ lrng_trng.hash = hash;
+
+ lrng_trng.crypto_cb = cb;
+
+ mutex_unlock(&lrng_trng.lock);
+
+ pr_info("TRNG allocated\n");
+
+ return ret;
+}
+#endif /* CONFIG_LRNG_DRNG_SWITCH */
--
2.23.0