2008-07-03 20:21:19

by Neil Horman

[permalink] [raw]
Subject: [PATCH 1/2] RNG: Add Pseudo Random Number Generator to kernel

Patch 1/2: Add prng files to source tree

Signed-off-by: Neil Horman <[email protected]>


prng.c | 409 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
prng.h | 27 ++++
2 files changed, 436 insertions(+)


diff --git a/crypto/prng.c b/crypto/prng.c
new file mode 100644
index 0000000..1310175
--- /dev/null
+++ b/crypto/prng.c
@@ -0,0 +1,409 @@
+/*
+ * PRNG: Pseudo Random Number Generator
+ * Based on NIST Recommended PRNG From ANSI X9.31 Appendix A.2.4 using
+ * AES 128 cipher in RFC3686 ctr mode
+ *
+ * (C) Neil Horman <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * any later version.
+ *
+ *
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/scatterlist.h>
+#include <linux/string.h>
+#include <linux/crypto.h>
+#include <linux/highmem.h>
+#include <linux/moduleparam.h>
+#include <linux/jiffies.h>
+#include <linux/timex.h>
+#include <linux/interrupt.h>
+#include <linux/miscdevice.h>
+#include "prng.h"
+
+#define TEST_PRNG_ON_START 0
+
+#define DEFAULT_PRNG_KEY "0123456789abcdef1011"
+#define DEFAULT_PRNG_KSZ 20
+#define DEFAULT_PRNG_IV "defaultv"
+#define DEFAULT_PRNG_IVSZ 8
+#define DEFAULT_BLK_SZ 16
+#define DEFAULT_V_SEED "zaybxcwdveuftgsh"
+
+/*
+ * Flags for the prng_context flags field
+ */
+
+#define PRNG_FIXED_SIZE 0x1
+#define PRNG_NEED_RESET 0x2
+
+/*
+ * Note: DT is our counter value
+ * I is our intermediate value
+ * V is our seed vector
+ * See http://csrc.nist.gov/groups/STM/cavp/documents/rng/931rngext.pdf
+ * for implementation details
+ */
+
+
+struct prng_context {
+ char *prng_key;
+ char *prng_iv;
+ spinlock_t prng_lock;
+ unsigned char rand_data[DEFAULT_BLK_SZ];
+ unsigned char last_rand_data[DEFAULT_BLK_SZ];
+ unsigned char DT[DEFAULT_BLK_SZ];
+ unsigned char I[DEFAULT_BLK_SZ];
+ unsigned char V[DEFAULT_BLK_SZ];
+ u32 rand_data_valid;
+ struct crypto_blkcipher *tfm;
+ u32 flags;
+};
+
+static int dbg;
+
+static void hexdump(char *note, unsigned char *buf, unsigned int len)
+{
+ if (dbg) {
+ printk(KERN_CRIT "%s", note);
+ print_hex_dump(KERN_CONT, "", DUMP_PREFIX_OFFSET,
+ 16, 1,
+ buf, len, false);
+ }
+}
+
+#define dbgprint(format, args...) do {if(dbg) printk(format, ##args);} while(0)
+
+static void xor_vectors(unsigned char *in1, unsigned char *in2,
+ unsigned char *out, unsigned int size)
+{
+ int i;
+
+ for (i=0;i<size;i++)
+ out[i] = in1[i] ^ in2[i];
+
+}
+/*
+ * Returns DEFAULT_BLK_SZ bytes of random data per call
+ * returns 0 if generation succeded, <0 if something went wrong
+ */
+static int _get_more_prng_bytes(struct prng_context *ctx)
+{
+ int i;
+ struct blkcipher_desc desc;
+ struct scatterlist sg_in, sg_out;
+ int ret;
+ unsigned char tmp[DEFAULT_BLK_SZ];
+
+ desc.tfm = ctx->tfm;
+ desc.flags = 0;
+
+
+ dbgprint(KERN_CRIT "Calling _get_more_prng_bytes for context %p\n",ctx);
+
+ hexdump("Input DT: ", ctx->DT, DEFAULT_BLK_SZ);
+ hexdump("Input I: ", ctx->I, DEFAULT_BLK_SZ);
+ hexdump("Input V: ", ctx->V, DEFAULT_BLK_SZ);
+
+ /*
+ * This algorithm is a 3 stage state machine
+ */
+ for (i=0;i<3;i++) {
+
+ desc.tfm = ctx->tfm;
+ desc.flags = 0;
+ switch (i) {
+ case 0:
+ /*
+ * Start by encrypting the counter value
+ * This gives us an intermediate value I
+ */
+ memcpy(tmp, ctx->DT, DEFAULT_BLK_SZ);
+ sg_init_one(&sg_out, &ctx->I[0], DEFAULT_BLK_SZ);
+ hexdump("tmp stage 0: ", tmp, DEFAULT_BLK_SZ);
+ break;
+ case 1:
+
+ /*
+ * Next xor I with our secret vector V
+ * encrypt that result to obtain our
+ * pseudo random data which we output
+ */
+ xor_vectors(ctx->I, ctx->V, tmp, DEFAULT_BLK_SZ);
+ sg_init_one(&sg_out, &ctx->rand_data[0], DEFAULT_BLK_SZ);
+ hexdump("tmp stage 1: ", tmp, DEFAULT_BLK_SZ);
+ break;
+ case 2:
+ /*
+ * First check that we didn't produce the same random data
+ * that we did last time around through this
+ */
+ if (!memcmp(ctx->rand_data, ctx->last_rand_data, DEFAULT_BLK_SZ)) {
+ printk(KERN_ERR "ctx %p Failed repetition check!\n",
+ ctx);
+ ctx->flags |= PRNG_NEED_RESET;
+ return -1;
+ }
+
+ /*
+ * Lastly xor the random data with I
+ * and encrypt that to obtain a new secret vector V
+ */
+ xor_vectors(ctx->rand_data, ctx->I, tmp, DEFAULT_BLK_SZ);
+ sg_init_one(&sg_out, &ctx->V[0], DEFAULT_BLK_SZ);
+ hexdump("tmp stage 2: ", tmp, DEFAULT_BLK_SZ);
+ break;
+ }
+
+ /* Initialize our input buffer */
+ sg_init_one(&sg_in, &tmp[0], DEFAULT_BLK_SZ);
+
+ /* do the encryption */
+ ret = crypto_blkcipher_encrypt(&desc, &sg_out, &sg_in, DEFAULT_BLK_SZ);
+
+ /* And check the result */
+ if (ret) {
+ dbgprint(KERN_CRIT "Encryption of new block failed for context %p\n",ctx);
+ ctx->rand_data_valid = DEFAULT_BLK_SZ;
+ return -1;
+ }
+
+ }
+
+ /*
+ * Now update our DT value
+ */
+ for (i=DEFAULT_BLK_SZ-1;i>0;i--) {
+ ctx->DT[i] = ctx->DT[i-1];
+ }
+ ctx->DT[0] += 1;
+
+ dbgprint("Returning new block for context %p\n",ctx);
+ ctx->rand_data_valid = 0;
+
+ hexdump("Output DT: ", ctx->DT, DEFAULT_BLK_SZ);
+ hexdump("Output I: ", ctx->I, DEFAULT_BLK_SZ);
+ hexdump("Output V: ", ctx->V, DEFAULT_BLK_SZ);
+ hexdump("New Random Data: ", ctx->rand_data, DEFAULT_BLK_SZ);
+
+ return 0;
+}
+
+/* Our exported functions */
+int get_prng_bytes(char *buf, int nbytes, struct prng_context *ctx)
+{
+ unsigned long flags;
+ unsigned char *ptr = buf;
+ unsigned int byte_count = (unsigned int)nbytes;
+ int err;
+
+
+ if (nbytes < 0)
+ return -EINVAL;
+
+ spin_lock_irqsave(&ctx->prng_lock, flags);
+
+ err = -EFAULT;
+ if (ctx->flags & PRNG_NEED_RESET)
+ goto done;
+
+ /*
+ * If the FIXED_SIZE flag is on, only return whole blocks of
+ * pseudo random data
+ */
+ err = -EINVAL;
+ if (ctx->flags & PRNG_FIXED_SIZE) {
+ if (nbytes < DEFAULT_BLK_SZ)
+ goto done;
+ byte_count = DEFAULT_BLK_SZ;
+ }
+
+ err = byte_count;
+
+ dbgprint(KERN_CRIT "getting %d random bytes for context %p\n",byte_count, ctx);
+
+
+remainder:
+ if (ctx->rand_data_valid == DEFAULT_BLK_SZ) {
+ if (_get_more_prng_bytes(ctx) < 0) {
+ memset(buf, 0, nbytes);
+ err = -EFAULT;
+ goto done;
+ }
+ }
+
+ /*
+ * Copy up to the next whole block size
+ */
+ if (byte_count < DEFAULT_BLK_SZ) {
+ for (;ctx->rand_data_valid < DEFAULT_BLK_SZ; ctx->rand_data_valid++) {
+ *ptr = ctx->rand_data[ctx->rand_data_valid];
+ ptr++;
+ byte_count--;
+ if (byte_count == 0)
+ goto done;
+ }
+ }
+
+ /*
+ * Now copy whole blocks
+ */
+ for(;byte_count >= DEFAULT_BLK_SZ; byte_count -= DEFAULT_BLK_SZ) {
+ if (_get_more_prng_bytes(ctx) < 0) {
+ memset(buf, 0, nbytes);
+ err = -1;
+ goto done;
+ }
+ memcpy(ptr, ctx->rand_data, DEFAULT_BLK_SZ);
+ ctx->rand_data_valid += DEFAULT_BLK_SZ;
+ ptr += DEFAULT_BLK_SZ;
+ }
+
+ /*
+ * Now copy any extra partial data
+ */
+ if (byte_count)
+ goto remainder;
+
+done:
+ spin_unlock_irqrestore(&ctx->prng_lock, flags);
+ dbgprint(KERN_CRIT "returning %d from get_prng_bytes in context %p\n",err, ctx);
+ return err;
+}
+EXPORT_SYMBOL_GPL(get_prng_bytes);
+
+struct prng_context *alloc_prng_context(void)
+{
+ struct prng_context *ctx=kzalloc(sizeof(struct prng_context), GFP_KERNEL);
+
+ spin_lock_init(&ctx->prng_lock);
+
+ if (reset_prng_context(ctx, NULL, NULL, NULL, NULL)) {
+ kfree(ctx);
+ ctx = NULL;
+ }
+
+ dbgprint(KERN_CRIT "returning context %p\n",ctx);
+ return ctx;
+}
+
+EXPORT_SYMBOL_GPL(alloc_prng_context);
+
+void free_prng_context(struct prng_context *ctx)
+{
+ crypto_free_blkcipher(ctx->tfm);
+ kfree(ctx);
+}
+EXPORT_SYMBOL_GPL(free_prng_context);
+
+int reset_prng_context(struct prng_context *ctx,
+ unsigned char *key, unsigned char *iv,
+ unsigned char *V, unsigned char *DT)
+{
+ int ret;
+ int iv_len;
+ int rc = -EFAULT;
+
+ spin_lock(&ctx->prng_lock);
+ ctx->flags |= PRNG_NEED_RESET;
+
+ if (key)
+ memcpy(ctx->prng_key,key,strlen(ctx->prng_key));
+ else
+ ctx->prng_key = DEFAULT_PRNG_KEY;
+
+ if (iv)
+ memcpy(ctx->prng_iv,iv, strlen(ctx->prng_iv));
+ else
+ ctx->prng_iv = DEFAULT_PRNG_IV;
+
+ if (V)
+ memcpy(ctx->V,V,DEFAULT_BLK_SZ);
+ else
+ memcpy(ctx->V,DEFAULT_V_SEED,DEFAULT_BLK_SZ);
+
+ if (DT)
+ memcpy(ctx->DT, DT, DEFAULT_BLK_SZ);
+ else
+ memset(ctx->DT, 0, DEFAULT_BLK_SZ);
+
+ memset(ctx->rand_data,0,DEFAULT_BLK_SZ);
+ memset(ctx->last_rand_data,0,DEFAULT_BLK_SZ);
+
+ if (ctx->tfm)
+ crypto_free_blkcipher(ctx->tfm);
+
+ ctx->tfm = crypto_alloc_blkcipher("rfc3686(ctr(aes))",0,0);
+ if (!ctx->tfm) {
+ dbgprint(KERN_CRIT "Failed to alloc crypto tfm for context %p\n",ctx->tfm);
+ goto out;
+ }
+
+ ctx->rand_data_valid = DEFAULT_BLK_SZ;
+
+ ret = crypto_blkcipher_setkey(ctx->tfm, ctx->prng_key, strlen(ctx->prng_key));
+ if (ret) {
+ dbgprint(KERN_CRIT "PRNG: setkey() failed flags=%x\n",
+ crypto_blkcipher_get_flags(ctx->tfm));
+ crypto_free_blkcipher(ctx->tfm);
+ goto out;
+ }
+
+ iv_len = crypto_blkcipher_ivsize(ctx->tfm);
+ if (iv_len) {
+ crypto_blkcipher_set_iv(ctx->tfm, ctx->prng_iv, iv_len);
+ }
+ rc = 0;
+ ctx->flags &= ~PRNG_NEED_RESET;
+out:
+ spin_unlock(&ctx->prng_lock);
+
+ return rc;
+
+}
+EXPORT_SYMBOL_GPL(reset_prng_context);
+
+/* Module initalization */
+static int __init prng_mod_init(void)
+{
+
+#ifdef TEST_PRNG_ON_START
+ int i;
+ unsigned char tmpbuf[DEFAULT_BLK_SZ];
+
+ struct prng_context *ctx = alloc_prng_context();
+ if (ctx == NULL)
+ return -EFAULT;
+ for (i=0;i<16;i++) {
+ if (get_prng_bytes(tmpbuf, DEFAULT_BLK_SZ, ctx) < 0) {
+ free_prng_context(ctx);
+ return -EFAULT;
+ }
+ }
+ free_prng_context(ctx);
+#endif
+
+ return 0;
+}
+
+static void __exit prng_mod_fini(void)
+{
+ return;
+}
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Software Pseudo Random Number Generator");
+MODULE_AUTHOR("Neil Horman <[email protected]>");
+module_param(dbg, int, 0);
+MODULE_PARM_DESC(dbg, "Boolean to enable debugging (0/1 == off/on)");
+module_init(prng_mod_init);
+module_exit(prng_mod_fini);
diff --git a/crypto/prng.h b/crypto/prng.h
new file mode 100644
index 0000000..d966791
--- /dev/null
+++ b/crypto/prng.h
@@ -0,0 +1,27 @@
+/*
+ * PRNG: Pseudo Random Number Generator
+ *
+ * (C) Neil Horman <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * any later version.
+ *
+ *
+ */
+
+#ifndef _PRNG_H_
+#define _PRNG_H_
+struct prng_context;
+
+int get_prng_bytes(char *buf, int nbytes, struct prng_context *ctx);
+struct prng_context *alloc_prng_context(void);
+int reset_prng_context(struct prng_context *ctx,
+ unsigned char *key, unsigned char *iv,
+ unsigned char *V,
+ unsigned char *DT);
+void free_prng_context(struct prng_context *ctx);
+
+#endif
+
--
/***************************************************
*Neil Horman
*[email protected]
*gpg keyid: 1024D / 0x92A74FA1
*http://pgp.mit.edu
***************************************************/


