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