chiark / gitweb /
Merge branch '2.4.x' into 2.5.x
[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.  @d@ might be null, to discard
307  *                  output; @s@ might be null, to process all-zero input.
308  *
309  *                * @dec@ is called to decrypt a source buffer @s@ and write
310  *                  the recovered plaintext to a destination @d@; @sz@ is the
311  *                  common size of these buffers.
312  *
313  *              Finally, @int argc@ and @char *argv@ are the command-line
314  *              arguments provided to @main@; @test_encmode@ parses these and
315  *              alters its behaviour accordingly.
316  *
317  *              Currently, @test_encmode@'s tests are built around a single,
318  *              fairly large, fixed message.  In each test step, the message
319  *              is split into a number of fragments which are encrypted and
320  *              decrypted in turn.
321  *
322  *              The following tests are performed.
323  *
324  *                * The fundamental `round-trip' test, which verifies that
325  *                  the message can be encrypted and then decrypted
326  *                  successfully, if the same fragment boundaries are used in
327  *                  both cases.
328  *
329  *                * A `consistency' test.  Some modes, such as CFB, OFB, and
330  *                  counter, are `resumable': encryption operations are
331  *                  insensitive to the position of fragment boundaries, so a
332  *                  single message can be broken into fragments without
333  *                  affecting the result.  If @TEMF_REFALIGN@ is clear then
334  *                  the mode under test is verified to have this property.
335  *                  If @TEMF_REFALIGN' is set, a weaker property is verified:
336  *                  that encryption is insensitive to the position of
337  *                  /block-aligned/ fragment boundaries only.
338  *
339  *                * A `regression' test, which verifies that the code
340  *                  produces the same ciphertext as a previous version.  By
341  *                  setting command-line arguments appropriately, a test
342  *                  program can be told to record ciphertexts in a (binary)
343  *                  data file.  Usually, instead, the program will read the
344  *                  recorded ciphertexts back and verify that it produces the
345  *                  same data.  For resumable modes, it's only necessary to
346  *                  record single ciphertext, since all the other ciphertexts
347  *                  must be equal by consistency; otherwise all non-block-
348  *                  aligned splits are recorded separately.
349  */
350
351 int test_encmode(const char *name,
352                  size_t ksz, size_t blksz, size_t minsz, unsigned f,
353                  setupfn *setup, resetfn *reset, encfn *enc, encfn *dec,
354                  int argc, char *argv[])
355 {
356   int ok = 1, refp = 0, i;
357   size_t sz0, sz1, sz2;
358   const char spinner[] = "/-\\|";
359   int rmode = CHECK, spin = isatty(STDOUT_FILENO) ? 0 : -1;
360   int regr;
361   const char *rname = 0, *p;
362   FILE *fp;
363   dstr d = DSTR_INIT;
364
365   ego(argv[0]);
366
367   /* Parse the command-line options. */
368   p = 0; i = 1;
369   for (;;) {
370
371     /* Read the next argument. */
372     if (!p || !*p) {
373       if (i >= argc) break;
374       p = argv[i++];
375       if (strcmp(p, "--") == 0) break;
376       if (p[0] != '-' || p[1] == 0) { i--; break; }
377       p++;
378     }
379
380     /* Interpret an option. */
381     switch (*p++) {
382       case 'h':
383         printf("%s test driver\n"
384                "Usage: %s [-i] [-o|-f FILENAME]\n", QUIS, QUIS);
385         exit(0);
386       case 'i':
387         rmode = IGNORE;
388         break;
389       case 'o':
390         if (!*p) {
391           if (i >= argc) die(1, "option `-o' expects an argument");
392           p = argv[i++];
393         }
394         rmode = RECORD; rname = p; p = 0;
395         break;
396       case 'f':
397         if (!*p) {
398           if (i >= argc) die(1, "option `-f' expects an argument");
399           p = argv[i++];
400         }
401         rmode = CHECK; rname = p; p = 0;
402         break;
403       default:
404         die(1, "option `-%c' unknown", p[-1]);
405     }
406   }
407
408   /* Check there's nothing else left. */
409   if (i < argc) die(1, "trailing junk on command line");
410
411   /* Open the regression-data file. */
412   if (rmode == IGNORE)
413     fp = 0;
414   else {
415     if (!rname) {
416       DRESET(&d); dstr_putf(&d, SRCDIR"/t/modes/%s.regress", name);
417       rname = xstrdup(d.buf);
418     }
419     fp = fopen(rname, rmode == RECORD ? "wb" : "rb");
420     if (!fp)
421       die(1, "failed to open `%s' for %s: %s", rname,
422           rmode == RECORD ? "writing" : "reading", strerror(errno));
423   }
424
425   /* Write a header describing the file, to trap misuse for the wrong mode,
426    * and changes in the text.
427    */
428   DRESET(&d);
429   dstr_putf(&d, "mode=%s, text=%lu", name, (unsigned long)TEXTSZ);
430   regress_framing(rmode, fp, "header", d.buf, d.len);
431
432   /* Start things up. */
433   printf("%s: ", name);
434   setup(key, ksz ? ksz: sizeof(key));
435
436   /* Work through various sizes of up to three fragments.  The middle
437    * fragment is the important one, since it can be misaligned or not at
438    * either end.
439    */
440   sz0 = sz1 = minsz;
441   for (;;) {
442
443     /* If output is to a terminal then display a spinner to keep humans
444      * amused.
445      */
446     if (spin >= 0) {
447       printf("\r%s: [%c]\b\b", name, spinner[spin]); fflush(stdout);
448       spin = (spin + 1)&3;
449     }
450
451     /* Prepare for the test. */
452     sz2 = TEXTSZ - sz1 - sz0;
453     ok = 1;
454
455     /* Encrypt the last fragment first, to check discarding behaviour. */
456     if (sz2) {
457       reset(iv);
458       enc(text, 0, sz0);
459       enc(text + sz0, 0, sz1);
460       enc(text + sz0 + sz1, ct + sz0 + sz1, sz2);
461     }
462
463     /* Encrypt the first two fragments. */
464     reset(iv);
465     enc(text, ct, sz0);
466     if (sz1) {
467       memcpy(ct + sz0, text + sz0, sz1);
468       enc(ct + sz0, ct + sz0, sz1);
469     }
470
471     /* Try to check consistency.  We can't do this if (a) the mode is
472      * non-resumable and the fragments sizes are misaligned, or (b) this is
473      * our first pass through and we don't have a consistency reference yet.
474      *
475      * Also, decide whether to deploy the regression test, which we do if and
476      * only if we can't compare against the consistency reference.
477      */
478     regr = 0;
479     if ((f&TEMF_REFALIGN) && (sz0%blksz || sz1%blksz)) regr = 1;
480     else if (!refp) { memcpy(ref, ct, TEXTSZ); regr = 1; refp = 1; }
481     else if (memcmp(ref, ct, TEXTSZ) != 0) {
482       ok = 0;
483       printf("\nConsistency failure (split = %lu/%lu/%lu)\n",
484              (unsigned long)sz0, (unsigned long)sz1, (unsigned long)sz2);
485       dump_split("plaintext", blksz, text, sz0, sz1, sz2);
486       dump_split("reference", blksz, ref, sz0, sz1, sz2);
487       dump_split("ciphertext", blksz, ct, sz0, sz1, sz2);
488       fputc('\n', stdout);
489     }
490
491     /* If we need the regression test then do that.  Write a framing record
492      * to avoid confusion if the policy changes.
493      */
494     if (regr) {
495       DRESET(&d);
496       dstr_putf(&d, "split = %lu/%lu/%lu",
497                 (unsigned long)sz0, (unsigned long)sz1, (unsigned long)sz2);
498       regress_framing(rmode, fp, "split", d.buf, d.len);
499       if (regress_crypto(rmode, fp, "regress", blksz, ct, sz0, sz1, sz2))
500         ok = 0;
501     }
502
503     /* Finally, decrypt and check that the round-trip works. */
504     reset(iv);
505     dec(ct, pt, sz0);
506     if (sz1) {
507       memcpy(pt + sz0, ct + sz0, sz1);
508       dec(pt + sz0, pt + sz0, sz1);
509     }
510     if (sz2)
511       dec(ct + sz0 + sz1, pt + sz0 + sz1, sz2);
512     if (memcmp(text, pt, TEXTSZ) != 0) {
513       ok = 0;
514       printf("\nRound-trip failure (split = %lu/%lu/%lu)\n",
515              (unsigned long)sz0, (unsigned long)sz1, (unsigned long)sz2);
516       dump_split("plaintext", blksz, text, sz0, sz1, sz2);
517       dump_split("ciphertext", blksz, ct, sz0, sz1, sz2);
518       dump_split("recovered", blksz, pt, sz0, sz1, sz2);
519       fputc('\n', stdout);
520     }
521
522     /* Update the fragment sizes. */
523     if (!sz1) break;
524     if (step(&sz1, TEXTSZ - sz0) == RESET) {
525       if (step(&sz0, TEXTSZ) == LIMIT) sz1 = 0;
526       else sz1 = minsz;
527     }
528   }
529
530   /* Close the regression data file. */
531   if (fp && (ferror(fp) || fclose(fp)))
532     die(1, "error closing `%s': %s", rname, strerror(errno));
533
534   /* Finish off the eyecandy spinner. */
535   if (spin >= 0) printf("\r%s: [%c] ", name, ok ? '*' : 'X');
536
537   /* Summarize the test result. */
538   if (ok) printf("ok\n");
539   else printf("failed\n");
540
541   /* And we're done. */
542   dstr_destroy(&d);
543   return (!ok);
544 }
545
546 /*----- That's all, folks -------------------------------------------------*/