2008-07-16 12:45:36

by Herbert Xu

[permalink] [raw]
Subject: Re: [PATCH 1/2] RNG: Add Pseudo Random Number Generator to kernel

On Thu, Jul 03, 2008 at 04:21:02PM -0400, Neil Horman wrote:
>
> + * See http://csrc.nist.gov/groups/STM/cavp/documents/rng/931rngext.pdf
> + * for implementation details

BTW, any reason why counter mode is used? This document specifies
simple AES only, with no modes of operations.

Thanks,
--
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

2008-07-16 13:25:51

by Neil Horman

[permalink] [raw]
Subject: Re: [PATCH 1/2] RNG: Add Pseudo Random Number Generator to kernel

On Wed, Jul 16, 2008 at 08:45:33PM +0800, Herbert Xu wrote:
> On Thu, Jul 03, 2008 at 04:21:02PM -0400, Neil Horman wrote:
> >
> > + * See http://csrc.nist.gov/groups/STM/cavp/documents/rng/931rngext.pdf
> > + * for implementation details
>
> BTW, any reason why counter mode is used? This document specifies
> simple AES only, with no modes of operations.
>
I think I have an irc log somewhere where I thought we discussed this, and
decided that counter mode allowed us a larger period in the random number
stream, but you're correct, simple AES mode is called for in the specification.
I suppose its better to do whats written down there rather than do what makes
intuitive sense, I'll change that.

