| /* |
| * 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); |