Commit | Line | Data |
---|---|---|
a4c6c9ba MW |
1 | /* |
2 | * crypto-test.c: common test vector processing | |
3 | */ | |
4 | /* | |
5 | * This file is Free Software. It was originally written for secnet. | |
6 | * | |
22708ca2 | 7 | * Copyright 2017, 2019 Mark Wooding |
a4c6c9ba MW |
8 | * |
9 | * You may redistribute secnet as a whole and/or modify it under the | |
10 | * terms of the GNU General Public License as published by the Free | |
11 | * Software Foundation; either version 3, or (at your option) any | |
12 | * later version. | |
13 | * | |
14 | * You may redistribute this file and/or modify it under the terms of | |
15 | * the GNU General Public License as published by the Free Software | |
16 | * Foundation; either version 2, or (at your option) any later | |
17 | * version. | |
18 | * | |
19 | * This software is distributed in the hope that it will be useful, | |
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
22 | * GNU General Public License for more details. | |
23 | * | |
24 | * You should have received a copy of the GNU General Public License | |
25 | * along with this software; if not, see | |
26 | * https://www.gnu.org/licenses/gpl.html. | |
27 | */ | |
28 | ||
29 | #include <assert.h> | |
30 | #include <errno.h> | |
31 | #include <ctype.h> | |
32 | #include <stdarg.h> | |
33 | #include <stdio.h> | |
34 | #include <stdlib.h> | |
35 | #include <string.h> | |
36 | ||
37 | #include "secnet.h" | |
38 | #include "util.h" | |
39 | ||
40 | #include "crypto-test.h" | |
41 | ||
42 | /*----- Utilities ---------------------------------------------------------*/ | |
43 | ||
44 | static void *xmalloc(size_t sz) | |
45 | { | |
46 | void *p; | |
47 | ||
48 | if (!sz) return 0; | |
49 | p = malloc(sz); | |
50 | if (!p) { | |
51 | fprintf(stderr, "out of memory!\n"); | |
52 | exit(2); | |
53 | } | |
54 | return p; | |
55 | } | |
56 | ||
57 | static void *xrealloc(void *p, size_t sz) | |
58 | { | |
59 | void *q; | |
60 | ||
61 | if (!sz) { free(p); return 0; } | |
62 | else if (!p) return xmalloc(sz); | |
63 | q = realloc(p, sz); | |
64 | if (!q) { | |
65 | fprintf(stderr, "out of memory!\n"); | |
66 | exit(2); | |
67 | } | |
68 | return q; | |
69 | } | |
70 | ||
71 | static int lno; | |
72 | ||
73 | void bail(const char *msg, ...) | |
74 | { | |
75 | va_list ap; | |
76 | va_start(ap, msg); | |
77 | fprintf(stderr, "unexpected error (line %d): ", lno); | |
78 | vfprintf(stderr, msg, ap); | |
79 | va_end(ap); | |
80 | fputc('\n', stderr); | |
81 | exit(2); | |
82 | } | |
83 | ||
84 | struct linebuf { | |
85 | char *p; | |
86 | size_t sz; | |
87 | }; | |
88 | #define LINEBUF_INIT { 0, 0 }; | |
89 | ||
90 | static int read_line(struct linebuf *b, FILE *fp) | |
91 | { | |
92 | size_t n = 0; | |
93 | int ch; | |
94 | ||
95 | ch = getc(fp); if (ch == EOF) return EOF; | |
96 | for (;;) { | |
97 | if (n >= b->sz) { | |
98 | b->sz = b->sz ? 2*b->sz : 64; | |
99 | b->p = xrealloc(b->p, b->sz); | |
100 | } | |
101 | if (ch == EOF || ch == '\n') { b->p[n++] = 0; return 0; } | |
102 | b->p[n++] = ch; | |
103 | ch = getc(fp); | |
104 | } | |
105 | } | |
106 | ||
107 | void parse_hex(uint8_t *b, size_t sz, char *p) | |
108 | { | |
109 | size_t n = strlen(p); | |
110 | unsigned i; | |
111 | char bb[3]; | |
112 | ||
113 | if (n%2) bail("bad hex (odd number of nibbles)"); | |
114 | else if (n/2 != sz) bail("bad hex (want %zu bytes, found %zu)", sz, n/2); | |
115 | while (sz) { | |
116 | for (i = 0; i < 2; i++) { | |
117 | if (!isxdigit((unsigned char)p[i])) | |
118 | bail("bad hex digit `%c'", p[i]); | |
119 | bb[i] = p[i]; | |
120 | } | |
121 | bb[2] = 0; | |
122 | p += 2; | |
123 | *b++ = strtoul(bb, 0, 16); sz--; | |
124 | } | |
125 | } | |
126 | ||
127 | void dump_hex(FILE *fp, const uint8_t *b, size_t sz) | |
128 | { while (sz--) fprintf(fp, "%02x", *b++); fputc('\n', fp); } | |
129 | ||
130 | void trivial_regty_init(union regval *v) { ; } | |
131 | void trivial_regty_release(union regval *v) { ; } | |
132 | ||
133 | /* Define some global variables we shouldn't need. | |
134 | * | |
135 | * Annoyingly, `secnet.h' declares static pointers and initializes them to | |
136 | * point to some external variables. At `-O0', GCC doesn't optimize these | |
137 | * away, so there's a link-time dependency on these variables. Define them | |
138 | * here, so that `f25519.c' and `f448.c' can find them. | |
139 | * | |
140 | * (Later GCC has `-Og', which optimizes without making debugging a | |
141 | * nightmare, but I'm not running that version here. Note that `serpent.c' | |
142 | * doesn't have this problem because it defines its own word load and store | |
143 | * operations to cope with its endian weirdness, whereas the field arithmetic | |
144 | * uses `unaligned.h' which manages to include `secnet.h'.) | |
145 | */ | |
146 | uint64_t now_global; | |
147 | struct timeval tv_now_global; | |
148 | ||
a47ab75a MW |
149 | /* Bletch. sha512.c wants to drag in the world. */ |
150 | void *safe_malloc(size_t size, const char *message) { return xmalloc(size); } | |
151 | list_t *new_closure(closure_t *cl) { abort(); } | |
152 | void dict_add(dict_t *dict, cstring_t key, list_t *val) { abort(); } | |
153 | ||
a4c6c9ba MW |
154 | /* Bletch. util.c is a mess of layers. */ |
155 | int consttime_memeq(const void *s1in, const void *s2in, size_t n) | |
156 | { | |
157 | const uint8_t *s1=s1in, *s2=s2in; | |
158 | register volatile uint8_t accumulator=0; | |
159 | ||
160 | while (n-- > 0) { | |
161 | accumulator |= (*s1++ ^ *s2++); | |
162 | } | |
163 | accumulator |= accumulator >> 4; /* constant-time */ | |
164 | accumulator |= accumulator >> 2; /* boolean canonicalisation */ | |
165 | accumulator |= accumulator >> 1; | |
166 | accumulator &= 1; | |
167 | accumulator ^= 1; | |
168 | return accumulator; | |
169 | } | |
170 | ||
171 | /*----- Built-in types ----------------------------------------------------*/ | |
172 | ||
173 | /* Signed integer. */ | |
174 | ||
175 | static void parse_int(union regval *v, char *p) | |
176 | { | |
177 | char *q; | |
178 | ||
179 | errno = 0; | |
180 | v->i = strtol(p, &q, 0); | |
181 | if (*q || errno) bail("bad integer `%s'", p); | |
182 | } | |
183 | ||
184 | static void dump_int(FILE *fp, const union regval *v) | |
185 | { fprintf(fp, "%ld\n", v->i); } | |
186 | ||
187 | static int eq_int(const union regval *v0, const union regval *v1) | |
188 | { return (v0->i == v1->i); } | |
189 | ||
190 | const struct regty regty_int = { | |
191 | trivial_regty_init, | |
192 | parse_int, | |
193 | dump_int, | |
194 | eq_int, | |
195 | trivial_regty_release | |
196 | }; | |
197 | ||
198 | /* Unsigned integer. */ | |
199 | ||
200 | static void parse_uint(union regval *v, char *p) | |
201 | { | |
202 | char *q; | |
203 | ||
204 | errno = 0; | |
205 | v->u = strtoul(p, &q, 0); | |
206 | if (*q || errno) bail("bad integer `%s'", p); | |
207 | } | |
208 | ||
209 | static void dump_uint(FILE *fp, const union regval *v) | |
210 | { fprintf(fp, "%lu\n", v->u); } | |
211 | ||
212 | static int eq_uint(const union regval *v0, const union regval *v1) | |
213 | { return (v0->u == v1->u); } | |
214 | ||
215 | const struct regty regty_uint = { | |
216 | trivial_regty_init, | |
217 | parse_uint, | |
218 | dump_uint, | |
219 | eq_uint, | |
220 | trivial_regty_release | |
221 | }; | |
222 | ||
223 | /* Byte string, as hex. */ | |
224 | ||
225 | void allocate_bytes(union regval *v, size_t sz) | |
226 | { v->bytes.p = xmalloc(sz); v->bytes.sz = sz; } | |
227 | ||
228 | static void init_bytes(union regval *v) { v->bytes.p = 0; v->bytes.sz = 0; } | |
229 | ||
230 | static void parse_bytes(union regval *v, char *p) | |
231 | { | |
232 | size_t n = strlen(p); | |
233 | ||
234 | allocate_bytes(v, n/2); | |
235 | parse_hex(v->bytes.p, v->bytes.sz, p); | |
236 | } | |
237 | ||
238 | static void dump_bytes(FILE *fp, const union regval *v) | |
239 | { dump_hex(fp, v->bytes.p, v->bytes.sz); } | |
240 | ||
241 | static int eq_bytes(const union regval *v0, const union regval *v1) | |
242 | { | |
243 | return v0->bytes.sz == v1->bytes.sz && | |
244 | !memcmp(v0->bytes.p, v1->bytes.p, v0->bytes.sz); | |
245 | } | |
246 | ||
247 | static void release_bytes(union regval *v) { free(v->bytes.p); } | |
248 | ||
249 | const struct regty regty_bytes = { | |
250 | init_bytes, | |
251 | parse_bytes, | |
252 | dump_bytes, | |
253 | eq_bytes, | |
254 | release_bytes | |
255 | }; | |
256 | ||
3f8f9733 MW |
257 | /* Text strings. Not really intended as an output type. */ |
258 | ||
259 | void allocate_string(union regval *v, size_t sz) | |
260 | { v->str.p = xmalloc(sz + 1); v->str.sz = sz; } | |
261 | ||
262 | static void init_string(union regval *v) { v->str.p = 0; v->str.sz = 0; } | |
263 | ||
264 | static void parse_string(union regval *v, char *p) | |
265 | { | |
266 | size_t n = strlen(p); | |
267 | ||
268 | allocate_string(v, n); | |
269 | memcpy(v->str.p, p, n + 1); | |
270 | } | |
271 | ||
272 | static void dump_string(FILE *fp, const union regval *v) | |
273 | { | |
274 | if (v->str.p) fprintf(fp, "`%s'\n", v->str.p); | |
275 | else fputs("nil\n", fp); | |
276 | } | |
277 | ||
278 | static int eq_string(const union regval *v0, const union regval *v1) | |
279 | { | |
280 | size_t n0 = v0->str.sz, n1 = v1->str.sz, n = n0 < n1 ? n0 : n1; | |
281 | return !strncmp(v0->str.p, v1->str.p, n); | |
282 | } | |
283 | ||
284 | static void release_string(union regval *v) { free(v->str.p); } | |
285 | ||
286 | const struct regty regty_string = { | |
287 | init_string, | |
288 | parse_string, | |
289 | dump_string, | |
290 | eq_string, | |
291 | release_string | |
292 | }; | |
293 | ||
a4c6c9ba MW |
294 | /*----- Core test machinery -----------------------------------------------*/ |
295 | ||
296 | /* Say that a register is `reset' by releasing and then re-initializing it. | |
297 | * While there is a current test, all of that test's registers are | |
298 | * initialized. The input registers are reset at the end of `check', ready | |
299 | * for the next test to load new values. The output registers are reset at | |
300 | * the end of `check_test_output', so that a test runner can run a test | |
301 | * multiple times against the same test input, but with different context | |
302 | * data. | |
303 | */ | |
304 | ||
305 | #define REG(rvec, i) \ | |
306 | ((struct reg *)((unsigned char *)state->rvec + (i)*state->regsz)) | |
307 | ||
308 | void check_test_output(struct test_state *state, const struct test *test) | |
309 | { | |
310 | const struct regdef *def; | |
311 | struct reg *reg, *in, *out; | |
312 | int ok = 1; | |
313 | int match; | |
314 | ||
315 | for (def = test->regs; def->name; def++) { | |
316 | if (def->i >= state->nrout) continue; | |
317 | in = REG(in, def->i); out = REG(out, def->i); | |
318 | if (!def->ty->eq(&in->v, &out->v)) ok = 0; | |
319 | } | |
320 | if (ok) | |
321 | state->win++; | |
322 | else { | |
323 | printf("failed test `%s'\n", test->name); | |
324 | for (def = test->regs; def->name; def++) { | |
325 | in = REG(in, def->i); | |
32280470 | 326 | if (!(in->f®F_LIVE)) continue; |
a4c6c9ba MW |
327 | if (def->i >= state->nrout) { |
328 | printf("\t input `%s' = ", def->name); | |
329 | def->ty->dump(stdout, &in->v); | |
330 | } else { | |
331 | out = REG(out, def->i); | |
332 | match = def->ty->eq(&in->v, &out->v); | |
333 | printf("\t%s `%s' = ", | |
334 | match ? " output" : "expected", def->name); | |
335 | def->ty->dump(stdout, &in->v); | |
336 | if (!match) { | |
337 | printf("\tcomputed `%s' = ", def->name); | |
338 | def->ty->dump(stdout, &out->v); | |
339 | } | |
340 | } | |
341 | } | |
342 | state->lose++; | |
343 | } | |
344 | ||
345 | for (def = test->regs; def->name; def++) { | |
346 | if (def->i >= state->nrout) continue; | |
347 | reg = REG(out, def->i); | |
348 | def->ty->release(®->v); def->ty->init(®->v); | |
349 | } | |
350 | } | |
351 | ||
352 | void run_test(struct test_state *state, const struct test *test) | |
353 | { | |
354 | test->fn(state->out, state->in, 0); | |
355 | check_test_output(state, test); | |
356 | } | |
357 | ||
358 | static void check(struct test_state *state, const struct test *test) | |
359 | { | |
360 | const struct regdef *def, *miss = 0; | |
361 | struct reg *reg; | |
362 | int any = 0; | |
363 | ||
364 | if (!test) return; | |
365 | for (def = test->regs; def->name; def++) { | |
366 | reg = REG(in, def->i); | |
367 | if (reg->f®F_LIVE) any = 1; | |
368 | else if (!miss && !(def->f®F_OPT)) miss = def; | |
369 | } | |
370 | if (!any) return; | |
371 | if (miss) | |
372 | bail("register `%s' not set in test `%s'", def->name, test->name); | |
373 | ||
374 | test->run(state, test); | |
375 | ||
376 | for (def = test->regs; def->name; def++) { | |
377 | reg = REG(in, def->i); | |
378 | reg->f = 0; def->ty->release(®->v); def->ty->init(®->v); | |
379 | } | |
380 | } | |
381 | ||
382 | int run_test_suite(unsigned nrout, unsigned nreg, size_t regsz, | |
383 | const struct test *tests, FILE *fp) | |
384 | { | |
385 | struct linebuf buf = LINEBUF_INIT; | |
386 | struct test_state state[1]; | |
387 | const struct test *test; | |
388 | const struct regdef *def; | |
389 | struct reg *reg; | |
390 | char *p; | |
391 | const char *q; | |
392 | int total; | |
393 | size_t n; | |
394 | ||
395 | for (test = tests; test->name; test++) | |
396 | for (def = test->regs; def->name; def++) | |
397 | assert(def->i < nreg); | |
398 | ||
399 | state->in = xmalloc(nreg*regsz); | |
400 | state->out = xmalloc(nrout*regsz); | |
401 | state->nrout = nrout; | |
402 | state->nreg = nreg; | |
403 | state->regsz = regsz; | |
404 | state->win = state->lose = 0; | |
405 | ||
406 | test = 0; | |
407 | lno = 0; | |
408 | while (!read_line(&buf, fp)) { | |
409 | lno++; | |
410 | p = buf.p; n = strlen(buf.p); | |
411 | ||
412 | while (isspace((unsigned char)*p)) p++; | |
413 | if (*p == '#') continue; | |
414 | if (!*p) { check(state, test); continue; } | |
415 | ||
416 | q = p; | |
417 | while (*p && !isspace((unsigned char)*p)) p++; | |
418 | if (*p) *p++ = 0; | |
419 | ||
420 | if (!strcmp(q, "test")) { | |
421 | if (!*p) bail("missing argument"); | |
422 | check(state, test); | |
423 | if (test) { | |
424 | for (def = test->regs; def->name; def++) { | |
425 | def->ty->release(®(in, def->i)->v); | |
426 | if (def->i < state->nrout) | |
427 | def->ty->release(®(out, def->i)->v); | |
428 | } | |
429 | } | |
430 | for (test = tests; test->name; test++) | |
431 | if (!strcmp(p, test->name)) goto found_test; | |
432 | bail("unknown test `%s'", p); | |
433 | found_test: | |
434 | for (def = test->regs; def->name; def++) { | |
435 | reg = REG(in, def->i); | |
436 | reg->f = 0; def->ty->init(®->v); | |
437 | if (def->i < state->nrout) { | |
438 | reg = REG(out, def->i); | |
439 | reg->f = 0; def->ty->init(®->v); | |
440 | } | |
441 | } | |
442 | continue; | |
443 | } | |
444 | ||
445 | if (!test) bail("no current test"); | |
446 | for (def = test->regs; def->name; def++) | |
447 | if (!strcmp(q, def->name)) goto found_reg; | |
448 | bail("unknown register `%s' in test `%s'", q, test->name); | |
449 | found_reg: | |
450 | reg = REG(in, def->i); | |
451 | if (reg->f®F_LIVE) bail("register `%s' already set", def->name); | |
452 | def->ty->parse(®->v, p); reg->f |= REGF_LIVE; | |
453 | } | |
454 | check(state, test); | |
455 | ||
456 | total = state->win + state->lose; | |
457 | if (!state->lose) | |
458 | printf("PASSED all %d test%s\n", state->win, total == 1 ? "" : "s"); | |
459 | else | |
460 | printf("FAILED %d of %d test%s\n", state->lose, total, | |
461 | total == 1 ? "" : "s"); | |
462 | return state->lose ? 1 : 0; | |
463 | } |