Regards
Neil

> Thanks,
> --
> Visit Openswan at http://www.openswan.org/
> Email: Herbert Xu ~{PmV>HI~} <[email protected]>
> Home Page: http://gondor.apana.org.au/~herbert/
> PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

--
/****************************************************
* Neil Horman <[email protected]>
* Software Engineer, Red Hat
****************************************************/

2008-07-16 13:39:03

by Herbert Xu

[permalink] [raw]
Subject: Re: [PATCH 1/2] RNG: Add Pseudo Random Number Generator to kernel

On Wed, Jul 16, 2008 at 09:25:40AM -0400, Neil Horman wrote:
>
> I think I have an irc log somewhere where I thought we discussed this, and
> decided that counter mode allowed us a larger period in the random number
> stream, but you're correct, simple AES mode is called for in the specification.
> I suppose its better to do whats written down there rather than do what makes
> intuitive sense, I'll change that.

Yes since this is primarily for the purposes of fulfilling the
specifications this would be the best course to take.

Thanks!
--
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

2008-07-16 13:45:00

by Herbert Xu

[permalink] [raw]
Subject: Re: [PATCH 1/2] RNG: Add Pseudo Random Number Generator to kernel

On Thu, Jul 03, 2008 at 04:21:02PM -0400, Neil Horman wrote:
>> + /*
> + * Now update our DT value
> + */
> + for (i=DEFAULT_BLK_SZ-1;i>0;i--) {
> + ctx->DT[i] = ctx->DT[i-1];
> + }
> + ctx->DT[0] += 1;

