Commit b8454eeb authored by Neil Horman's avatar Neil Horman Committed by Herbert Xu

crypto: prng - Deterministic CPRNG

This patch adds a cryptographic pseudo-random number generator
based on CTR(AES-128).  It is meant to be used in cases where a
deterministic CPRNG is required.

One of the first applications will be as an input in the IPsec IV
generation process.
Signed-off-by: default avatarNeil Horman <nhorman@tuxdriver.com>
Signed-off-by: default avatarHerbert Xu <herbert@gondor.apana.org.au>
parent 166247f4
...@@ -666,6 +666,15 @@ config CRYPTO_LZO ...@@ -666,6 +666,15 @@ config CRYPTO_LZO
help help
This is the LZO algorithm. This is the LZO algorithm.
comment "Random Number Generation"
config CRYPTO_PRNG
tristate "Pseudo Random Number Generation for Cryptographic modules"
help
This option enables the generic pseudo random number generator
for cryptographic modules. Uses the Algorithm specified in
ANSI X9.31 A.2.4
source "drivers/crypto/Kconfig" source "drivers/crypto/Kconfig"
endif # if CRYPTO endif # if CRYPTO
...@@ -69,7 +69,7 @@ obj-$(CONFIG_CRYPTO_MICHAEL_MIC) += michael_mic.o ...@@ -69,7 +69,7 @@ obj-$(CONFIG_CRYPTO_MICHAEL_MIC) += michael_mic.o
obj-$(CONFIG_CRYPTO_CRC32C) += crc32c.o obj-$(CONFIG_CRYPTO_CRC32C) += crc32c.o
obj-$(CONFIG_CRYPTO_AUTHENC) += authenc.o obj-$(CONFIG_CRYPTO_AUTHENC) += authenc.o
obj-$(CONFIG_CRYPTO_LZO) += lzo.o obj-$(CONFIG_CRYPTO_LZO) += lzo.o
obj-$(CONFIG_CRYPTO_PRNG) += prng.o
obj-$(CONFIG_CRYPTO_TEST) += tcrypt.o obj-$(CONFIG_CRYPTO_TEST) += tcrypt.o
# #
......
/*
* 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 <nhorman@tuxdriver.com>
*
* 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;
}
memcpy(ctx->last_rand_data, ctx->rand_data, DEFAULT_BLK_SZ);
/*
* 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 <nhorman@tuxdriver.com>");
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);
/*
* PRNG: Pseudo Random Number Generator
*
* (C) Neil Horman <nhorman@tuxdriver.com>
*
* 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment