b06d230af416992bc547861cbb3faecc0d078208
[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 -------------------------------------------------*/