Is there any reason behind using this algorithm as opposed to
a simple counter?

Cheers,
--
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

2008-07-16 13:58:51

by Neil Horman

[permalink] [raw]
Subject: Re: [PATCH 1/2] RNG: Add Pseudo Random Number Generator to kernel

On Wed, Jul 16, 2008 at 09:44:56PM +0800, Herbert Xu wrote:
> On Thu, Jul 03, 2008 at 04:21:02PM -0400, Neil Horman wrote:
> >> + /*
> > + * Now update our DT value
> > + */
> > + for (i=DEFAULT_BLK_SZ-1;i>0;i--) {
> > + ctx->DT[i] = ctx->DT[i-1];
> > + }
> > + ctx->DT[0] += 1;
>
> Is there any reason behind using this algorithm as opposed to
> a simple counter?
>
No, this was rather a stream of conscious thing, shifting incrementing numbers
through the buffer, I can certainly make it a simple counter.

Neil

> Cheers,
> --
> Visit Openswan at http://www.openswan.org/
> Email: Herbert Xu ~{PmV>HI~} <[email protected]>
> Home Page: http://gondor.apana.org.au/~herbert/
> PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

--
/****************************************************
* Neil Horman <[email protected]>
* Software Engineer, Red Hat
****************************************************/

2008-07-16 14:03:20

by Herbert Xu

[permalink] [raw]
Subject: Re: [PATCH 1/2] RNG: Add Pseudo Random Number Generator to kernel

On Wed, Jul 16, 2008 at 09:58:41AM -0400, Neil Horman wrote:
>
> No, this was rather a stream of conscious thing, shifting incrementing numbers
> through the buffer, I can certainly make it a simple counter.

Yeah I think a simple counter fits the spirit of the specification
better. It'll also have a much longer period than this.

Thanks,
--
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt