Source: libbsd
Followup-For: Bug #747671

Dear Maintainer,

inspired by https://tools.ietf.org/html/draft-popov-tls-prohibiting-rc4-02
here's a patch to replace RC4 with ChaCha20 (sending it upstream
requires a bugzilla account :)

I've tried to keep it as small as possible, but since upstream started
calling static function "_rs_foo()" instead of "arc4_foo()" the result
is inconsistent naming ("_rs_stir_if_needed" vs "arc4_stir").


src/chacha_private.h is copied verbatim;
src/arc4random.c should be ok - if you spot any mistake it's my fault
only.

Regards.


---
 src/arc4random.c     | 202 ++++++++++++++++++++++++++--------------------
 src/chacha_private.h | 222 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 336 insertions(+), 88 deletions(-)
 create mode 100644 src/chacha_private.h

diff --git a/src/arc4random.c b/src/arc4random.c
index 20fce09..b7abb35 100644
--- a/src/arc4random.c
+++ b/src/arc4random.c
@@ -1,6 +1,7 @@
 /*
  * Copyright (c) 1996, David Mazieres <d...@uun.org>
  * Copyright (c) 2008, Damien Miller <d...@openbsd.org>
+ * Copyright (c) 2013, Markus Friedl <mar...@openbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -16,20 +17,7 @@
  */
 
 /*
- * Arc4 random number generator for OpenBSD.
- *
- * This code is derived from section 17.1 of Applied Cryptography,
- * second edition, which describes a stream cipher allegedly
- * compatible with RSA Labs "RC4" cipher (the actual description of
- * which is a trade secret).  The same algorithm is used as a stream
- * cipher called "arcfour" in Tatu Ylonen's ssh package.
- *
- * Here the stream cipher has been modified always to include the time
- * when initializing the state.  That makes it impossible to
- * regenerate the same random sequence twice, so this can't be used
- * for encryption, but will generate good random numbers.
- *
- * RC4 is a registered trademark of RSA Laboratories.
+ * ChaCha based random number generator for OpenBSD.
  */
 
 #include <sys/cdefs.h>
@@ -41,15 +29,19 @@ __FBSDID("$FreeBSD$");
 #include <fcntl.h>
 #include <unistd.h>
 #include <pthread.h>
+#include <string.h>
+
+#define KEYSTREAM_ONLY
+#include "chacha_private.h"
 
-struct arc4_stream {
-       u_int8_t i;
-       u_int8_t j;
-       u_int8_t s[256];
-};
+/* "min" in arc4random_uniform() */
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
 
 #define        RANDOMDEV       "/dev/urandom"
-#define KEYSIZE                128
+#define KEYSZ  32
+#define IVSZ   8
+#define BLOCKSZ        64
+#define RSBUFSZ        (16*BLOCKSZ)
 
 #ifdef __REENTRANT
 static pthread_mutex_t arc4random_mtx = PTHREAD_MUTEX_INITIALIZER;
@@ -60,45 +52,42 @@ static pthread_mutex_t arc4random_mtx = 
PTHREAD_MUTEX_INITIALIZER;
 #define        THREAD_UNLOCK()
 #endif
 
-static struct arc4_stream rs;
+/*
+ * XXX rs, rsx should be pointers, keeping them this way to reduce diff size;
+ * 
http://openbsd.cs.toronto.edu/cgi-bin/cvsweb/src/lib/libc/crypt/arc4random.c.diff?r1=1.27&r2=1.28
+ */
+static struct _rs {
+       size_t          rs_have;        /* valid bytes at end of rs_buf */
+       size_t          rs_count;       /* bytes till reseed */
+} rs[1];
+static struct _rsx {
+       chacha_ctx      rs_chacha;      /* chacha context for random keystream 
*/
+       u_char          rs_buf[RSBUFSZ];        /* keystream blocks */
+} rsx[1];
 static int rs_initialized;
 static int rs_stired;
-static int arc4_count;
 
-static inline u_int8_t arc4_getbyte(void);
 static void arc4_stir(void);
 
 static inline void
 arc4_init(void)
 {
-       int     n;
-
-       for (n = 0; n < 256; n++)
-               rs.s[n] = n;
-       rs.i = 0;
-       rs.j = 0;
+       /* NOP - chacha only needs key/iv setup */
 }
 
 static inline void
-arc4_addrandom(u_char *dat, int datlen)
+arc4_addrandom(uint8_t *buf, int datlen)
 {
-       int     n;
-       u_int8_t si;
-
-       rs.i--;
-       for (n = 0; n < 256; n++) {
-               rs.i = (rs.i + 1);
-               si = rs.s[rs.i];
-               rs.j = (rs.j + si + dat[n % datlen]);
-               rs.s[rs.i] = rs.s[rs.j];
-               rs.s[rs.j] = si;
-       }
-       rs.j = rs.i;
+       if (datlen != KEYSZ + IVSZ)
+               abort();        /* canthappen - this is an assertion. */
+       chacha_keysetup(&rsx->rs_chacha, buf, KEYSZ * 8, 0);
+       chacha_ivsetup(&rsx->rs_chacha, buf + KEYSZ);
 }
 
 static void
 arc4_stir(void)
 {
+       enum { KEYSIZE = KEYSZ + IVSZ };
        int done, fd, n;
        struct {
                struct timeval  tv;
@@ -121,62 +110,82 @@ arc4_stir(void)
 
        arc4_addrandom((u_char *)&rdat, KEYSIZE);
 
-       /*
-        * Throw away the first N bytes of output, as suggested in the
-        * paper "Weaknesses in the Key Scheduling Algorithm of RC4"
-        * by Fluher, Mantin, and Shamir.  N=1024 is based on
-        * suggestions in the paper "(Not So) Random Shuffles of RC4"
-        * by Ilya Mironov.
-        */
-       for (n = 0; n < 1024; n++)
-               (void) arc4_getbyte();
-       arc4_count = 1600000;
+       /* invalidate rs_buf */
+       rs->rs_have = 0;
+       memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf));
+
+       /* Chacha doesn't need to discard anything */
+       rs->rs_count = 1600000;
 }
 
-static inline u_int8_t
-arc4_getbyte(void)
+static void
+arc4_check_init(void)
 {
-       u_int8_t si, sj;
+       if (!rs_initialized) {
+               arc4_init();
+               rs_initialized = 1;
+       }
+}
 
-       rs.i = (rs.i + 1);
-       si = rs.s[rs.i];
-       rs.j = (rs.j + si);
-       sj = rs.s[rs.j];
-       rs.s[rs.i] = sj;
-       rs.s[rs.j] = si;
+static inline void
+arc4_check_stir(void)
+{
+       if (!rs_stired || rs->rs_count <= 0) {
+               arc4_stir();
+               rs_stired = 1;
+       }
+}
 
-       return (rs.s[(si + sj) & 0xff]);
+static inline void
+_rs_rekey(u_char *dat, size_t datlen)
+{
+#ifndef KEYSTREAM_ONLY
+       memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf));
+#endif
+       /* fill rs_buf with the keystream */
+       chacha_encrypt_bytes(&rsx->rs_chacha, rsx->rs_buf,
+           rsx->rs_buf, sizeof(rsx->rs_buf));
+       /* mix in optional user provided data */
+       if (dat) {
+               size_t i, m;
+
+               m = MIN(datlen, KEYSZ + IVSZ);
+               for (i = 0; i < m; i++)
+                       rsx->rs_buf[i] ^= dat[i];
+       }
+       /* immediately reinit for backtracking resistance */
+       arc4_addrandom(rsx->rs_buf, KEYSZ + IVSZ);
+       memset(rsx->rs_buf, 0, KEYSZ + IVSZ);
+       rs->rs_have = sizeof(rsx->rs_buf) - KEYSZ - IVSZ;
 }
 
 static inline u_int32_t
 arc4_getword(void)
 {
        u_int32_t val;
+       u_char *keystream;
 
-       val = arc4_getbyte() << 24;
-       val |= arc4_getbyte() << 16;
-       val |= arc4_getbyte() << 8;
-       val |= arc4_getbyte();
+       arc4_check_stir();
+       if (rs->rs_have < sizeof(val))
+               _rs_rekey(NULL, 0);
+       keystream = rsx->rs_buf + sizeof(rsx->rs_buf) - rs->rs_have;
+       memcpy(&val, keystream, sizeof(val));
+       memset(keystream, 0, sizeof(val));
+       rs->rs_have -= sizeof(val);
 
        return (val);
 }
 
-static void
-arc4_check_init(void)
-{
-       if (!rs_initialized) {
-               arc4_init();
-               rs_initialized = 1;
-       }
-}
-
 static inline void
-arc4_check_stir(void)
+_rs_stir_if_needed(size_t len)
 {
-       if (!rs_stired || arc4_count <= 0) {
+       arc4_check_stir();
+       if (rs->rs_count <= len)
                arc4_stir();
-               rs_stired = 1;
-       }
+       if (rs->rs_count <= len)
+               rs->rs_count = 0;
+       else
+               rs->rs_count -= len;
 }
 
 void
@@ -208,24 +217,41 @@ arc4random(void)
        arc4_check_init();
        arc4_check_stir();
        rnd = arc4_getword();
-       arc4_count -= 4;
+       rs->rs_count -= 4;
        THREAD_UNLOCK();
 
        return (rnd);
 }
 
-void
-arc4random_buf(void *_buf, size_t n)
+static inline void
+_rs_random_buf(void *_buf, size_t n)
 {
        u_char *buf = (u_char *)_buf;
+       u_char *keystream;
+       size_t m;
+
+       _rs_stir_if_needed(n);
+       while (n > 0) {
+               if (rs->rs_have > 0) {
+                       m = MIN(n, rs->rs_have);
+                       keystream = rsx->rs_buf + sizeof(rsx->rs_buf)
+                           - rs->rs_have;
+                       memcpy(buf, keystream, m);
+                       memset(keystream, 0, m);
+                       buf += m;
+                       n -= m;
+                       rs->rs_have -= m;
+               }
+               if (rs->rs_have == 0)
+                       _rs_rekey(NULL, 0);
+       }
+}
 
+void
+arc4random_buf(void *_buf, size_t n)
+{
        THREAD_LOCK();
-       arc4_check_init();
-       while (n--) {
-               arc4_check_stir();
-               buf[n] = arc4_getbyte();
-               arc4_count--;
-       }
+       _rs_random_buf(_buf, n);
        THREAD_UNLOCK();
 }
 
