chiark / gitweb /
math/t/mpx-mul4: Fix comment markers.
[catacomb] / symm / modes-test.c
1 /* -*-c-*-
2  *
3  * Common code for testing encryption modes
4  *
5  * (c) 2018 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Catacomb.
11  *
12  * Catacomb is free software: you can redistribute it and/or modify it
13  * under the terms of the GNU Library General Public License as published
14  * by the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * Catacomb is distributed in the hope that it will be useful, but
18  * WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with Catacomb.  If not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25  * USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include <errno.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <unistd.h>
36
37 #include <mLib/alloc.h>
38 #include <mLib/bits.h>
39 #include <mLib/dstr.h>
40 #include <mLib/quis.h>
41 #include <mLib/macros.h>
42 #include <mLib/report.h>
43
44 #include "modes-test.h"
45
46 /*----- The reference data ------------------------------------------------*/
47
48 #ifdef SMALL_TEST
49 static const octet text[] = "A small piece of text for testing encryption.";
50 #else
51 #define STORY "\
52 Once upon a time there were a beautiful princess, a slightly nutty wizard,\n\
53 and a watermelon.  Now, the watermelon had decided that it probably wasn't\n\
54 going to get very far with the princess unless it did something pretty\n\
55 drastic.  So it asked the wizard to turn it into a handsome prince.\n\
56 \n\
57 At least, this is the way that the wizard viewed the situation.  He might\n\
58 have just hallucinated it all; those mushrooms had looked ever so nice.\n\
59 \n\
60 Back to the point.  The watermelon had expressed its desire not to be a\n\
61 watermelon any more.  And the wizard was probably tripping something quite\n\
62 powerful.  He hunted around a bit for his staff, and mumbled something\n\
63 that film directors would think of as sounding appropriately arcane and\n\
64 mystical (but was, in fact, just the ingredients list for an ancient\n\
65 remedy for athlete's foot) and *pop*.  Cooked watermelon.  Yuk.\n\
66 \n\
67 Later in the year, the princess tripped over the hem of her dress, fell\n\
68 down a spiral staircase, and died.  The king ordered dressmakers to attach\n\
69 safety warnings to long dresses.\n\
70 \n\
71 And the wizard?  Who cares?\n\
72 "
73 static const octet text[] = STORY STORY;
74 #endif
75
76 #define TEXTSZ (sizeof(text))
77
78 static const octet key[] = "Penguins rule OK, rhubarb cauliflower",
79   iv[] = "EdgewareCatacomb, parsley, sage, rosemary and thyme";
80
81 /*----- Static variables --------------------------------------------------*/
82
83 /* Encryption buffers, for ciphertext, recovered plaintext, and consistency
84  * reference.
85  */
86 static octet ct[TEXTSZ], pt[TEXTSZ], ref[TEXTSZ];
87
88 /* A resizeable buffer for verifying regression data. */
89 static octet *t = 0; size_t tsz = 0;
90
91 /*----- Diagnostic utilities ----------------------------------------------*/
92
93 /* Print the @sz@-byte buffer @p@, beginning at offset @off@ within some
94  * larger buffer, marking block boundaries every @blksz@ bytes.
95  */
96 static void hexdump(const octet *p, size_t sz, size_t off, size_t blksz)
97 {
98   const octet *q = p + sz;
99   for (sz = 0; p < q; p++, sz++) {
100     printf("%02x", *p);
101     if ((off + sz + 1)%blksz == 0) putchar(':');
102   }
103 }
104
105 /* Print the buffer @p@, labelling it as @what@, splitting it into three
106  * pieces of sizes @sz0@, @sz1@, and @sz2@ respectively.  Block boundaries
107  * every @blksz@ bytes are shown consistency, independent of the split
108  * positions.
109  */
110 static void dump_split(const char *what, size_t blksz, const octet *p,
111                        size_t sz0, size_t sz1, size_t sz2)
112 {
113   printf("\t%-16s = ", what);
114   hexdump(p, sz0, 0, blksz);
115   if (sz1) { printf(", "); hexdump(p + sz0, sz1, sz0, blksz); }
116   if (sz2) { printf(", "); hexdump(p + sz0 + sz1, sz2, sz0 + sz1, blksz); }
117   fputc('\n', stdout);
118 }
119
120 /*----- Regression-data utilities -----------------------------------------*/
121
122 /* Regression modes.  We can @CHECK@ existing data, @RECORD@ new data, or
123  * @IGNORE@ the regression testing entirely.
124  */
125 enum { IGNORE, RECORD, CHECK };
126
127 /* Read or write regression data from/to @fp@ according to @rmode@.  The data
128  * item is described as @what@ in diagnostic messages, and consists of @sz@
129  * bytes beginning at @p@.
130  *
131  * If @rmode@ is @IGNORE@, then this function does nothing; if @rmode@ is
132  * @RECORD@, then it writes @p@ to the output file with some framing; and if
133  * @rmode@ is @CHECK@ then it checks that the next chunk of data from the
134  * file matches @p@.
135  *
136  * Returns zero if all is well or @-1@ on a mismatch; I/O errors are fatal.
137  *
138  * Framing is trivial and consists of a 4-byte big-endian non-inclusive
139  * length prepended to each buffer.  No padding is written to maintain
140  * alignment.
141  */
142 static int regress_data(int rmode, FILE *fp, const char *what,
143                         const void *p, size_t sz)
144 {
145   octet b[4];
146   size_t psz;
147
148   switch (rmode) {
149     case IGNORE:
150       return (0);
151     case RECORD:
152       STORE32(b, sz);
153       if (!fwrite(b, 4, 1, fp) || !fwrite(p, sz, 1, fp))
154         die(1, "failed to write %s: %s", what, strerror(errno));
155       return (0);
156     case CHECK:
157       if (!fread(b, 4, 1, fp))
158         die(1, "failed to read %s length: %s", what,
159             ferror(fp) ? strerror(errno) : "unexpected eof");
160       psz = LOAD32(b);
161       if (psz != sz)
162         die(1, "incorrect %s length (%lu /= %lu; sync failure?)",
163             what, (unsigned long)psz, (unsigned long)sz);
164       if (tsz < sz) { xfree(t); t = xmalloc(sz); tsz = sz; }
165       if (!fread(t, sz, 1, fp))
166         die(1, "failed to read %s: %s", what,
167             ferror(fp) ? strerror(errno) : "unexpected eof");
168       if (MEMCMP(p, !=, t, sz)) return (-1);
169       return (0);
170     default:
171       abort();
172   }
173 }
174
175 /* Read or write framing data from/to @fp@ according to @rmode@.  The framing
176  * item is describd as @what@ in diagnostic messages, and consists of @sz@
177  * bytes beginning at @p@.
178  *
179  * Framing data is used to verify that a recorded regression-data file is
180  * still appropriate for use.  A fatal error is reported on any kind of
181  * failure.
182  */
183 static void regress_framing(int rmode, FILE *fp, const char *what,
184                             const void *p, size_t sz)
185 {
186   if (regress_data(rmode, fp, what, p, sz))
187     die(1, "regression framing mismatch for %s (bug, or wrong file)", what);
188 }
189
190 /* Read or write crypto data from/to @fp@ according to @rmode@.  The data
191  * item is describd as @what@ in diagnostic messages, and consists of the
192  * bytes beginning at @p@.  For the purposes of diagnostics, this buffer has
193  * been notionally split into three pieces, with sizes @sz0@, @sz1@, and
194  * @sz2@, respectively.
195  *
196  * If al is well, return zero.  If the crypto data doesn't match the recorded
197  * regression data, then report the mismatch, showing the way in which the
198  * buffer is split, and return -1.  I/O errors are fatal.
199  */
200 static int regress_crypto(int rmode, FILE *fp, const char *what, size_t blksz,
201                           const void *p, size_t sz0, size_t sz1, size_t sz2)
202 {
203   int rc;
204
205   rc = regress_data(rmode, fp, what, p, sz0 + sz1 + sz2);
206   if (rc) {
207     printf("\nRegression mismatch (split = %lu/%lu/%lu)\n",
208            (unsigned long)sz0, (unsigned long)sz1, (unsigned long)sz2);
209     dump_split("plaintext", blksz, text, sz0, sz1, sz2);
210     dump_split("expected ct", blksz, t, sz0, sz1, sz2);
211     dump_split("computed ct", blksz, p, sz0, sz1, sz2);
212     fputc('\n', stdout);
213   }
214   return (rc);
215 }
216
217 /*----- Selecting fragment sizes ------------------------------------------*/
218
219 /* Return codes from @step@. */
220 enum { STEP, LIMIT, RESET };
221
222 /* Update @*sz_inout@ the next largest suitable fragment size, up to a
223  * maximum of @max@.
224  *
225  * If the new size is still smaller than the maximum, then return @STEP@.  If
226  * the size is maximal, then return @LIMIT@.  If the size was previously
227  * maximal already, then return @RESET@.
228  *
229  * The sizes here are selected powers of two, and powers of two plus or minus
230  * 1, with the objective of testing how internal buffering is affected when
231  * the cursor is misaligned and realigned with block boundaries.
232  */
233 static int step(size_t *sz_inout, size_t max)
234 {
235   size_t i;
236
237   static size_t steps[] = {   1,   7,   8,   9,  15,  16,  17,
238                                   63,  64,  65, 255, 256, 257 };
239
240   if (*sz_inout == max) return (RESET);
241   for (i = 0; i < N(steps); i++)
242     if (steps[i] > *sz_inout) {
243       if (steps[i] < max) { *sz_inout = steps[i]; return (STEP); }
244       else break;
245     }
246   *sz_inout = max; return (LIMIT);
247 }
248
249 /*----- Main code ---------------------------------------------------------*/
250
251 /* --- @test_encmode@ --- *
252  *
253  * Arguments:   @const char *name@ = name of the encryption scheme; used to
254  *                      find the regression-test filename
255  *              @size_t ksz@ = key size to use, or zero for `don't care'
256  *              @size_t blksz@ = block size
257  *              @size_t minsz@ = smallest acceptable buffer size, or 1
258  *              @unsigned f@ = various additional flags
259  *              @setupfn *setup@ = key-setup function
260  *              @resetfn *reset@ = state-reset function
261  *              @encfn *enc@ = encryption function
262  *              @decfn *dec@ = decryption function
263  *              @int argc@ = number of command-line arguments
264  *              @char *argv@ = pointer to command-line argument vector
265  *
266  * Returns:     Zero on success, nonzero to report failure.
267  *
268  * Use:         Tests an encryption mode which doesn't have any more formal
269  *              test vectors.
270  *
271  *              The @name@ is used firstly in diagnostic output and secondly
272  *              to form the default filename to use for regression-test data,
273  *              as `.../symm/t/modes/NAME.regress'.
274  *
275  *              The key size @ksz@ is simply passed on back to the @setup@
276  *              function, unless the caller passes in zero, in which case
277  *              @test_encmode@ chooses a key size for itself.
278  *
279  *              The block size @blksz@ is used in failure reports, to draw
280  *              attention to the block structure in the various buffers,
281  *              which may assist with diagnosis.  It's also used to determine
282  *              when to apply a consistency check: see below regarding the
283  *              @TEMF_REFALIGN@ flag.
284  *
285  *              The minimum buffer size @minsz@ expresses a limitation on the
286  *              provided @enc@ and @dec@ functions, that they don't work on
287  *              inputs smaller than @minsz@; accordingly, @test_encmode@ will
288  *              not test such small sizes.  This should be 1 if the mode has
289  *              no limitation.
290  *
291  *              The flags @f@ influence testing in various ways explained
292  *              below.
293  *
294  *              The caller-provided functions are assumed to act on some
295  *              global but hidden state,
296  *
297  *                * @setup@ is (currently, at least) called only once, with
298  *                  the key @k@ and its chosen size @ksz@.
299  *
300  *                * @reset@ is called at the start of each encryption or
301  *                  decryption operation, to program in the initialization
302  *                  vector to use.  Currently, the same IV is used in all of
303  *                  the tests, but this might not always be the case.
304  *
305  *                * @enc@ is called to encrypt a source buffer @s@ and write
306  *                  the ciphertext to a destination @d@; @sz@ is the common
307  *                  size of these buffers.  @d@ might be null, to discard
308  *                  output; @s@ might be null, to process all-zero input.
309  *
310  *                * @dec@ is called to decrypt a source buffer @s@ and write
311  *                  the recovered plaintext to a destination @d@; @sz@ is the
312  *                  common size of these buffers.
313  *
314  *              Finally, @int argc@ and @char *argv@ are the command-line
315  *              arguments provided to @main@; @test_encmode@ parses these and
316  *              alters its behaviour accordingly.
317  *
318  *              Currently, @test_encmode@'s tests are built around a single,
319  *              fairly large, fixed message.  In each test step, the message
320  *              is split into a number of fragments which are encrypted and
321  *              decrypted in turn.
322  *
323  *              The following tests are performed.
324  *
325  *                * The fundamental `round-trip' test, which verifies that
326  *                  the message can be encrypted and then decrypted
327  *                  successfully, if the same fragment boundaries are used in
328  *                  both cases.
329  *
330  *                * A `consistency' test.  Some modes, such as CFB, OFB, and
331  *                  counter, are `resumable': encryption operations are
332  *                  insensitive to the position of fragment boundaries, so a
333  *                  single message can be broken into fragments without
334  *                  affecting the result.  If @TEMF_REFALIGN@ is clear then
335  *                  the mode under test is verified to have this property.
336  *                  If @TEMF_REFALIGN' is set, a weaker property is verified:
337  *                  that encryption is insensitive to the position of
338  *                  /block-aligned/ fragment boundaries only.
339  *
340  *                * A `regression' test, which verifies that the code
341  *                  produces the same ciphertext as a previous version.  By
342  *                  setting command-line arguments appropriately, a test
343  *                  program can be told to record ciphertexts in a (binary)
344  *                  data file.  Usually, instead, the program will read the
345  *                  recorded ciphertexts back and verify that it produces the
346  *                  same data.  For resumable modes, it's only necessary to
347  *                  record single ciphertext, since all the other ciphertexts
348  *                  must be equal by consistency; otherwise all non-block-
349  *                  aligned splits are recorded separately.
350  */
351
352 int test_encmode(const char *name,
353                  size_t ksz, size_t blksz, size_t minsz, unsigned f,
354                  setupfn *setup, resetfn *reset, encfn *enc, encfn *dec,
355                  int argc, char *argv[])
356 {
357   int ok = 1, refp = 0, i;
358   size_t sz0, sz1, sz2;
359   const char spinner[] = "/-\\|";
360   int rmode = CHECK, spin = isatty(STDOUT_FILENO) ? 0 : -1;
361   int regr;
362   const char *rname = 0, *p;
363   FILE *fp;
364   dstr d = DSTR_INIT;
365
366   ego(argv[0]);
367
368   /* Parse the command-line options. */
369   p = 0; i = 1;
370   for (;;) {
371
372     /* Read the next argument. */
373     if (!p || !*p) {
374       if (i >= argc) break;
375       p = argv[i++];
376       if (STRCMP(p, ==, "--")) break;
377       if (p[0] != '-' || p[1] == 0) { i--; break; }
378       p++;
379     }
380
381     /* Interpret an option. */
382     switch (*p++) {
383       case 'h':
384         printf("%s test driver\n"
385                "Usage: %s [-i] [-o|-f FILENAME]\n", QUIS, QUIS);
386         exit(0);
387       case 'i':
388         rmode = IGNORE;
389         break;
390       case 'o':
391         if (!*p) {
392           if (i >= argc) die(1, "option `-o' expects an argument");
393           p = argv[i++];
394         }
395         rmode = RECORD; rname = p; p = 0;
396         break;
397       case 'f':
398         if (!*p) {
399           if (i >= argc) die(1, "option `-f' expects an argument");
400           p = argv[i++];
401         }
402         rmode = CHECK; rname = p; p = 0;
403         break;
404       default:
405         die(1, "option `-%c' unknown", p[-1]);
406     }
407   }
408
409   /* Check there's nothing else left. */
410   if (i < argc) die(1, "trailing junk on command line");
411
412   /* Open the regression-data file. */
413   if (rmode == IGNORE)
414     fp = 0;
415   else {
416     if (!rname) {
417       DRESET(&d); dstr_putf(&d, SRCDIR"/t/modes/%s.regress", name);
418       rname = xstrdup(d.buf);
419     }
420     fp = fopen(rname, rmode == RECORD ? "wb" : "rb");
421     if (!fp)
422       die(1, "failed to open `%s' for %s: %s", rname,
423           rmode == RECORD ? "writing" : "reading", strerror(errno));
424   }
425
426   /* Write a header describing the file, to trap misuse for the wrong mode,
427    * and changes in the text.
428    */
429   DRESET(&d);
430   dstr_putf(&d, "mode=%s, text=%lu", name, (unsigned long)TEXTSZ);
431   regress_framing(rmode, fp, "header", d.buf, d.len);
432
433   /* Start things up. */
434   printf("%s: ", name);
435   setup(key, ksz ? ksz: sizeof(key));
436
437   /* Work through various sizes of up to three fragments.  The middle
438    * fragment is the important one, since it can be misaligned or not at
439    * either end.
440    */
441   sz0 = sz1 = minsz;
442   for (;;) {
443
444     /* If output is to a terminal then display a spinner to keep humans
445      * amused.
446      */
447     if (spin >= 0) {
448       printf("\r%s: [%c]\b\b", name, spinner[spin]); fflush(stdout);
449       spin = (spin + 1)&3;
450     }
451
452     /* Prepare for the test. */
453     sz2 = TEXTSZ - sz1 - sz0;
454     ok = 1;
455
456     /* Encrypt the last fragment first, to check discarding behaviour. */
457     if (sz2) {
458       reset(iv);
459       enc(text, 0, sz0);
460       enc(text + sz0, 0, sz1);
461       enc(text + sz0 + sz1, ct + sz0 + sz1, sz2);
462     }
463
464     /* Encrypt the first two fragments. */
465     reset(iv);
466     enc(text, ct, sz0);
467     if (sz1) {
468       memcpy(ct + sz0, text + sz0, sz1);
469       enc(ct + sz0, ct + sz0, sz1);
470     }
471
472     /* Try to check consistency.  We can't do this if (a) the mode is
473      * non-resumable and the fragments sizes are misaligned, or (b) this is
474      * our first pass through and we don't have a consistency reference yet.
475      *
476      * Also, decide whether to deploy the regression test, which we do if and
477      * only if we can't compare against the consistency reference.
478      */
479     regr = 0;
480     if ((f&TEMF_REFALIGN) && (sz0%blksz || sz1%blksz)) regr = 1;
481     else if (!refp) { memcpy(ref, ct, TEXTSZ); regr = 1; refp = 1; }
482     else if (MEMCMP(ref, !=, ct, TEXTSZ)) {
483       ok = 0;
484       printf("\nConsistency failure (split = %lu/%lu/%lu)\n",
485              (unsigned long)sz0, (unsigned long)sz1, (unsigned long)sz2);
486       dump_split("plaintext", blksz, text, sz0, sz1, sz2);
487       dump_split("reference", blksz, ref, sz0, sz1, sz2);
488       dump_split("ciphertext", blksz, ct, sz0, sz1, sz2);
489       fputc('\n', stdout);
490     }
491
492     /* If we need the regression test then do that.  Write a framing record
493      * to avoid confusion if the policy changes.
494      */
495     if (regr) {
496       DRESET(&d);
497       dstr_putf(&d, "split = %lu/%lu/%lu",
498                 (unsigned long)sz0, (unsigned long)sz1, (unsigned long)sz2);
499       regress_framing(rmode, fp, "split", d.buf, d.len);
500       if (regress_crypto(rmode, fp, "regress", blksz, ct, sz0, sz1, sz2))
501         ok = 0;
502     }
503
504     /* Finally, decrypt and check that the round-trip works. */
505     reset(iv);
506     dec(ct, pt, sz0);
507     if (sz1) {
508       memcpy(pt + sz0, ct + sz0, sz1);
509       dec(pt + sz0, pt + sz0, sz1);
510     }
511     if (sz2)
512       dec(ct + sz0 + sz1, pt + sz0 + sz1, sz2);
513     if (MEMCMP(text, !=, pt, TEXTSZ)) {
514       ok = 0;
515       printf("\nRound-trip failure (split = %lu/%lu/%lu)\n",
516              (unsigned long)sz0, (unsigned long)sz1, (unsigned long)sz2);
517       dump_split("plaintext", blksz, text, sz0, sz1, sz2);
518       dump_split("ciphertext", blksz, ct, sz0, sz1, sz2);
519       dump_split("recovered", blksz, pt, sz0, sz1, sz2);
520       fputc('\n', stdout);
521     }
522
523     /* Update the fragment sizes. */
524     if (!sz1) break;
525     if (step(&sz1, TEXTSZ - sz0) == RESET) {
526       if (step(&sz0, TEXTSZ) == LIMIT) sz1 = 0;
527       else sz1 = minsz;
528     }
529   }
530
531   /* Close the regression data file. */
532   if (fp && (ferror(fp) || fclose(fp)))
533     die(1, "error closing `%s': %s", rname, strerror(errno));
534
535   /* Finish off the eyecandy spinner. */
536   if (spin >= 0) printf("\r%s: [%c] ", name, ok ? '*' : 'X');
537
538   /* Summarize the test result. */
539   if (ok) printf("ok\n");
540   else printf("failed\n");
541
542   /* And we're done. */
543   dstr_destroy(&d);
544   return (!ok);
545 }
546
547 /*----- That's all, folks -------------------------------------------------*/