progs/perftest.c: Use from Glibc syscall numbers.
[catacomb] / symm / modes-test.c
CommitLineData
57f459eb
MW
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>
141c1284 41#include <mLib/macros.h>
57f459eb
MW
42#include <mLib/report.h>
43
44#include "modes-test.h"
45
46/*----- The reference data ------------------------------------------------*/
47
48#ifdef SMALL_TEST
49static const octet text[] = "A small piece of text for testing encryption.";
50#else
51#define STORY "\
52Once upon a time there were a beautiful princess, a slightly nutty wizard,\n\
53and a watermelon. Now, the watermelon had decided that it probably wasn't\n\
54going to get very far with the princess unless it did something pretty\n\
55drastic. So it asked the wizard to turn it into a handsome prince.\n\
56\n\
57At least, this is the way that the wizard viewed the situation. He might\n\
58have just hallucinated it all; those mushrooms had looked ever so nice.\n\
59\n\
60Back to the point. The watermelon had expressed its desire not to be a\n\
61watermelon any more. And the wizard was probably tripping something quite\n\
62powerful. He hunted around a bit for his staff, and mumbled something\n\
63that film directors would think of as sounding appropriately arcane and\n\
64mystical (but was, in fact, just the ingredients list for an ancient\n\
65remedy for athlete's foot) and *pop*. Cooked watermelon. Yuk.\n\
66\n\
67Later in the year, the princess tripped over the hem of her dress, fell\n\
68down a spiral staircase, and died. The king ordered dressmakers to attach\n\
69safety warnings to long dresses.\n\
70\n\
71And the wizard? Who cares?\n\
72"
73static const octet text[] = STORY STORY;
74#endif
75
76#define TEXTSZ (sizeof(text))
77
78static 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 */
86static octet ct[TEXTSZ], pt[TEXTSZ], ref[TEXTSZ];
87
88/* A resizeable buffer for verifying regression data. */
89static 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 */
96static 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 */
110static 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 */
125enum { 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 */
142static 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");
141c1284 168 if (MEMCMP(p, !=, t, sz)) return (-1);
57f459eb
MW
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 */
183static 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 */
200static 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@. */
220enum { 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 */
233static 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
6a61f346
MW
307 * size of these buffers. @d@ might be null, to discard
308 * output; @s@ might be null, to process all-zero input.
57f459eb
MW
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
352int 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++];
141c1284 376 if (STRCMP(p, ==, "--")) break;
57f459eb
MW
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
6a61f346
MW
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. */
57f459eb
MW
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 }
57f459eb
MW
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; }
141c1284 482 else if (MEMCMP(ref, !=, ct, TEXTSZ)) {
57f459eb
MW
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);
141c1284 513 if (MEMCMP(text, !=, pt, TEXTSZ)) {
57f459eb
MW
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 -------------------------------------------------*/