Browse Source

Merge pull request #363 from libtom/improve/fortuna

Improve Fortuna PRNG
Steffen Jaeckel 7 years ago
parent
commit
8236ade38e
7 changed files with 189 additions and 36 deletions
  1. 4 4
      .travis.yml
  2. 20 0
      doc/crypt.tex
  3. 21 0
      src/headers/tomcrypt_custom.h
  4. 6 4
      src/headers/tomcrypt_prng.h
  5. 7 1
      src/misc/crypt/crypt.c
  6. 106 26
      src/prngs/fortuna.c
  7. 25 1
      tests/prng_test.c

+ 4 - 4
.travis.yml

@@ -99,16 +99,16 @@ env:
     BUILDOPTIONS="-DLTC_NO_ECC_TIMING_RESISTANT -DLTC_NO_RSA_BLINDING"
   - |
     BUILDSCRIPT=".ci/run.sh"
-    BUILDNAME="CLEANSTACK+NOTABLES+SMALL+NO_ASM+NO_TIMING_RESISTANCE"
-    BUILDOPTIONS="-DLTC_CLEAN_STACK -DLTC_NO_TABLES -DLTC_SMALL_CODE -DLTC_NO_ECC_TIMING_RESISTANT -DLTC_NO_RSA_BLINDING"
+    BUILDNAME="CLEANSTACK+NOTABLES+SMALL+NO_ASM+NO_TIMING_RESISTANCE+LTC_FORTUNA_RESEED_RATELIMIT_STATIC"
+    BUILDOPTIONS="-DLTC_CLEAN_STACK -DLTC_NO_TABLES -DLTC_SMALL_CODE -DLTC_NO_ECC_TIMING_RESISTANT -DLTC_NO_RSA_BLINDING -DLTC_FORTUNA_RESEED_RATELIMIT_STATIC"
   - |
     BUILDSCRIPT=".ci/run.sh"
     BUILDNAME="PTHREAD"
     BUILDOPTIONS="-DLTC_PTHREAD"
   - |
     BUILDSCRIPT=".ci/run.sh"
-    BUILDNAME="CLEANSTACK+NOTABLES+SMALL+NO_ASM+NO_TIMING_RESISTANCE+PTHREAD"
-    BUILDOPTIONS="-DLTC_CLEAN_STACK -DLTC_NO_TABLES -DLTC_SMALL_CODE -DLTC_NO_ECC_TIMING_RESISTANT -DLTC_NO_RSA_BLINDING -DLTC_PTHREAD"
+    BUILDNAME="CLEANSTACK+NOTABLES+SMALL+NO_ASM+NO_TIMING_RESISTANCE+LTC_FORTUNA_RESEED_RATELIMIT_STATIC+PTHREAD"
+    BUILDOPTIONS="-DLTC_CLEAN_STACK -DLTC_NO_TABLES -DLTC_SMALL_CODE -DLTC_NO_ECC_TIMING_RESISTANT -DLTC_NO_RSA_BLINDING -DLTC_FORTUNA_RESEED_RATELIMIT_STATIC -DLTC_PTHREAD"
 
 after_failure:
   - cat test_std.txt

+ 20 - 0
doc/crypt.tex

@@ -3831,6 +3831,26 @@ For detailed information on how the algorithm works and what you have to do to m
 get a copy of the book\footnote{Niels Ferguson and Bruce Schneier, Practical Cryptography. ISBN 0-471-22357-3.} or
 read the paper online\footnote{\url{https://www.schneier.com/academic/paperfiles/fortuna.pdf} [Accessed on 7th Dec. 2017]}.
 
+Fortuna provides additional API functions to be able to implement some of the important steps proposed in the original algorithm specification.
+
+\index{fortuna\_add\_random\_event()}
+\begin{verbatim}
+int fortuna_add_random_event(      unsigned long  source, unsigned long pool,
+                             const unsigned char *in,     unsigned long inlen,
+                                      prng_state *prng);
+\end{verbatim}
+
+\textit{fortuna\_add\_random\_event()} can be used as a powerful alternative to the more general \textit{add\_entropy()}.
+
+\index{fortuna\_update\_seed()}
+\begin{verbatim}
+int fortuna_update_seed(const unsigned char *in, unsigned long inlen,
+                                 prng_state *prng);
+\end{verbatim}
+
+\textit{fortuna\_update\_seed()} can be used to implement the \textit{UpdateSeedFile} function from the original specification.
+
+
 \subsubsection{RC4}
 
 RC4 is an old stream cipher that can also double duty as a PRNG in a pinch.  You key RC4 by

+ 21 - 0
src/headers/tomcrypt_custom.h

@@ -358,11 +358,32 @@
 
 #ifdef LTC_FORTUNA
 
+#if !defined(LTC_FORTUNA_RESEED_RATELIMIT_STATIC) && \
+      ((defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || defined(_WIN32))
+
+/* time-based rate limit of the reseeding */
+#define LTC_FORTUNA_RESEED_RATELIMIT_TIMED
+
+#else
+
 #ifndef LTC_FORTUNA_WD
 /* reseed every N calls to the read function */
 #define LTC_FORTUNA_WD    10
 #endif
 
+#ifdef LTC_FORTUNA_RESEED_RATELIMIT_TIMED
+/* make sure only one of
+ *   LTC_FORTUNA_RESEED_RATELIMIT_STATIC
+ * and
+ *   LTC_FORTUNA_RESEED_RATELIMIT_TIMED
+ * is defined.
+ */
+#undef LTC_FORTUNA_RESEED_RATELIMIT_TIMED
+#warning "undef'ed LTC_FORTUNA_RESEED_RATELIMIT_TIMED, looks like your architecture doesn't support it"
+#endif
+
+#endif
+
 #ifndef LTC_FORTUNA_POOLS
 /* number of pools (4..32) can save a bit of ram by lowering the count */
 #define LTC_FORTUNA_POOLS 32

+ 6 - 4
src/headers/tomcrypt_prng.h

@@ -43,7 +43,7 @@ struct fortuna_prng {
                   pool0_len,  /* length of 0'th pool */
                   wd;
 
-    ulong64       reset_cnt;  /* number of times we have reset */
+    ulong64       reset_cnt;  /* number of times we have reseeded */
 };
 #endif
 
@@ -148,12 +148,14 @@ extern const struct ltc_prng_descriptor yarrow_desc;
 #ifdef LTC_FORTUNA
 int fortuna_start(prng_state *prng);
 int fortuna_add_entropy(const unsigned char *in, unsigned long inlen, prng_state *prng);
+int fortuna_add_random_event(unsigned long source, unsigned long pool, const unsigned char *in, unsigned long inlen, prng_state *prng);
 int fortuna_ready(prng_state *prng);
 unsigned long fortuna_read(unsigned char *out, unsigned long outlen, prng_state *prng);
 int fortuna_done(prng_state *prng);
-int  fortuna_export(unsigned char *out, unsigned long *outlen, prng_state *prng);
-int  fortuna_import(const unsigned char *in, unsigned long inlen, prng_state *prng);
-int  fortuna_test(void);
+int fortuna_export(unsigned char *out, unsigned long *outlen, prng_state *prng);
+int fortuna_import(const unsigned char *in, unsigned long inlen, prng_state *prng);
+int fortuna_update_seed(const unsigned char *in, unsigned long inlen, prng_state *prng);
+int fortuna_test(void);
 extern const struct ltc_prng_descriptor fortuna_desc;
 #endif
 

+ 7 - 1
src/misc/crypt/crypt.c

@@ -313,7 +313,13 @@ const char *crypt_build_settings =
     "   ChaCha20\n"
 #endif
 #if defined(LTC_FORTUNA)
-    "   Fortuna (" NAME_VALUE(LTC_FORTUNA_POOLS) ", " NAME_VALUE(LTC_FORTUNA_WD) ")\n"
+    "   Fortuna (" NAME_VALUE(LTC_FORTUNA_POOLS) ", "
+#if defined(LTC_FORTUNA_RESEED_RATELIMIT_TIMED)
+    "LTC_FORTUNA_RESEED_RATELIMIT_TIMED, "
+#else
+    "LTC_FORTUNA_RESEED_RATELIMIT_STATIC, " NAME_VALUE(LTC_FORTUNA_WD)
+#endif
+    ")\n"
 #endif
 #if defined(LTC_SOBER128)
     "   SOBER128\n"

+ 106 - 26
src/prngs/fortuna.c

@@ -61,6 +61,29 @@ static void _fortuna_update_iv(prng_state *prng)
    }
 }
 
+#ifdef LTC_FORTUNA_RESEED_RATELIMIT_TIMED
+/* get the current time in 100ms steps */
+static ulong64 _fortuna_current_time(void)
+{
+   ulong64 cur_time;
+#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L
+   struct timespec ts;
+   clock_gettime(CLOCK_MONOTONIC, &ts);
+   cur_time = (ulong64)(ts.tv_sec) * 1000000 + (ulong64)(ts.tv_nsec) / 1000; /* get microseconds */
+#elif defined(_WIN32)
+   FILETIME CurrentTime;
+   ULARGE_INTEGER ul;
+   GetSystemTimeAsFileTime(&CurrentTime);
+   ul.LowPart  = CurrentTime.dwLowDateTime;
+   ul.HighPart = CurrentTime.dwHighDateTime;
+   cur_time = ul.QuadPart;
+   cur_time -= CONST64(116444736000000000); /* subtract epoch in microseconds */
+   cur_time /= 1000; /* nanoseconds -> microseconds */
+#endif
+   return cur_time / 100;
+}
+#endif
+
 /* reseed the PRNG */
 static int _fortuna_reseed(prng_state *prng)
 {
@@ -69,6 +92,14 @@ static int _fortuna_reseed(prng_state *prng)
    ulong64       reset_cnt;
    int           err, x;
 
+#ifdef LTC_FORTUNA_RESEED_RATELIMIT_TIMED
+   unsigned long now = _fortuna_current_time();
+   if (now == prng->fortuna.wd)
+      return CRYPT_OK;
+#else
+   if (++prng->fortuna.wd < LTC_FORTUNA_WD)
+      return CRYPT_OK;
+#endif
 
    /* new K == LTC_SHA256(K || s) where s == LTC_SHA256(P0) || LTC_SHA256(P1) ... */
    sha256_init(&md);
@@ -112,7 +143,11 @@ static int _fortuna_reseed(prng_state *prng)
 
    /* reset/update internals */
    prng->fortuna.pool0_len = 0;
+#ifdef LTC_FORTUNA_RESEED_RATELIMIT_TIMED
+   prng->fortuna.wd        = now;
+#else
    prng->fortuna.wd        = 0;
+#endif
    prng->fortuna.reset_cnt = reset_cnt;
 
 
@@ -132,7 +167,7 @@ static int _fortuna_reseed(prng_state *prng)
   @param prng     The PRNG to import
   @return CRYPT_OK if successful
 */
-static int _fortuna_update_seed(const unsigned char *in, unsigned long inlen, prng_state *prng)
+int fortuna_update_seed(const unsigned char *in, unsigned long inlen, prng_state *prng)
 {
    int           err;
    unsigned char tmp[MAXBLOCKSIZE];
@@ -204,48 +239,86 @@ int fortuna_start(prng_state *prng)
    return CRYPT_OK;
 }
 
+static int _fortuna_add(unsigned long source, unsigned long pool, const unsigned char *in, unsigned long inlen, prng_state *prng)
+{
+   unsigned char tmp[2];
+   int err;
+
+   /* ensure inlen <= 32 */
+   if (inlen > 32) {
+      inlen = 32;
+   }
+
+   /* add s || length(in) || in to pool[pool_idx] */
+   tmp[0] = (unsigned char)source;
+   tmp[1] = (unsigned char)inlen;
+
+   if ((err = sha256_process(&prng->fortuna.pool[pool], tmp, 2)) != CRYPT_OK) {
+      return err;
+   }
+   if ((err = sha256_process(&prng->fortuna.pool[pool], in, inlen)) != CRYPT_OK) {
+      return err;
+   }
+   if (pool == 0) {
+      prng->fortuna.pool0_len += inlen;
+   }
+   return CRYPT_OK; /* success */
+}
+
 /**
-  Add entropy to the PRNG state
+  Add random event to the PRNG state as proposed by the original paper.
+  @param source   The source this random event comes from (0 .. 255)
+  @param pool     The pool where to add the data to (0 .. LTC_FORTUNA_POOLS)
   @param in       The data to add
   @param inlen    Length of the data to add
   @param prng     PRNG state to update
   @return CRYPT_OK if successful
 */
-int fortuna_add_entropy(const unsigned char *in, unsigned long inlen, prng_state *prng)
+int fortuna_add_random_event(unsigned long source, unsigned long pool, const unsigned char *in, unsigned long inlen, prng_state *prng)
 {
-   unsigned char tmp[2];
    int           err;
 
    LTC_ARGCHK(prng != NULL);
    LTC_ARGCHK(in != NULL);
    LTC_ARGCHK(inlen > 0);
+   LTC_ARGCHK(source <= 255);
+   LTC_ARGCHK(pool < LTC_FORTUNA_POOLS);
 
-   /* ensure inlen <= 32 */
-   if (inlen > 32) {
-      inlen = 32;
-   }
+   LTC_MUTEX_LOCK(&prng->lock);
 
-   /* add s || length(in) || in to pool[pool_idx] */
-   tmp[0] = 0;
-   tmp[1] = (unsigned char)inlen;
+   err = _fortuna_add(source, pool, in, inlen, prng);
+
+   LTC_MUTEX_UNLOCK(&prng->lock);
+
+   return err;
+}
+
+/**
+  Add entropy to the PRNG state
+  @param in       The data to add
+  @param inlen    Length of the data to add
+  @param prng     PRNG state to update
+  @return CRYPT_OK if successful
+*/
+int fortuna_add_entropy(const unsigned char *in, unsigned long inlen, prng_state *prng)
+{
+   int err;
+
+   LTC_ARGCHK(prng != NULL);
+   LTC_ARGCHK(in != NULL);
+   LTC_ARGCHK(inlen > 0);
 
    LTC_MUTEX_LOCK(&prng->lock);
-   if ((err = sha256_process(&prng->fortuna.pool[prng->fortuna.pool_idx], tmp, 2)) != CRYPT_OK) {
-      goto LBL_UNLOCK;
-   }
-   if ((err = sha256_process(&prng->fortuna.pool[prng->fortuna.pool_idx], in, inlen)) != CRYPT_OK) {
-      goto LBL_UNLOCK;
-   }
-   if (prng->fortuna.pool_idx == 0) {
-      prng->fortuna.pool0_len += inlen;
-   }
-   if (++(prng->fortuna.pool_idx) == LTC_FORTUNA_POOLS) {
-      prng->fortuna.pool_idx = 0;
+
+   err = _fortuna_add(0, prng->fortuna.pool_idx, in, inlen, prng);
+
+   if (err == CRYPT_OK) {
+      ++(prng->fortuna.pool_idx);
+      prng->fortuna.pool_idx %= LTC_FORTUNA_POOLS;
    }
-   err = CRYPT_OK; /* success */
 
-LBL_UNLOCK:
    LTC_MUTEX_UNLOCK(&prng->lock);
+
    return err;
 }
 
@@ -260,6 +333,13 @@ int fortuna_ready(prng_state *prng)
    LTC_ARGCHK(prng != NULL);
 
    LTC_MUTEX_LOCK(&prng->lock);
+   /* make sure the reseed doesn't fail because
+    * of the chosen rate limit */
+#ifdef LTC_FORTUNA_RESEED_RATELIMIT_TIMED
+   prng->fortuna.wd = _fortuna_current_time() - 1;
+#else
+   prng->fortuna.wd = LTC_FORTUNA_WD;
+#endif
    err = _fortuna_reseed(prng);
    prng->ready = (err == CRYPT_OK) ? 1 : 0;
 
@@ -288,7 +368,7 @@ unsigned long fortuna_read(unsigned char *out, unsigned long outlen, prng_state
    }
 
    /* do we have to reseed? */
-   if (++prng->fortuna.wd == LTC_FORTUNA_WD || prng->fortuna.pool0_len >= 64) {
+   if (prng->fortuna.pool0_len >= 64) {
       if (_fortuna_reseed(prng) != CRYPT_OK) {
          goto LBL_UNLOCK;
       }
@@ -401,7 +481,7 @@ int fortuna_import(const unsigned char *in, unsigned long inlen, prng_state *prn
       return err;
    }
 
-   if ((err = _fortuna_update_seed(in, inlen, prng)) != CRYPT_OK) {
+   if ((err = fortuna_update_seed(in, inlen, prng)) != CRYPT_OK) {
       return err;
    }
 

+ 25 - 1
tests/prng_test.c

@@ -74,7 +74,7 @@ int prng_test(void)
       DOX(prng_descriptor[x].pexport(buf, &n, &nprng), prng_descriptor[x].name);
       prng_descriptor[x].done(&nprng);
       DOX(prng_descriptor[x].pimport(buf, n, &nprng), prng_descriptor[x].name);
-      DOX(prng_descriptor[x].pimport(buf, 4096, &nprng), prng_descriptor[x].name); /* try to import larger data */
+      DOX(prng_descriptor[x].pimport(buf, sizeof(buf), &nprng), prng_descriptor[x].name); /* try to import larger data */
       DOX(prng_descriptor[x].ready(&nprng), prng_descriptor[x].name);
       if (prng_descriptor[x].read(buf, 100, &nprng) != 100) {
          fprintf(stderr, "Error reading from imported PRNG (%s)!\n", prng_descriptor[x].name);
@@ -87,6 +87,30 @@ int prng_test(void)
       fprintf(stderr, "rng_make_prng(-1,..) with 'yarrow' failed: %s\n", error_to_string(err));
    }
    DO(yarrow_done(&nprng));
+
+#ifdef LTC_FORTUNA
+   DO(fortuna_start(&nprng));
+   DO(fortuna_add_entropy(buf, 32, &nprng));
+   DO(fortuna_ready(&nprng));
+   if (fortuna_read(buf + 32, 32, &nprng) != 32) {
+      fprintf(stderr, "Error reading from Fortuna after fortuna_add_entropy()!\n");
+      return CRYPT_ERROR;
+   }
+   DO(fortuna_done(&nprng));
+
+   DO(fortuna_start(&nprng));
+   DO(fortuna_add_random_event(0, 0, buf, 32, &nprng));
+   DO(fortuna_ready(&nprng));
+   if (fortuna_read(buf + 64, 32, &nprng) != 32) {
+      fprintf(stderr, "Error reading from Fortuna after fortuna_add_random_event()!\n");
+      return CRYPT_ERROR;
+   }
+   DO(fortuna_done(&nprng));
+
+   if (compare_testvector(buf + 64, 32, buf + 32, 32, "fortuna_add_entropy() vs. fortuna_add_random_event()", 0) != 0) {
+      err = CRYPT_FAIL_TESTVECTOR;
+   }
+#endif
    return err;
 }