Restructure handling of cipher-based generators. Add counter-mode
authormdw <mdw>
Sat, 17 Jun 2000 12:08:28 +0000 (12:08 +0000)
committermdw <mdw>
Sat, 17 Jun 2000 12:08:28 +0000 (12:08 +0000)
ciphers and MGF-1 hash functions.  Add FIPS 140-1 and Maurer's tests.

rspit.c

diff --git a/rspit.c b/rspit.c
index ec55707..e10d05f 100644 (file)
--- a/rspit.c
+++ b/rspit.c
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: rspit.c,v 1.3 2000/02/12 18:21:03 mdw Exp $
+ * $Id: rspit.c,v 1.4 2000/06/17 12:08:28 mdw Exp $
  *
  * Spit out random numbers
  *
 /*----- Revision history --------------------------------------------------* 
  *
  * $Log: rspit.c,v $
+ * Revision 1.4  2000/06/17 12:08:28  mdw
+ * Restructure handling of cipher-based generators.  Add counter-mode
+ * ciphers and MGF-1 hash functions.  Add FIPS 140-1 and Maurer's tests.
+ *
  * Revision 1.3  2000/02/12 18:21:03  mdw
  * Overhaul of key management (again).
  *
@@ -45,7 +49,9 @@
 
 #include "config.h"
 
+#include <assert.h>
 #include <errno.h>
+#include <math.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -63,7 +69,9 @@
 #include <mLib/report.h>
 #include <mLib/sub.h>
 
+#include "fipstest.h"
 #include "grand.h"
+#include "maurer.h"
 #include "key.h"
 
 #include "lcrand.h"
 #include "mprand.h"
 
 #include "rc4.h"
+#include "seal.h"
 
 #include "des-ofb.h"
 #include "des3-ofb.h"
+#include "rc2-ofb.h"
 #include "rc5-ofb.h"
 #include "blowfish-ofb.h"
+#include "twofish-ofb.h"
 #include "idea-ofb.h"
+#include "cast128-ofb.h"
+#include "cast256-ofb.h"
+#include "rijndael-ofb.h"
+#include "serpent-ofb.h"
+
+#include "des-counter.h"
+#include "des3-counter.h"
+#include "rc2-counter.h"
+#include "rc5-counter.h"
+#include "blowfish-counter.h"
+#include "twofish-counter.h"
+#include "idea-counter.h"
+#include "cast128-counter.h"
+#include "cast256-counter.h"
+#include "rijndael-counter.h"
+#include "serpent-counter.h"
+
+#include "md4-mgf.h"
+#include "md5-mgf.h"
+#include "sha-mgf.h"
+#include "rmd160-mgf.h"
 
 #include "rmd160.h"
 
@@ -95,6 +127,56 @@ typedef struct gen {
 
 static gen generators[];
 
+#define CIPHERS                                                                \
+  E(DES, des)                                                          \
+  E(DES3, des3)                                                                \
+  E(RC2, rc2)                                                          \
+  E(RC5, rc5)                                                          \
+  E(BLOWFISH, blowfish)                                                        \
+  E(TWOFISH, twofish)                                                  \
+  E(IDEA, idea)                                                                \
+  E(CAST128, cast128)                                                  \
+  E(CAST256, cast256)                                                  \
+  E(RIJNDAEL, rijndael)                                                        \
+  E(SERPENT, serpent)
+
+#define HASHES                                                         \
+  E(MD4, md4)                                                          \
+  E(MD5, md5)                                                          \
+  E(SHA, sha)                                                          \
+  E(RMD160, rmd160)
+
+#define E(PRE, pre) CIPHER_##PRE,
+enum { CIPHERS CIPHER__bogus };
+#undef E
+
+#define E(PRE, pre) HASH_##PRE,
+enum { HASHES HASH__bogus };
+#undef E
+
+static struct {
+  const octet *keysz;
+  size_t blksz;
+  grand *(*ofb)(const void */*k*/, size_t /*sz*/);
+  grand *(*counter)(const void */*k*/, size_t /*sz*/);
+} ciphertab[] = {
+#define E(PRE, pre)                                                    \
+  { pre##_keysz, PRE##_BLKSZ, pre##_ofbrand, pre##_counterrand },
+  CIPHERS
+#undef E
+};
+
+static struct {
+  const gchash *h;
+  const octet *keysz;
+  grand *(*mgf)(const void */*k*/, size_t /*sz*/);
+} hashtab[] = {
+#define E(PRE, pre)                                                    \
+  { &pre, pre##_mgfkeysz, pre##_mgfrand },
+  HASHES
+#undef E
+};
+
 /*----- Miscellaneous static data -----------------------------------------*/
 
 static FILE *outfp = stdout;
@@ -107,7 +189,9 @@ static unsigned flags = 0;
 
 enum {
   f_progress = 1,
-  f_file = 2
+  f_file = 2,
+  f_fips = 4,
+  f_maurer = 8
 };
 
 /*----- Help options ------------------------------------------------------*/
@@ -141,6 +225,8 @@ common core set:\n\
 \n\
 -l, --list             Show a list of the supported generators, with\n\
                        their options.\n\
+-f, --fipstest         Run the FIPS 140-1 randomness test.\n\
+-m, --maurer           Run Maurer's universal statistical test.\n\
 -o, --output FILE      Write output to FILE, not stdout.\n\
 -z, --size SIZE                Emit SIZE bytes, not an unlimited number.\n\
 -p, --progress         Show a little progress meter (on stderr).\n\
@@ -163,6 +249,8 @@ static struct option opts[] = {
   /* --- Other useful things --- */
 
   { "list",    0,              0,      'l' },
+  { "fipstest",        0,              0,      'f' },
+  { "maurer",  0,              0,      'm' },
   { "output",  OPTF_ARGREQ,    0,      'o' },
   { "size",    OPTF_ARGREQ,    0,      'z' },
   { "progress",        0,              0,      'p' },
@@ -172,7 +260,7 @@ static struct option opts[] = {
   { 0,         0,              0,      0 }
 };
 
-static const char *sopts = "hvu lo:z:p";
+static const char *sopts = "hvu lfmo:z:p";
 
 #ifndef OPTION_V
    DA_DECL(option_v, struct option);
@@ -232,6 +320,12 @@ static int opt(void)
          printf("  %s %s\n", g->name, g->help);
        exit(0);
       } break;
+      case 'f':
+       flags |= f_fips;
+       break;
+      case 'm':
+       flags |= f_maurer;
+       break;
       case 'o':
        if (flags & f_file)
          die(EXIT_FAILURE, "already set an output file");
@@ -241,7 +335,7 @@ static int opt(void)
          outfp = fopen(optarg, "w");
          if (!outfp) {
            die(EXIT_FAILURE, "couldn't open output file `%s': %s",
-               strerror(errno));
+               optarg, strerror(errno));
          }
        }
        flags |= f_file;
@@ -304,6 +398,46 @@ static void unhex(const char *p, char **end, dstr *d)
   *end = (char *)p;
 }
 
+/* --- Generate a key --- */
+
+static void textkey(dstr *d, const char *p, const octet *ksz)
+{
+  size_t sz = strlen(p);
+
+  if (!sz)
+    die(EXIT_FAILURE, "zero-length key string");
+  if (keysz(sz, ksz) != sz)
+    DPUTM(d, p, sz);
+  else {
+    rmd160_mgfctx g;
+    rmd160_mgfinit(&g, p, sz);
+    sz = keysz(0, ksz);
+    dstr_ensure(d, sz);
+    rmd160_mgfencrypt(&g, 0, d->buf, sz);
+    d->len += sz;
+  }
+  assert(((void)"I can't seem to choose a good key size",
+         keysz(d->len, ksz) == d->len));
+}
+
+static void hexkey(dstr *d, const char *p, const octet *ksz)
+{
+  char *q;
+  unhex(optarg, &q, d);
+  if (*q)
+    die(EXIT_FAILURE, "bad hex key `%s'", p);
+  if (keysz(d->len, ksz) != d->len)
+    die(EXIT_FAILURE, "bad key length");
+}
+
+static void randkey(dstr *d, const octet *ksz)
+{
+  size_t sz = keysz(0, ksz);
+  dstr_ensure(d, sz);
+  rand_get(RAND_GLOBAL, d->buf, sz);
+  d->len += sz;
+}
+
 /*----- Generators --------------------------------------------------------*/
 
 /* --- Blum-Blum-Shub strong generator --- */
@@ -322,14 +456,14 @@ static grand *gen_bbs(unsigned i)
    *     @5374320073594018817245784145742769603334292182227671519041431067@
    *     @61344781426317516045890159@
    *
-   * Both %$p$% and %$q$% are prime; %$(p - 1)/2%$ and %$(q - 1)/2$% have no
+   * Both %$p$% and %$q$% are prime; %$(p - 1)/2$% and %$(q - 1)/2$% have no
    * common factors.  They were found using this program, with random
    * starting points.
    *
    * I hope that, by publishing these factors, I'll dissuade people from
-   * actually using this modulus in attempt to actually attain real
-   * security.  The program is quite quick at finding Blum numbers, so
-   * there's no excuse for not generating your own.
+   * actually using this modulus in an attempt to attain real security.  The
+   * program is quite quick at finding Blum numbers, so there's no excuse for
+   * not generating your own.
    */
 
   const char *mt =
@@ -347,7 +481,7 @@ static grand *gen_bbs(unsigned i)
   /* --- Parse options --- */
 
   static struct option opts[] = {
-    { "modulus",       OPTF_ARGREQ,    0,      'm' },
+    { "modulus",       OPTF_ARGREQ,    0,      'M' },
     { "generate",      0,              0,      'g' },
     { "seed",          OPTF_ARGREQ,    0,      's' },
     { "bits",          OPTF_ARGREQ,    0,      'b' },
@@ -358,14 +492,14 @@ static grand *gen_bbs(unsigned i)
     { 0,               0,              0,      0 }
   };
 
-  addopts("m:gs:b:Sk:i:t:", opts);
+  addopts("M:gs:b:Sk:i:t:", opts);
 
   for (;;) {
     int o = opt();
     if (o < 0)
       break;
     switch (o) {
-      case 'm':
+      case 'M':
        mt = optarg;
        break;
       case 'g':
@@ -496,38 +630,31 @@ static grand *gen_rand(unsigned i)
     { "key",           OPTF_ARGREQ,    0,      'k' },
     { "text",          OPTF_ARGREQ,    0,      't' },
     { "hex",           OPTF_ARGREQ,    0,      'H' },
-    { "noise",         0,              0,      'n' },
     { 0,               0,              0,      0 }
   };
 
   addopts("k:t:H:n", opts);
 
+  r->ops->misc(r, RAND_NOISESRC, &noise_source);
+  r->ops->misc(r, RAND_SEED, 160);
+
   for (;;) {
     int o = opt();
     if (o < 0)
       break;
     switch (o) {
-      case 'k': {
-       rmd160_ctx c;
-       octet hash[RMD160_HASHSZ];
-       rmd160_init(&c);
-       rmd160_hash(&c, optarg, strlen(optarg));
-       rmd160_done(&c, hash);
-       r->ops->misc(r, RAND_KEY, hash, sizeof(hash));
-      } break;
+      case 'k':
+       DRESET(&d);
+       textkey(&d, optarg, rmd160_mackeysz);
+       r->ops->misc(r, RAND_KEY, d.buf, d.len);
+       break;
       case 't':
        r->ops->misc(r, GRAND_SEEDBLOCK, optarg, strlen(optarg));
        break;
-      case 'H': {
-       char *p;
+      case 'H':
        DRESET(&d);
-       unhex(optarg, &p, &d);
-       if (*p)
-         die(EXIT_FAILURE, "bad hex key `%s'", optarg);
+       hexkey(&d, optarg, rmd160_mackeysz);
        r->ops->misc(r, GRAND_SEEDBLOCK, d.buf, d.len);
-      } break;
-      case 'n':
-       r->ops->misc(r, RAND_NOISESRC, &noise_source);
        break;
     }
   }
@@ -556,61 +683,75 @@ static grand *gen_rc4(unsigned i)
     if (o < 0)
       break;
     switch (o) {
-      case 'k': {
-       rmd160_ctx c;
-       dstr_ensure(&d, RMD160_HASHSZ);
-       rmd160_init(&c);
-       rmd160_hash(&c, optarg, strlen(optarg));
-       rmd160_done(&c, d.buf);
-       d.len += RMD160_HASHSZ;
-      } break;
-      case 'H': {
-       char *p;
-       unhex(optarg, &p, &d);
-       if (*p)
-         die(EXIT_FAILURE, "bad hex key `%s'", optarg);
-      } break;
+      case 'k':
+       DRESET(&d);
+       textkey(&d, optarg, rc4_keysz);
+       break;
+      case 'H':
+       DRESET(&d);
+       hexkey(&d, optarg, rc4_keysz);
+       break;
       default:
        return (0);
     }
   }
 
-  if (!d.len) {
-    dstr_ensure(&d, 16);
-    d.len = 16;
-    rand_getgood(RAND_GLOBAL, d.buf, d.len);
-  }
+  if (!d.len)
+    randkey(&d, rc4_keysz);
   r = rc4_rand(d.buf, d.len);
   dstr_destroy(&d);
   return (r);
 }
 
-/* --- Output feedback generators --- */
+/* --- SEAL output --- */
 
-#define OFBTAB                                                         \
-  E(OFB_DES,     DES_KEYSZ, DES_BLKSZ, des_ofbrand),                   \
-  E(OFB_DES3,    DES3_KEYSZ, DES3_BLKSZ, des3_ofbrand),                \
-  E(OFB_RC5,     RC5_KEYSZ, RC5_BLKSZ, rc5_ofbrand),                   \
-  E(OFB_BLOWFISH, BLOWFISH_KEYSZ, BLOWFISH_BLKSZ, blowfish_ofbrand),   \
-  E(OFB_IDEA,    IDEA_KEYSZ, IDEA_BLKSZ, idea_ofbrand)
+static grand *gen_seal(unsigned i)
+{
+  grand *r;
+  dstr d = DSTR_INIT;
+  uint32 n = 0;
 
-static struct {
-  size_t keysz;
-  size_t blksz;
-  grand *(*rand)(const void */*k*/, size_t /*sz*/);
-} ofbtab[] = {
-#define E(c, x, y, z) { x, y, z }
-  OFBTAB
-#undef E
-};
+  static struct option opts[] = {
+    { "key",           OPTF_ARGREQ,    0,      'k' },
+    { "hex",           OPTF_ARGREQ,    0,      'H' },
+    { "sequence",      OPTF_ARGREQ,    0,      'n' },
+    { 0,               0,              0,      0 }
+  };
 
-enum {
-#define E(c, x, y, z) c
-  OFBTAB
-#undef E
-};
+  addopts("k:H:n:", opts);
 
-#undef OFBTAB
+  for (;;) {
+    int o = opt();
+    if (o < 0)
+      break;
+    switch (o) {
+      case 'k':
+       DRESET(&d);
+       textkey(&d, optarg, seal_keysz);
+       break;
+      case 'H':
+       DRESET(&d);
+       hexkey(&d, optarg, seal_keysz);
+       break;
+      case 'n': {
+       char *p;
+       n = strtoul(optarg, &p, 0);
+       if (*p)
+         die(EXIT_FAILURE, "bad number `%s'", optarg);
+      } break;
+      default:
+       return (0);
+    }
+  }
+
+  if (!d.len)
+    randkey(&d, seal_keysz);
+  r = seal_rand(d.buf, d.len, n);
+  dstr_destroy(&d);
+  return (r);
+}
+
+/* --- Output feedback generators --- */
 
 static grand *gen_ofb(unsigned i)
 {
@@ -632,20 +773,14 @@ static grand *gen_ofb(unsigned i)
     if (o < 0)
       break;
     switch (o) {
-      case 'k': {
-       rmd160_ctx c;
-       dstr_ensure(&d, RMD160_HASHSZ);
-       rmd160_init(&c);
-       rmd160_hash(&c, optarg, strlen(optarg));
-       rmd160_done(&c, d.buf);
-       d.len += RMD160_HASHSZ;
-      } break;
-      case 'H': {
-       char *p;
-       unhex(optarg, &p, &d);
-       if (*p)
-         die(EXIT_FAILURE, "bad hex key `%s'", optarg);
-      } break;
+      case 'k':
+       DRESET(&d);
+       textkey(&d, optarg, ciphertab[i].keysz);
+       break;
+      case 'H':
+       DRESET(&d);
+       hexkey(&d, optarg, ciphertab[i].keysz);
+       break;
       case 'i': {
        char *p;
        unhex(optarg, &p, &iv);
@@ -657,24 +792,71 @@ static grand *gen_ofb(unsigned i)
     }
   }
 
-  if (!d.len) {
-    size_t n = ofbtab[i].keysz;
-    if (!n)
-      n = 16;
-    dstr_ensure(&d, n);
-    d.len = n;
-    rand_getgood(RAND_GLOBAL, d.buf, d.len);
+  if (!d.len)
+    randkey(&d, ciphertab[i].keysz);
+  r = ciphertab[i].ofb(d.buf, d.len);
+  if (iv.len) {
+    if (iv.len != ciphertab[i].blksz) {
+      die(EXIT_FAILURE, "bad IV length %lu (must be %lu)",
+         (unsigned long)iv.len, (unsigned long)ciphertab[i].blksz);
+    }
+    r->ops->misc(r, GRAND_SEEDBLOCK, iv.buf);
   }
 
-  while (d.len < ofbtab[i].keysz)
-    DPUTD(&d, &d);
-  if (ofbtab[i].keysz && d.len > ofbtab[i].keysz)
-    d.len = ofbtab[i].keysz;
+  dstr_destroy(&d);
+  dstr_destroy(&iv);
+  return (r);
+}
+
+/* --- Counter generators --- */
+
+static grand *gen_counter(unsigned i)
+{
+  grand *r;
+  dstr d = DSTR_INIT;
+  dstr iv = DSTR_INIT;
 
-  r = ofbtab[i].rand(d.buf, d.len);
+  static struct option opts[] = {
+    { "key",           OPTF_ARGREQ,    0,      'k' },
+    { "hex",           OPTF_ARGREQ,    0,      'H' },
+    { "iv",            OPTF_ARGREQ,    0,      'i' },
+    { 0,               0,              0,      0 }
+  };
+
+  addopts("k:H:i:", opts);
+
+  for (;;) {
+    int o = opt();
+    if (o < 0)
+      break;
+    switch (o) {
+      case 'k':
+       DRESET(&d);
+       textkey(&d, optarg, ciphertab[i].keysz);
+       break;
+      case 'H':
+       DRESET(&d);
+       hexkey(&d, optarg, ciphertab[i].keysz);
+       break;
+      case 'i': {
+       char *p;
+       unhex(optarg, &p, &iv);
+       if (*p)
+         die(EXIT_FAILURE, "bad hex IV `%s'", optarg);
+      } break;
+      default:
+       return (0);
+    }
+  }
+
+  if (!d.len)
+    randkey(&d, ciphertab[i].keysz);
+  r = ciphertab[i].counter(d.buf, d.len);
   if (iv.len) {
-    while (iv.len < ofbtab[i].blksz)
-      DPUTD(&iv, &iv);
+    if (iv.len != ciphertab[i].blksz) {
+      die(EXIT_FAILURE, "bad IV length %lu (must be %lu)",
+         (unsigned long)iv.len, (unsigned long)ciphertab[i].blksz);
+    }
     r->ops->misc(r, GRAND_SEEDBLOCK, iv.buf);
   }
 
@@ -683,6 +865,58 @@ static grand *gen_ofb(unsigned i)
   return (r);
 }
 
+/* --- Mask generators --- */
+
+static grand *gen_mgf(unsigned i)
+{
+  grand *r;
+  dstr d = DSTR_INIT;
+  uint32 c = 0;
+
+  static struct option opts[] = {
+    { "key",           OPTF_ARGREQ,    0,      'k' },
+    { "hex",           OPTF_ARGREQ,    0,      'H' },
+    { "index",         OPTF_ARGREQ,    0,      'i' },
+    { 0,               0,              0,      0 }
+  };
+
+  addopts("k:H:i:", opts);
+
+  for (;;) {
+    int o = opt();
+    if (o < 0)
+      break;
+    switch (o) {
+      case 'k':
+       DRESET(&d);
+       textkey(&d, optarg, hashtab[i].keysz);
+       break;
+      case 'H':
+       DRESET(&d);
+       hexkey(&d, optarg, hashtab[i].keysz);
+       break;
+      case 'i': {
+       char *p;
+       c = strtoul(optarg, &p, 0);
+       if (*p)
+         die(EXIT_FAILURE, "bad index `%s'", optarg);
+      } break;
+      default:
+       return (0);
+    }
+  }
+
+  if (!d.len)
+    randkey(&d, hashtab[i].keysz);
+
+  r = hashtab[i].mgf(d.buf, d.len);
+  if (c)
+    r->ops->misc(r, GRAND_SEEDUINT32, c);
+
+  dstr_destroy(&d);
+  return (r);
+}
+
 /* --- Fibonacci generator --- */
 
 static grand *gen_fib(unsigned i)
@@ -774,22 +1008,29 @@ static gen generators[] = {
     "[-s SEED]" },
   { "lc",              gen_lc,         0,
     "[-s SEED]" },
-  { "des-ofb",         gen_ofb,        OFB_DES,
-    "[-k KEY-PHRASE] [-H HEX-KEY] [-i HEX-IV]" },
-  { "3des-ofb",                gen_ofb,        OFB_DES3,
+#define E(PRE, pre)                                                    \
+  { #pre "-ofb",       gen_ofb,        CIPHER_##PRE,                   \
     "[-k KEY-PHRASE] [-H HEX-KEY] [-i HEX-IV]" },
-  { "rc5-ofb",         gen_ofb,        OFB_RC5,
-    "[-k KEY-PHRASE] [-H HEX-KEY] [-i HEX-IV]" },
-  { "blowfish-ofb",            gen_ofb,        OFB_BLOWFISH,
-    "[-k KEY-PHRASE] [-H HEX-KEY] [-i HEX-IV]" },
-  { "idea-ofb",                gen_ofb,        OFB_IDEA,
+  CIPHERS
+#undef E
+#define E(PRE, pre)                                                    \
+  { #pre "-counter",   gen_counter,    CIPHER_##PRE,                   \
     "[-k KEY-PHRASE] [-H HEX-KEY] [-i HEX-IV]" },
+  CIPHERS
+#undef E(PRE, pre)
+#define E(PRE, pre)                                                    \
+  { #pre "-mgf",       gen_mgf,        HASH_##PRE,                     \
+    "[-k KEY-PHRASE] [-H HEX-KEY] [-i INDEX]" },
+  HASHES
+#undef E(PRE, pre)
   { "rc4",             gen_rc4,        0,
     "[-k KEY-PHRASE] [-H HEX-KEY]" },
+  { "seal",            gen_seal,       0,
+    "[-k KEY-PHRASE] [-H HEX-KEY] [-n SEQ]" },
   { "rand",            gen_rand,       0,
     "[-n] [-k KEY-PHRASE] [-t TEXT-BLOCK] [-H HEX-BLOCK]" },
   { "bbs",             gen_bbs,        0,
-    "[-gS] [-s SEED] [-m MODULUS] [-b BITS] [-k KEYRING] [-i TAG] [-t TYPE]"
+    "[-gS] [-s SEED] [-M MODULUS] [-b BITS] [-k KEYRING] [-i TAG] [-t TYPE]"
   },
   { 0,                 0,              0, 0 },
 };
@@ -803,10 +1044,10 @@ int main(int ac, char *av[])
 {
   gen *g = &optsg;
   grand *r;
-  unsigned percent = -1;
+  unsigned percent = 0;
   size_t kb = 0;
   time_t last;
-  static char baton[] = "|/-\\";
+  static char baton[] = "-\\|/";
   char *bp;
 
   /* --- Initialize mLib --- */
@@ -817,6 +1058,7 @@ int main(int ac, char *av[])
   /* --- Set up the main Catacomb generator --- */
 
   rand_noisesrc(RAND_GLOBAL, &noise_source);
+  rand_seed(RAND_GLOBAL, 160);
 
   /* --- Initialize the options table --- */
 
@@ -857,6 +1099,72 @@ int main(int ac, char *av[])
     exit(EXIT_FAILURE);
   }
 
+  /* --- Do the FIPS test --- */
+
+  if (flags & f_fips) {
+    octet buf[FIPSTEST_BUFSZ];
+    unsigned rc;
+
+    r->ops->fill(r, buf, sizeof(buf));
+    rc = fipstest(buf);
+    if (rc & FIPSTEST_MONOBIT)
+      moan("failed monobit test");
+    if (rc & FIPSTEST_POKER)
+      moan("failed poker test");
+    if (rc & FIPSTEST_RUNS)
+      moan("failed runs test");
+    if (rc & FIPSTEST_LONGRUNS)
+      moan("failed long runs test");
+    if (!rc && (flags & f_progress))
+      puts("test passed");
+    return (rc ? EXIT_FAILURE : 0);
+  }
+
+  /* --- Do Maurer's test --- */
+
+  if (flags & f_maurer) {
+    octet buf[250 * 1024];
+    unsigned i;
+    unsigned rc = 0;
+    unsigned f = 0, jj = 0;
+    double maxz = 0;
+
+    static struct { double x; const char *sig; } sigtab[] = {
+      { 3.2905, "1e-3" },
+      { 3.0902, "2e-3" },
+      { 2.8070, "5e-3" },
+      { 2.5758, "1e-2" },
+      { 0     , 0      }
+    };
+
+    r->ops->fill(r, buf, sizeof(buf));
+    for (i = 5; i < 8; i++) {
+      double z = maurer(buf, sizeof(buf), i + 1);
+      double zz = fabs(z);
+      unsigned j;
+
+      for (j = 0; sigtab[j].sig; j++) {
+       if (zz > sigtab[j].x) {
+         if (zz > fabs(maxz)) {
+           maxz = z;
+           f = i + 1;
+           jj = j;
+         }
+         rc = EXIT_FAILURE;
+         moan("failed, bits = %u, sig = %s, Z_u = %g",
+              i + 1, sigtab[j].sig, z);
+         break;
+       }
+      }
+      if (flags & f_progress)
+       printf("bits = %u, Z_u = %g\n", i + 1, z);
+    }
+
+    return (rc);
+  }
+
+  /* --- Make sure we don't write to the terminal --- */
+
 #ifndef PORTABLE
   if (!(flags & f_file) && isatty(STDOUT_FILENO))
     die(EXIT_FAILURE, "writing output to a terminal is a bad idea");
@@ -869,7 +1177,10 @@ int main(int ac, char *av[])
   if (flags & f_progress) {
     char *errbuf = xmalloc(BUFSIZ);
     setvbuf(stderr, errbuf, _IOLBF, BUFSIZ);
-    fputc('[', stderr);
+    if (outsz)
+      fprintf(stderr, "[%*s]   0%%    0\r[/\b", 50, "");
+    else
+      fputs("[ ]    0\r[/\b", stderr);
     fflush(stderr);
   }