1/*
2 * cipher_driver.c
3 *
4 * A driver for the generic cipher type
5 *
6 * David A. McGrew
7 * Cisco Systems, Inc.
8 */
9
10/*
11 *
12 * Copyright (c) 2001-2006, Cisco Systems, Inc.
13 * All rights reserved.
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 *
19 *   Redistributions of source code must retain the above copyright
20 *   notice, this list of conditions and the following disclaimer.
21 *
22 *   Redistributions in binary form must reproduce the above
23 *   copyright notice, this list of conditions and the following
24 *   disclaimer in the documentation and/or other materials provided
25 *   with the distribution.
26 *
27 *   Neither the name of the Cisco Systems, Inc. nor the names of its
28 *   contributors may be used to endorse or promote products derived
29 *   from this software without specific prior written permission.
30 *
31 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
34 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
35 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
36 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
37 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
38 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
39 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
42 * OF THE POSSIBILITY OF SUCH DAMAGE.
43 *
44 */
45
46#include <stdio.h>           /* for printf() */
47#include <stdlib.h>          /* for rand() */
48#include <string.h>          /* for memset() */
49#include <unistd.h>          /* for getopt() */
50#include "cipher.h"
51#include "aes_icm.h"
52#include "null_cipher.h"
53
54#define PRINT_DEBUG 0
55
56void
57cipher_driver_test_throughput(cipher_t *c);
58
59err_status_t
60cipher_driver_self_test(cipher_type_t *ct);
61
62
63/*
64 * cipher_driver_test_buffering(ct) tests the cipher's output
65 * buffering for correctness by checking the consistency of succesive
66 * calls
67 */
68
69err_status_t
70cipher_driver_test_buffering(cipher_t *c);
71
72
73/*
74 * functions for testing cipher cache thrash
75 */
76err_status_t
77cipher_driver_test_array_throughput(cipher_type_t *ct,
78				    int klen, int num_cipher);
79
80void
81cipher_array_test_throughput(cipher_t *ca[], int num_cipher);
82
83uint64_t
84cipher_array_bits_per_second(cipher_t *cipher_array[], int num_cipher,
85			     unsigned octets_in_buffer, int num_trials);
86
87err_status_t
88cipher_array_delete(cipher_t *cipher_array[], int num_cipher);
89
90err_status_t
91cipher_array_alloc_init(cipher_t ***cipher_array, int num_ciphers,
92			cipher_type_t *ctype, int klen);
93
94void
95usage(char *prog_name) {
96  printf("usage: %s [ -t | -v | -a ]\n", prog_name);
97  exit(255);
98}
99
100void
101check_status(err_status_t s) {
102  if (s) {
103    printf("error (code %d)\n", s);
104    exit(s);
105  }
106  return;
107}
108
109/*
110 * null_cipher, aes_icm, and aes_cbc are the cipher meta-objects
111 * defined in the files in crypto/cipher subdirectory.  these are
112 * declared external so that we can use these cipher types here
113 */
114
115extern cipher_type_t null_cipher;
116extern cipher_type_t aes_icm;
117extern cipher_type_t aes_cbc;
118
119int
120main(int argc, char *argv[]) {
121  cipher_t *c = NULL;
122  err_status_t status;
123  unsigned char test_key[48] = {
124    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
125    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
126    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
127    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
128    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
129    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
130  };
131  int q;
132  unsigned do_timing_test = 0;
133  unsigned do_validation = 0;
134  unsigned do_array_timing_test = 0;
135
136  /* process input arguments */
137  while (1) {
138    q = getopt(argc, argv, "tva");
139    if (q == -1)
140      break;
141    switch (q) {
142    case 't':
143      do_timing_test = 1;
144      break;
145    case 'v':
146      do_validation = 1;
147      break;
148    case 'a':
149      do_array_timing_test = 1;
150      break;
151    default:
152      usage(argv[0]);
153    }
154  }
155
156  printf("cipher test driver\n"
157	 "David A. McGrew\n"
158	 "Cisco Systems, Inc.\n");
159
160  if (!do_validation && !do_timing_test && !do_array_timing_test)
161    usage(argv[0]);
162
163   /* arry timing (cache thrash) test */
164  if (do_array_timing_test) {
165    int max_num_cipher = 1 << 16;   /* number of ciphers in cipher_array */
166    int num_cipher;
167
168    for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8)
169      cipher_driver_test_array_throughput(&null_cipher, 0, num_cipher);
170
171    for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8)
172      cipher_driver_test_array_throughput(&aes_icm, 30, num_cipher);
173
174    for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8)
175      cipher_driver_test_array_throughput(&aes_icm, 46, num_cipher);
176
177    for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8)
178      cipher_driver_test_array_throughput(&aes_cbc, 16, num_cipher);
179
180    for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8)
181      cipher_driver_test_array_throughput(&aes_cbc, 32, num_cipher);
182  }
183
184  if (do_validation) {
185    cipher_driver_self_test(&null_cipher);
186    cipher_driver_self_test(&aes_icm);
187    cipher_driver_self_test(&aes_cbc);
188  }
189
190  /* do timing and/or buffer_test on null_cipher */
191  status = cipher_type_alloc(&null_cipher, &c, 0);
192  check_status(status);
193
194  status = cipher_init(c, NULL, direction_encrypt);
195  check_status(status);
196
197  if (do_timing_test)
198    cipher_driver_test_throughput(c);
199  if (do_validation) {
200    status = cipher_driver_test_buffering(c);
201    check_status(status);
202  }
203  status = cipher_dealloc(c);
204  check_status(status);
205
206
207  /* run the throughput test on the aes_icm cipher (128-bit key) */
208    status = cipher_type_alloc(&aes_icm, &c, 30);
209    if (status) {
210      fprintf(stderr, "error: can't allocate cipher\n");
211      exit(status);
212    }
213
214    status = cipher_init(c, test_key, direction_encrypt);
215    check_status(status);
216
217    if (do_timing_test)
218      cipher_driver_test_throughput(c);
219
220    if (do_validation) {
221      status = cipher_driver_test_buffering(c);
222      check_status(status);
223    }
224
225    status = cipher_dealloc(c);
226    check_status(status);
227
228  /* repeat the tests with 256-bit keys */
229    status = cipher_type_alloc(&aes_icm, &c, 46);
230    if (status) {
231      fprintf(stderr, "error: can't allocate cipher\n");
232      exit(status);
233    }
234
235    status = cipher_init(c, test_key, direction_encrypt);
236    check_status(status);
237
238    if (do_timing_test)
239      cipher_driver_test_throughput(c);
240
241    if (do_validation) {
242      status = cipher_driver_test_buffering(c);
243      check_status(status);
244    }
245
246    status = cipher_dealloc(c);
247    check_status(status);
248
249  return 0;
250}
251
252void
253cipher_driver_test_throughput(cipher_t *c) {
254  int i;
255  int min_enc_len = 32;
256  int max_enc_len = 2048;   /* should be a power of two */
257  int num_trials = 1000000;
258
259  printf("timing %s throughput, key length %d:\n", c->type->description, c->key_len);
260  fflush(stdout);
261  for (i=min_enc_len; i <= max_enc_len; i = i * 2)
262    printf("msg len: %d\tgigabits per second: %f\n",
263	   i, cipher_bits_per_second(c, i, num_trials) / 1e9);
264
265}
266
267err_status_t
268cipher_driver_self_test(cipher_type_t *ct) {
269  err_status_t status;
270
271  printf("running cipher self-test for %s...", ct->description);
272  status = cipher_type_self_test(ct);
273  if (status) {
274    printf("failed with error code %d\n", status);
275    exit(status);
276  }
277  printf("passed\n");
278
279  return err_status_ok;
280}
281
282/*
283 * cipher_driver_test_buffering(ct) tests the cipher's output
284 * buffering for correctness by checking the consistency of succesive
285 * calls
286 */
287
288err_status_t
289cipher_driver_test_buffering(cipher_t *c) {
290  int i, j, num_trials = 1000;
291  unsigned len, buflen = 1024;
292  uint8_t buffer0[buflen], buffer1[buflen], *current, *end;
293  uint8_t idx[16] = {
294    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
295    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34
296  };
297  err_status_t status;
298
299  printf("testing output buffering for cipher %s...",
300	 c->type->description);
301
302  for (i=0; i < num_trials; i++) {
303
304   /* set buffers to zero */
305    for (j=0; j < buflen; j++)
306      buffer0[j] = buffer1[j] = 0;
307
308    /* initialize cipher  */
309    status = cipher_set_iv(c, idx);
310    if (status)
311      return status;
312
313    /* generate 'reference' value by encrypting all at once */
314    status = cipher_encrypt(c, buffer0, &buflen);
315    if (status)
316      return status;
317
318    /* re-initialize cipher */
319    status = cipher_set_iv(c, idx);
320    if (status)
321      return status;
322
323    /* now loop over short lengths until buffer1 is encrypted */
324    current = buffer1;
325    end = buffer1 + buflen;
326    while (current < end) {
327
328      /* choose a short length */
329      len = rand() & 0x01f;
330
331      /* make sure that len doesn't cause us to overreach the buffer */
332      if (current + len > end)
333	len = end - current;
334
335      status = cipher_encrypt(c, current, &len);
336      if (status)
337	return status;
338
339      /* advance pointer into buffer1 to reflect encryption */
340      current += len;
341
342      /* if buffer1 is all encrypted, break out of loop */
343      if (current == end)
344	break;
345    }
346
347    /* compare buffers */
348    for (j=0; j < buflen; j++)
349      if (buffer0[j] != buffer1[j]) {
350#if PRINT_DEBUG
351	printf("test case %d failed at byte %d\n", i, j);
352	printf("computed: %s\n", octet_string_hex_string(buffer1, buflen));
353	printf("expected: %s\n", octet_string_hex_string(buffer0, buflen));
354#endif
355	return err_status_algo_fail;
356      }
357  }
358
359  printf("passed\n");
360
361  return err_status_ok;
362}
363
364
365/*
366 * The function cipher_test_throughput_array() tests the effect of CPU
367 * cache thrash on cipher throughput.
368 *
369 * cipher_array_alloc_init(ctype, array, num_ciphers) creates an array
370 * of cipher_t of type ctype
371 */
372
373err_status_t
374cipher_array_alloc_init(cipher_t ***ca, int num_ciphers,
375			cipher_type_t *ctype, int klen) {
376  int i, j;
377  err_status_t status;
378  uint8_t *key;
379  cipher_t **cipher_array;
380  /* pad klen allocation, to handle aes_icm reading 16 bytes for the
381     14-byte salt */
382  int klen_pad = ((klen + 15) >> 4) << 4;
383
384  /* allocate array of pointers to ciphers */
385  cipher_array = (cipher_t **) malloc(sizeof(cipher_t *) * num_ciphers);
386  if (cipher_array == NULL)
387    return err_status_alloc_fail;
388
389  /* set ca to location of cipher_array */
390  *ca = cipher_array;
391
392  /* allocate key */
393  key = crypto_alloc(klen_pad);
394  if (key == NULL) {
395    free(cipher_array);
396    return err_status_alloc_fail;
397  }
398
399  /* allocate and initialize an array of ciphers */
400  for (i=0; i < num_ciphers; i++) {
401
402    /* allocate cipher */
403    status = cipher_type_alloc(ctype, cipher_array, klen);
404    if (status)
405      return status;
406
407    /* generate random key and initialize cipher */
408    for (j=0; j < klen; j++)
409      key[j] = (uint8_t) rand();
410    for (; j < klen_pad; j++)
411      key[j] = 0;
412    status = cipher_init(*cipher_array, key, direction_encrypt);
413    if (status)
414      return status;
415
416/*     printf("%dth cipher is at %p\n", i, *cipher_array); */
417/*     printf("%dth cipher description: %s\n", i,  */
418/* 	   (*cipher_array)->type->description); */
419
420    /* advance cipher array pointer */
421    cipher_array++;
422  }
423
424  crypto_free(key);
425
426  return err_status_ok;
427}
428
429err_status_t
430cipher_array_delete(cipher_t *cipher_array[], int num_cipher) {
431  int i;
432
433  for (i=0; i < num_cipher; i++) {
434    cipher_dealloc(cipher_array[i]);
435  }
436
437  free(cipher_array);
438
439  return err_status_ok;
440}
441
442
443/*
444 * cipher_array_bits_per_second(c, l, t) computes (an estimate of) the
445 * number of bits that a cipher implementation can encrypt in a second
446 * when distinct keys are used to encrypt distinct messages
447 *
448 * c is a cipher (which MUST be allocated an initialized already), l
449 * is the length in octets of the test data to be encrypted, and t is
450 * the number of trials
451 *
452 * if an error is encountered, the value 0 is returned
453 */
454
455uint64_t
456cipher_array_bits_per_second(cipher_t *cipher_array[], int num_cipher,
457			      unsigned octets_in_buffer, int num_trials) {
458  int i;
459  v128_t nonce;
460  clock_t timer;
461  unsigned char *enc_buf;
462  int cipher_index = rand() % num_cipher;
463
464  /* Over-alloc, for NIST CBC padding */
465  enc_buf = crypto_alloc(octets_in_buffer+17);
466  if (enc_buf == NULL)
467    return 0;  /* indicate bad parameters by returning null */
468  memset(enc_buf, 0, octets_in_buffer);
469
470  /* time repeated trials */
471  v128_set_to_zero(&nonce);
472  timer = clock();
473  for(i=0; i < num_trials; i++, nonce.v32[3] = i) {
474    /* length parameter to cipher_encrypt is in/out -- out is total, padded
475     * length -- so reset it each time. */
476    unsigned octets_to_encrypt = octets_in_buffer;
477
478    /* encrypt buffer with cipher */
479    cipher_set_iv(cipher_array[cipher_index], &nonce);
480    cipher_encrypt(cipher_array[cipher_index], enc_buf, &octets_to_encrypt);
481
482    /* choose a cipher at random from the array*/
483    cipher_index = (*((uint32_t *)enc_buf)) % num_cipher;
484  }
485  timer = clock() - timer;
486
487  free(enc_buf);
488
489  if (timer == 0) {
490    /* Too fast! */
491    return 0;
492  }
493
494  return (uint64_t)CLOCKS_PER_SEC * num_trials * 8 * octets_in_buffer / timer;
495}
496
497void
498cipher_array_test_throughput(cipher_t *ca[], int num_cipher) {
499  int i;
500  int min_enc_len = 16;
501  int max_enc_len = 2048;   /* should be a power of two */
502  int num_trials = 1000000;
503
504  printf("timing %s throughput with key length %d, array size %d:\n",
505	 (ca[0])->type->description, (ca[0])->key_len, num_cipher);
506  fflush(stdout);
507  for (i=min_enc_len; i <= max_enc_len; i = i * 4)
508    printf("msg len: %d\tgigabits per second: %f\n", i,
509	   cipher_array_bits_per_second(ca, num_cipher, i, num_trials) / 1e9);
510
511}
512
513err_status_t
514cipher_driver_test_array_throughput(cipher_type_t *ct,
515				    int klen, int num_cipher) {
516  cipher_t **ca = NULL;
517  err_status_t status;
518
519  status = cipher_array_alloc_init(&ca, num_cipher, ct, klen);
520  if (status) {
521    printf("error: cipher_array_alloc_init() failed with error code %d\n",
522	   status);
523    return status;
524  }
525
526  cipher_array_test_throughput(ca, num_cipher);
527
528  cipher_array_delete(ca, num_cipher);
529
530  return err_status_ok;
531}
532