diff --git a/src/chacha_private.h b/src/chacha_private.h
new file mode 100644
index 0000000..b720d93
--- /dev/null
+++ b/src/chacha_private.h
@@ -0,0 +1,222 @@
+/*
+chacha-merged.c version 20080118
+D. J. Bernstein
+Public domain.
+*/
+
+/* $OpenBSD$ */
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+typedef struct
+{
+  u32 input[16]; /* could be compressed */
+} chacha_ctx;
+
+#define U8C(v) (v##U)
+#define U32C(v) (v##U)
+
+#define U8V(v) ((u8)(v) & U8C(0xFF))
+#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF))
+
+#define ROTL32(v, n) \
+  (U32V((v) << (n)) | ((v) >> (32 - (n))))
+
+#define U8TO32_LITTLE(p) \
+  (((u32)((p)[0])      ) | \
+   ((u32)((p)[1]) <<  8) | \
+   ((u32)((p)[2]) << 16) | \
+   ((u32)((p)[3]) << 24))
+
+#define U32TO8_LITTLE(p, v) \
+  do { \
+    (p)[0] = U8V((v)      ); \
+    (p)[1] = U8V((v) >>  8); \
+    (p)[2] = U8V((v) >> 16); \
+    (p)[3] = U8V((v) >> 24); \
+  } while (0)
+
+#define ROTATE(v,c) (ROTL32(v,c))
+#define XOR(v,w) ((v) ^ (w))
+#define PLUS(v,w) (U32V((v) + (w)))
+#define PLUSONE(v) (PLUS((v),1))
+
+#define QUARTERROUND(a,b,c,d) \
+  a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \
+  c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \
+  a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \
+  c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
+
+static const char sigma[16] = "expand 32-byte k";
+static const char tau[16] = "expand 16-byte k";
+
+static void
+chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits,u32 ivbits)
+{
+  const char *constants;
+
+  x->input[4] = U8TO32_LITTLE(k + 0);
+  x->input[5] = U8TO32_LITTLE(k + 4);
+  x->input[6] = U8TO32_LITTLE(k + 8);
+  x->input[7] = U8TO32_LITTLE(k + 12);
+  if (kbits == 256) { /* recommended */
+    k += 16;
+    constants = sigma;
+  } else { /* kbits == 128 */
+    constants = tau;
+  }
+  x->input[8] = U8TO32_LITTLE(k + 0);
+  x->input[9] = U8TO32_LITTLE(k + 4);
+  x->input[10] = U8TO32_LITTLE(k + 8);
+  x->input[11] = U8TO32_LITTLE(k + 12);
+  x->input[0] = U8TO32_LITTLE(constants + 0);
+  x->input[1] = U8TO32_LITTLE(constants + 4);
+  x->input[2] = U8TO32_LITTLE(constants + 8);
+  x->input[3] = U8TO32_LITTLE(constants + 12);
+}
+
+static void
+chacha_ivsetup(chacha_ctx *x,const u8 *iv)
+{
+  x->input[12] = 0;
+  x->input[13] = 0;
+  x->input[14] = U8TO32_LITTLE(iv + 0);
+  x->input[15] = U8TO32_LITTLE(iv + 4);
+}
+
+static void
+chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes)
+{
+  u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
+  u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+  u8 *ctarget = NULL;
+  u8 tmp[64];
+  u_int i;
+
+  if (!bytes) return;
+
+  j0 = x->input[0];
+  j1 = x->input[1];
+  j2 = x->input[2];
+  j3 = x->input[3];
+  j4 = x->input[4];
+  j5 = x->input[5];
+  j6 = x->input[6];
+  j7 = x->input[7];
+  j8 = x->input[8];
+  j9 = x->input[9];
+  j10 = x->input[10];
+  j11 = x->input[11];
+  j12 = x->input[12];
+  j13 = x->input[13];
+  j14 = x->input[14];
+  j15 = x->input[15];
+
+  for (;;) {
+    if (bytes < 64) {
+      for (i = 0;i < bytes;++i) tmp[i] = m[i];
+      m = tmp;
+      ctarget = c;
+      c = tmp;
+    }
+    x0 = j0;
+    x1 = j1;
+    x2 = j2;
+    x3 = j3;
+    x4 = j4;
+    x5 = j5;
+    x6 = j6;
+    x7 = j7;
+    x8 = j8;
+    x9 = j9;
+    x10 = j10;
+    x11 = j11;
+    x12 = j12;
+    x13 = j13;
+    x14 = j14;
+    x15 = j15;
+    for (i = 20;i > 0;i -= 2) {
+      QUARTERROUND( x0, x4, x8,x12)
+      QUARTERROUND( x1, x5, x9,x13)
+      QUARTERROUND( x2, x6,x10,x14)
+      QUARTERROUND( x3, x7,x11,x15)
+      QUARTERROUND( x0, x5,x10,x15)
+      QUARTERROUND( x1, x6,x11,x12)
+      QUARTERROUND( x2, x7, x8,x13)
+      QUARTERROUND( x3, x4, x9,x14)
+    }
+    x0 = PLUS(x0,j0);
+    x1 = PLUS(x1,j1);
+    x2 = PLUS(x2,j2);
+    x3 = PLUS(x3,j3);
+    x4 = PLUS(x4,j4);
+    x5 = PLUS(x5,j5);
+    x6 = PLUS(x6,j6);
+    x7 = PLUS(x7,j7);
+    x8 = PLUS(x8,j8);
+    x9 = PLUS(x9,j9);
+    x10 = PLUS(x10,j10);
+    x11 = PLUS(x11,j11);
+    x12 = PLUS(x12,j12);
+    x13 = PLUS(x13,j13);
+    x14 = PLUS(x14,j14);
+    x15 = PLUS(x15,j15);
+
+#ifndef KEYSTREAM_ONLY
+    x0 = XOR(x0,U8TO32_LITTLE(m + 0));
+    x1 = XOR(x1,U8TO32_LITTLE(m + 4));
+    x2 = XOR(x2,U8TO32_LITTLE(m + 8));
+    x3 = XOR(x3,U8TO32_LITTLE(m + 12));
+    x4 = XOR(x4,U8TO32_LITTLE(m + 16));
+    x5 = XOR(x5,U8TO32_LITTLE(m + 20));
+    x6 = XOR(x6,U8TO32_LITTLE(m + 24));
+    x7 = XOR(x7,U8TO32_LITTLE(m + 28));
+    x8 = XOR(x8,U8TO32_LITTLE(m + 32));
+    x9 = XOR(x9,U8TO32_LITTLE(m + 36));
+    x10 = XOR(x10,U8TO32_LITTLE(m + 40));
+    x11 = XOR(x11,U8TO32_LITTLE(m + 44));
+    x12 = XOR(x12,U8TO32_LITTLE(m + 48));
+    x13 = XOR(x13,U8TO32_LITTLE(m + 52));
+    x14 = XOR(x14,U8TO32_LITTLE(m + 56));
+    x15 = XOR(x15,U8TO32_LITTLE(m + 60));
+#endif
+
+    j12 = PLUSONE(j12);
+    if (!j12) {
+      j13 = PLUSONE(j13);
+      /* stopping at 2^70 bytes per nonce is user's responsibility */
+    }
+
+    U32TO8_LITTLE(c + 0,x0);
+    U32TO8_LITTLE(c + 4,x1);
+    U32TO8_LITTLE(c + 8,x2);
+    U32TO8_LITTLE(c + 12,x3);
+    U32TO8_LITTLE(c + 16,x4);
+    U32TO8_LITTLE(c + 20,x5);
+    U32TO8_LITTLE(c + 24,x6);
+    U32TO8_LITTLE(c + 28,x7);
+    U32TO8_LITTLE(c + 32,x8);
+    U32TO8_LITTLE(c + 36,x9);
+    U32TO8_LITTLE(c + 40,x10);
+    U32TO8_LITTLE(c + 44,x11);
+    U32TO8_LITTLE(c + 48,x12);
+    U32TO8_LITTLE(c + 52,x13);
+    U32TO8_LITTLE(c + 56,x14);
+    U32TO8_LITTLE(c + 60,x15);
+
+    if (bytes <= 64) {
+      if (bytes < 64) {
+        for (i = 0;i < bytes;++i) ctarget[i] = c[i];
+      }
+      x->input[12] = j12;
+      x->input[13] = j13;
+      return;
+    }
+    bytes -= 64;
+    c += 64;
+#ifndef KEYSTREAM_ONLY
+    m += 64;
+#endif
+  }
+}
-- 
2.1.4


-- System Information:
Debian Release: 8.0
  APT prefers unstable
  APT policy: (500, 'unstable')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 3.16.0-4-amd64 (SMP w/2 CPU cores)
Locale: LANG=en_US.utf8, LC_CTYPE=en_US.utf8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/mksh-static
Init: systemd (via /run/systemd/system)


-- 
To UNSUBSCRIBE, email to debian-bugs-dist-requ...@lists.debian.org
with a subject of "unsubscribe". Trouble? Contact listmas...@lists.debian.org

Reply via email to