Param Reddappagari | 386ce4d | 2011-10-04 12:15:40 -0700 | [diff] [blame] | 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 | |
| 56 | void |
| 57 | cipher_driver_test_throughput(cipher_t *c); |
| 58 | |
| 59 | err_status_t |
| 60 | cipher_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 | |
| 69 | err_status_t |
| 70 | cipher_driver_test_buffering(cipher_t *c); |
| 71 | |
| 72 | |
| 73 | /* |
| 74 | * functions for testing cipher cache thrash |
| 75 | */ |
| 76 | err_status_t |
| 77 | cipher_driver_test_array_throughput(cipher_type_t *ct, |
| 78 | int klen, int num_cipher); |
| 79 | |
| 80 | void |
| 81 | cipher_array_test_throughput(cipher_t *ca[], int num_cipher); |
| 82 | |
| 83 | uint64_t |
| 84 | cipher_array_bits_per_second(cipher_t *cipher_array[], int num_cipher, |
| 85 | unsigned octets_in_buffer, int num_trials); |
| 86 | |
| 87 | err_status_t |
| 88 | cipher_array_delete(cipher_t *cipher_array[], int num_cipher); |
| 89 | |
| 90 | err_status_t |
| 91 | cipher_array_alloc_init(cipher_t ***cipher_array, int num_ciphers, |
| 92 | cipher_type_t *ctype, int klen); |
| 93 | |
| 94 | void |
| 95 | usage(char *prog_name) { |
| 96 | printf("usage: %s [ -t | -v | -a ]\n", prog_name); |
| 97 | exit(255); |
| 98 | } |
| 99 | |
| 100 | void |
| 101 | check_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 | |
| 115 | extern cipher_type_t null_cipher; |
| 116 | extern cipher_type_t aes_icm; |
| 117 | extern cipher_type_t aes_cbc; |
| 118 | |
| 119 | int |
| 120 | main(int argc, char *argv[]) { |
| 121 | cipher_t *c = NULL; |
| 122 | err_status_t status; |
| 123 | unsigned char test_key[20] = { |
| 124 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, |
| 125 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, |
| 126 | 0x10, 0x11, 0x12, 0x13 |
| 127 | }; |
| 128 | int q; |
| 129 | unsigned do_timing_test = 0; |
| 130 | unsigned do_validation = 0; |
| 131 | unsigned do_array_timing_test = 0; |
| 132 | |
| 133 | /* process input arguments */ |
| 134 | while (1) { |
| 135 | q = getopt(argc, argv, "tva"); |
| 136 | if (q == -1) |
| 137 | break; |
| 138 | switch (q) { |
| 139 | case 't': |
| 140 | do_timing_test = 1; |
| 141 | break; |
| 142 | case 'v': |
| 143 | do_validation = 1; |
| 144 | break; |
| 145 | case 'a': |
| 146 | do_array_timing_test = 1; |
| 147 | break; |
| 148 | default: |
| 149 | usage(argv[0]); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | printf("cipher test driver\n" |
| 154 | "David A. McGrew\n" |
| 155 | "Cisco Systems, Inc.\n"); |
| 156 | |
| 157 | if (!do_validation && !do_timing_test && !do_array_timing_test) |
| 158 | usage(argv[0]); |
| 159 | |
| 160 | /* arry timing (cache thrash) test */ |
| 161 | if (do_array_timing_test) { |
| 162 | int max_num_cipher = 1 << 16; /* number of ciphers in cipher_array */ |
| 163 | int num_cipher; |
| 164 | |
| 165 | for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) |
| 166 | cipher_driver_test_array_throughput(&null_cipher, 0, num_cipher); |
| 167 | |
| 168 | for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) |
| 169 | cipher_driver_test_array_throughput(&aes_icm, 30, num_cipher); |
| 170 | |
| 171 | for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) |
| 172 | cipher_driver_test_array_throughput(&aes_cbc, 16, num_cipher); |
| 173 | |
| 174 | } |
| 175 | |
| 176 | if (do_validation) { |
| 177 | cipher_driver_self_test(&null_cipher); |
| 178 | cipher_driver_self_test(&aes_icm); |
| 179 | cipher_driver_self_test(&aes_cbc); |
| 180 | } |
| 181 | |
| 182 | /* do timing and/or buffer_test on null_cipher */ |
| 183 | status = cipher_type_alloc(&null_cipher, &c, 0); |
| 184 | check_status(status); |
| 185 | |
| 186 | status = cipher_init(c, NULL, direction_encrypt); |
| 187 | check_status(status); |
| 188 | |
| 189 | if (do_timing_test) |
| 190 | cipher_driver_test_throughput(c); |
| 191 | if (do_validation) { |
| 192 | status = cipher_driver_test_buffering(c); |
| 193 | check_status(status); |
| 194 | } |
| 195 | status = cipher_dealloc(c); |
| 196 | check_status(status); |
| 197 | |
| 198 | |
| 199 | /* run the throughput test on the aes_icm cipher */ |
| 200 | status = cipher_type_alloc(&aes_icm, &c, 30); |
| 201 | if (status) { |
| 202 | fprintf(stderr, "error: can't allocate cipher\n"); |
| 203 | exit(status); |
| 204 | } |
| 205 | |
| 206 | status = cipher_init(c, test_key, direction_encrypt); |
| 207 | check_status(status); |
| 208 | |
| 209 | if (do_timing_test) |
| 210 | cipher_driver_test_throughput(c); |
| 211 | |
| 212 | if (do_validation) { |
| 213 | status = cipher_driver_test_buffering(c); |
| 214 | check_status(status); |
| 215 | } |
| 216 | |
| 217 | status = cipher_dealloc(c); |
| 218 | check_status(status); |
| 219 | |
| 220 | return 0; |
| 221 | } |
| 222 | |
| 223 | void |
| 224 | cipher_driver_test_throughput(cipher_t *c) { |
| 225 | int i; |
| 226 | int min_enc_len = 32; |
| 227 | int max_enc_len = 2048; /* should be a power of two */ |
| 228 | int num_trials = 100000; |
| 229 | |
| 230 | printf("timing %s throughput:\n", c->type->description); |
| 231 | fflush(stdout); |
| 232 | for (i=min_enc_len; i <= max_enc_len; i = i * 2) |
| 233 | printf("msg len: %d\tgigabits per second: %f\n", |
| 234 | i, cipher_bits_per_second(c, i, num_trials) / 1e9); |
| 235 | |
| 236 | } |
| 237 | |
| 238 | err_status_t |
| 239 | cipher_driver_self_test(cipher_type_t *ct) { |
| 240 | err_status_t status; |
| 241 | |
| 242 | printf("running cipher self-test for %s...", ct->description); |
| 243 | status = cipher_type_self_test(ct); |
| 244 | if (status) { |
| 245 | printf("failed with error code %d\n", status); |
| 246 | exit(status); |
| 247 | } |
| 248 | printf("passed\n"); |
| 249 | |
| 250 | return err_status_ok; |
| 251 | } |
| 252 | |
| 253 | /* |
| 254 | * cipher_driver_test_buffering(ct) tests the cipher's output |
| 255 | * buffering for correctness by checking the consistency of succesive |
| 256 | * calls |
| 257 | */ |
| 258 | |
| 259 | err_status_t |
| 260 | cipher_driver_test_buffering(cipher_t *c) { |
| 261 | int i, j, num_trials = 1000; |
| 262 | unsigned len, buflen = 1024; |
| 263 | uint8_t buffer0[buflen], buffer1[buflen], *current, *end; |
| 264 | uint8_t idx[16] = { |
| 265 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 266 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34 |
| 267 | }; |
| 268 | err_status_t status; |
| 269 | |
| 270 | printf("testing output buffering for cipher %s...", |
| 271 | c->type->description); |
| 272 | |
| 273 | for (i=0; i < num_trials; i++) { |
| 274 | |
| 275 | /* set buffers to zero */ |
| 276 | for (j=0; j < buflen; j++) |
| 277 | buffer0[j] = buffer1[j] = 0; |
| 278 | |
| 279 | /* initialize cipher */ |
| 280 | status = cipher_set_iv(c, idx); |
| 281 | if (status) |
| 282 | return status; |
| 283 | |
| 284 | /* generate 'reference' value by encrypting all at once */ |
| 285 | status = cipher_encrypt(c, buffer0, &buflen); |
| 286 | if (status) |
| 287 | return status; |
| 288 | |
| 289 | /* re-initialize cipher */ |
| 290 | status = cipher_set_iv(c, idx); |
| 291 | if (status) |
| 292 | return status; |
| 293 | |
| 294 | /* now loop over short lengths until buffer1 is encrypted */ |
| 295 | current = buffer1; |
| 296 | end = buffer1 + buflen; |
| 297 | while (current < end) { |
| 298 | |
| 299 | /* choose a short length */ |
| 300 | len = rand() & 0x01f; |
| 301 | |
| 302 | /* make sure that len doesn't cause us to overreach the buffer */ |
| 303 | if (current + len > end) |
| 304 | len = end - current; |
| 305 | |
| 306 | status = cipher_encrypt(c, current, &len); |
| 307 | if (status) |
| 308 | return status; |
| 309 | |
| 310 | /* advance pointer into buffer1 to reflect encryption */ |
| 311 | current += len; |
| 312 | |
| 313 | /* if buffer1 is all encrypted, break out of loop */ |
| 314 | if (current == end) |
| 315 | break; |
| 316 | } |
| 317 | |
| 318 | /* compare buffers */ |
| 319 | for (j=0; j < buflen; j++) |
| 320 | if (buffer0[j] != buffer1[j]) { |
| 321 | #if PRINT_DEBUG |
| 322 | printf("test case %d failed at byte %d\n", i, j); |
| 323 | printf("computed: %s\n", octet_string_hex_string(buffer1, buflen)); |
| 324 | printf("expected: %s\n", octet_string_hex_string(buffer0, buflen)); |
| 325 | #endif |
| 326 | return err_status_algo_fail; |
| 327 | } |
| 328 | } |
| 329 | |
| 330 | printf("passed\n"); |
| 331 | |
| 332 | return err_status_ok; |
| 333 | } |
| 334 | |
| 335 | |
| 336 | /* |
| 337 | * The function cipher_test_throughput_array() tests the effect of CPU |
| 338 | * cache thrash on cipher throughput. |
| 339 | * |
| 340 | * cipher_array_alloc_init(ctype, array, num_ciphers) creates an array |
| 341 | * of cipher_t of type ctype |
| 342 | */ |
| 343 | |
| 344 | err_status_t |
| 345 | cipher_array_alloc_init(cipher_t ***ca, int num_ciphers, |
| 346 | cipher_type_t *ctype, int klen) { |
| 347 | int i, j; |
| 348 | err_status_t status; |
| 349 | uint8_t *key; |
| 350 | cipher_t **cipher_array; |
| 351 | |
| 352 | /* allocate array of pointers to ciphers */ |
| 353 | cipher_array = (cipher_t **) malloc(sizeof(cipher_t *) * num_ciphers); |
| 354 | if (cipher_array == NULL) |
| 355 | return err_status_alloc_fail; |
| 356 | |
| 357 | /* set ca to location of cipher_array */ |
| 358 | *ca = cipher_array; |
| 359 | |
| 360 | /* allocate key */ |
| 361 | key = crypto_alloc(klen); |
| 362 | if (key == NULL) { |
| 363 | free(cipher_array); |
| 364 | return err_status_alloc_fail; |
| 365 | } |
| 366 | |
| 367 | /* allocate and initialize an array of ciphers */ |
| 368 | for (i=0; i < num_ciphers; i++) { |
| 369 | |
| 370 | /* allocate cipher */ |
| 371 | status = cipher_type_alloc(ctype, cipher_array, klen); |
| 372 | if (status) |
| 373 | return status; |
| 374 | |
| 375 | /* generate random key and initialize cipher */ |
| 376 | for (j=0; j < klen; j++) |
| 377 | key[j] = (uint8_t) rand(); |
| 378 | status = cipher_init(*cipher_array, key, direction_encrypt); |
| 379 | if (status) |
| 380 | return status; |
| 381 | |
| 382 | /* printf("%dth cipher is at %p\n", i, *cipher_array); */ |
| 383 | /* printf("%dth cipher description: %s\n", i, */ |
| 384 | /* (*cipher_array)->type->description); */ |
| 385 | |
| 386 | /* advance cipher array pointer */ |
| 387 | cipher_array++; |
| 388 | } |
| 389 | |
| 390 | return err_status_ok; |
| 391 | } |
| 392 | |
| 393 | err_status_t |
| 394 | cipher_array_delete(cipher_t *cipher_array[], int num_cipher) { |
| 395 | int i; |
| 396 | |
| 397 | for (i=0; i < num_cipher; i++) { |
| 398 | cipher_dealloc(cipher_array[i]); |
| 399 | } |
| 400 | |
| 401 | free(cipher_array); |
| 402 | |
| 403 | return err_status_ok; |
| 404 | } |
| 405 | |
| 406 | |
| 407 | /* |
| 408 | * cipher_array_bits_per_second(c, l, t) computes (an estimate of) the |
| 409 | * number of bits that a cipher implementation can encrypt in a second |
| 410 | * when distinct keys are used to encrypt distinct messages |
| 411 | * |
| 412 | * c is a cipher (which MUST be allocated an initialized already), l |
| 413 | * is the length in octets of the test data to be encrypted, and t is |
| 414 | * the number of trials |
| 415 | * |
| 416 | * if an error is encountered, the value 0 is returned |
| 417 | */ |
| 418 | |
| 419 | uint64_t |
| 420 | cipher_array_bits_per_second(cipher_t *cipher_array[], int num_cipher, |
| 421 | unsigned octets_in_buffer, int num_trials) { |
| 422 | int i; |
| 423 | v128_t nonce; |
| 424 | clock_t timer; |
| 425 | unsigned char *enc_buf; |
| 426 | int cipher_index = 0; |
| 427 | |
| 428 | |
| 429 | enc_buf = crypto_alloc(octets_in_buffer); |
| 430 | if (enc_buf == NULL) |
| 431 | return 0; /* indicate bad parameters by returning null */ |
| 432 | |
| 433 | /* time repeated trials */ |
| 434 | v128_set_to_zero(&nonce); |
| 435 | timer = clock(); |
| 436 | for(i=0; i < num_trials; i++, nonce.v32[3] = i) { |
| 437 | |
| 438 | /* choose a cipher at random from the array*/ |
| 439 | cipher_index = (*((uint32_t *)enc_buf)) % num_cipher; |
| 440 | |
| 441 | /* encrypt buffer with cipher */ |
| 442 | cipher_set_iv(cipher_array[cipher_index], &nonce); |
| 443 | cipher_encrypt(cipher_array[cipher_index], enc_buf, &octets_in_buffer); |
| 444 | } |
| 445 | timer = clock() - timer; |
| 446 | |
| 447 | free(enc_buf); |
| 448 | |
| 449 | if (timer == 0) { |
| 450 | /* Too fast! */ |
| 451 | return 0; |
| 452 | } |
| 453 | |
| 454 | return CLOCKS_PER_SEC * num_trials * 8 * octets_in_buffer / timer; |
| 455 | } |
| 456 | |
| 457 | void |
| 458 | cipher_array_test_throughput(cipher_t *ca[], int num_cipher) { |
| 459 | int i; |
| 460 | int min_enc_len = 16; |
| 461 | int max_enc_len = 2048; /* should be a power of two */ |
| 462 | int num_trials = 10000; |
| 463 | |
| 464 | printf("timing %s throughput with array size %d:\n", |
| 465 | (ca[0])->type->description, num_cipher); |
| 466 | fflush(stdout); |
| 467 | for (i=min_enc_len; i <= max_enc_len; i = i * 4) |
| 468 | printf("msg len: %d\tgigabits per second: %f\n", i, |
| 469 | cipher_array_bits_per_second(ca, num_cipher, i, num_trials) / 1e9); |
| 470 | |
| 471 | } |
| 472 | |
| 473 | err_status_t |
| 474 | cipher_driver_test_array_throughput(cipher_type_t *ct, |
| 475 | int klen, int num_cipher) { |
| 476 | cipher_t **ca = NULL; |
| 477 | err_status_t status; |
| 478 | |
| 479 | status = cipher_array_alloc_init(&ca, num_cipher, ct, klen); |
| 480 | if (status) { |
| 481 | printf("error: cipher_array_alloc_init() failed with error code %d\n", |
| 482 | status); |
| 483 | return status; |
| 484 | } |
| 485 | |
| 486 | cipher_array_test_throughput(ca, num_cipher); |
| 487 | |
| 488 | cipher_array_delete(ca, num_cipher); |
| 489 | |
| 490 | return err_status_ok; |
| 491 | } |