#include <sys/param.h>
#include <sys/time.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
struct test_fixture;
struct test_fixture
{
int valid;
int lineno;
char *raw_line;
char *cipher_name;
size_t key_len;
size_t iv_len;
size_t data_len;
uint8_t *key;
uint8_t *iv;
uint8_t *pt;
uint8_t *ct;
struct test_fixture *next;
};
static void usage(void);
static const char *get_test_fixture_header(void);
static void print_test_fixture_header(FILE *out);
static void
print_test_fixture(FILE *out, struct test_fixture *fixture, uint8_t *result);
static int
read_test_fixture(char *line, int lineno, struct test_fixture *fixture);
static struct test_fixture *read_fixtures_from_file(const char *filename);
static void free_test_fixture(struct test_fixture *fixture);
static void print_hex(FILE *out, const uint8_t *bytes, size_t len);
static void read_hex(const char *str, uint8_t *bytes, size_t len);
static int parse_hex_digit(char ch);
static char to_hex(int n);
typedef int(crypt_impl_t)(
const char *cipher_name, unsigned char *key, size_t klen, unsigned char *iv,
unsigned char *in, unsigned char *out, size_t len, int do_encrypt,
int repetitions);
crypt_impl_t syscrypt_cryptoapi;
static uint8_t *safe_syscrypt(
const char *cipher_name, crypt_impl_t *impl, const uint8_t *key,
size_t klen, const uint8_t *iv, size_t ivlen, const uint8_t *data,
size_t datalen, int do_encrypt, int repetitions);
const char *get_test_fixture_header(void)
{
return "cipher;key_len;iv_len;data_len;key;iv;pt;ct\n";
}
void print_test_fixture_header(FILE *out)
{
fprintf(out, "%s", get_test_fixture_header());
}
void print_test_fixture(
FILE *out, struct test_fixture *fixture, uint8_t *result)
{
fprintf(
out, "%s;%ld;%ld;%ld;", fixture->cipher_name, fixture->key_len,
fixture->iv_len, fixture->data_len);
print_hex(out, fixture->key, fixture->key_len);
fprintf(out, ";");
print_hex(out, fixture->iv, fixture->iv_len);
fprintf(out, ";");
print_hex(out, fixture->pt, fixture->data_len);
fprintf(out, ";");
print_hex(out, result ? result : fixture->ct, fixture->data_len);
fprintf(out, "\n");
}
int read_test_fixture(char *line, int lineno, struct test_fixture *fixture)
{
char *token;
bzero(fixture, sizeof(*fixture));
fixture->valid = 0;
fixture->lineno = lineno;
fixture->raw_line = strdup(line);
if (line[0] == '\n' || line[0] == '#')
return (0);
token = strsep(&line, ";");
if (token == NULL)
return (1);
fixture->cipher_name = strdup(token);
token = strsep(&line, ";");
if (token == NULL)
return (1);
fixture->key_len = atoi(token);
token = strsep(&line, ";");
if (token == NULL)
return (1);
fixture->iv_len = atoi(token);
token = strsep(&line, ";");
if (token == NULL)
return (1);
fixture->data_len = atoi(token);
token = strsep(&line, ";");
if (token == NULL)
return (1);
fixture->key = malloc(fixture->key_len);
bzero(fixture->key, fixture->key_len);
read_hex(token, fixture->key, fixture->key_len);
token = strsep(&line, ";");
if (token == NULL)
return (1);
fixture->iv = malloc(fixture->iv_len);
bzero(fixture->iv, fixture->iv_len);
read_hex(token, fixture->iv, fixture->iv_len);
token = strsep(&line, ";");
if (token == NULL)
return (1);
fixture->pt = malloc(fixture->data_len);
bzero(fixture->pt, fixture->data_len);
read_hex(token, fixture->pt, fixture->data_len);
token = strsep(&line, ";");
if (token == NULL)
return (1);
fixture->ct = malloc(fixture->data_len);
bzero(fixture->ct, fixture->data_len);
read_hex(token, fixture->ct, fixture->data_len);
fixture->valid = 1;
return (0);
}
void free_test_fixture(struct test_fixture *fixture)
{
if (fixture->raw_line)
free(fixture->raw_line);
if (fixture->cipher_name)
free(fixture->cipher_name);
if (fixture->key)
free(fixture->key);
if (fixture->iv)
free(fixture->iv);
if (fixture->pt)
free(fixture->pt);
if (fixture->ct)
free(fixture->ct);
free(fixture);
}
void print_hex(FILE *out, const uint8_t *bytes, size_t len)
{
for (size_t i = 0; i < len; ++i) {
fputc(to_hex(bytes[i] / 16), out);
fputc(to_hex(bytes[i] % 16), out);
}
}
int parse_hex_digit(char ch)
{
if (ch >= '0' && ch <= '9')
return (ch - '0');
if (ch >= 'a' && ch <= 'f')
return (10 + ch - 'a');
if (ch >= 'A' && ch <= 'F')
return (10 + ch - 'A');
return (-1);
}
char to_hex(int n)
{
if (n >= 0 && n <= 9)
return ('0' + n);
if (n >= 10 && n <= 15)
return ('a' + n - 10);
return ('X');
}
void read_hex(const char *str, uint8_t *bytes, size_t len)
{
for (size_t i = 0; i < len; i++) {
int digit0 = parse_hex_digit(str[2 * i]);
if (digit0 < 0) {
fprintf(
stderr,
"ERROR: Failed to parse hex string %s (near: %s)\n",
str, &str[2 * i]);
exit(1);
}
int digit1 = parse_hex_digit(str[2 * i + 1]);
if (digit1 < 0) {
fprintf(
stderr,
"ERROR: Failed to parse hex string %s (near: %s)\n",
str, &str[2 * i + 1]);
exit(1);
}
*(bytes++) = digit0 * 16 + digit1;
}
}
static uint8_t *safe_syscrypt(
const char *cipher_name, crypt_impl_t *impl, const uint8_t *key,
size_t klen, const uint8_t *iv, size_t ivlen, const uint8_t *data,
size_t datalen, int do_encrypt, int repetitions)
{
char keycopy[64];
char ivcopy[256];
char *inbuf, *outbuf;
int error;
inbuf = outbuf = NULL;
if (klen > sizeof(keycopy)) {
fprintf(stderr, "ERROR: Invalid klen\n");
exit(1);
}
if (ivlen > sizeof(ivcopy)) {
fprintf(stderr, "ERROR: Invalid iv\n");
exit(1);
}
inbuf = malloc(datalen);
if (inbuf == NULL) {
fprintf(stderr, "ERROR: No memory\n");
exit(1);
}
outbuf = malloc(datalen);
if (outbuf == NULL) {
fprintf(stderr, "ERROR: No memory\n");
free(inbuf);
exit(1);
}
bzero(inbuf, datalen);
bzero(outbuf, datalen);
memcpy(inbuf, data, datalen);
bzero(keycopy, sizeof(keycopy));
memcpy(keycopy, key, klen);
bzero(ivcopy, sizeof(ivcopy));
memcpy(ivcopy, iv, ivlen);
error = impl(
cipher_name, keycopy, klen, ivcopy, inbuf, outbuf, datalen,
do_encrypt, repetitions);
if (error) {
fprintf(
stderr, "ERROR: syscrypt impl failed with: %d\n", error);
free(inbuf);
free(outbuf);
return (NULL);
}
free(inbuf);
return outbuf;
}
void usage(void)
{
printf("Usage: cryptoapi_test [OPTIONS]\n\n");
printf("Options:\n");
printf(
" -t CRYPTOIMPL Crypto implementation to test [default: "
"cryptoapi]\n");
printf(
" -f FIXTURE Test fixture [default: fixtures.csv]\n");
printf(" -o FILE Write results to FILE\n");
printf(
" -b REPETITIONS Run benchmark with specified number of "
"repetitions\n");
printf(" -h Print this help message\n");
}
struct test_fixture *read_fixtures_from_file(const char *filename)
{
static char linebuf[4 * 4096];
FILE *f;
struct test_fixture *head = NULL;
struct test_fixture *prev = NULL;
struct test_fixture *fixture = NULL;
int error;
f = fopen(filename, "r");
if (!f) {
fprintf(stderr, "ERROR: Failed to open file %s\n", filename);
exit(1);
}
fgets(linebuf, sizeof(linebuf), f);
if (strcmp(linebuf, get_test_fixture_header()) != 0) {
fprintf(stderr, "ERROR: invalid test fixture header\n");
exit(1);
}
for (int lineno = 1; fgets(linebuf, sizeof(linebuf), f); ++lineno) {
fixture = malloc(sizeof(struct test_fixture));
error = read_test_fixture(linebuf, lineno, fixture);
if (error) {
fprintf(
stderr, "Invalid test fixture in line %d. Line: %s",
lineno, fixture->raw_line);
exit(1);
}
fixture->next = NULL;
if (head == NULL)
head = fixture;
if (prev)
prev->next = fixture;
prev = fixture;
}
fclose(f);
return (head);
}
int main(int argc, char *argv[])
{
struct timespec start, stop, tv;
int ch;
const char *crypt_impl_name = "cryptoapi";
const char *fixtures_filename = "fixtures.csv";
crypt_impl_t *crypt_impl = NULL;
int repetitions = 1;
bool benchmark = false;
const char *result_filename = NULL;
FILE *result_file = NULL;
while ((ch = getopt(argc, argv, "t:f:b:o:h")) != -1) {
switch (ch) {
case 't':
crypt_impl_name = optarg;
break;
case 'f':
fixtures_filename = optarg;
break;
case 'b':
benchmark = true;
repetitions = atoi(optarg);
break;
case 'o':
result_filename = optarg;
break;
case 'h':
usage();
exit(0);
break;
default:
usage();
exit(1);
break;
}
}
if (strcmp(crypt_impl_name, "cryptoapi") == 0)
crypt_impl = &syscrypt_cryptoapi;
else {
usage();
exit(2);
}
struct test_fixture *fixtures =
read_fixtures_from_file(fixtures_filename);
int total = 0;
int failures = 0;
int ok = 0;
if (result_filename)
result_file = fopen(result_filename, "w");
if (result_file)
print_test_fixture_header(result_file);
if (benchmark) {
fprintf(
stderr, "--------------------------------------------\n");
fprintf(
stderr, "%-22s %21s\n",
"crypto implementation:", crypt_impl_name);
fprintf(stderr, "%-22s %21s\n", "fixtures:", fixtures_filename);
fprintf(stderr, "%-22s %21d\n", "repetitions:", repetitions);
fprintf(
stderr, "--------------------------------------------\n");
fprintf(
stderr, "%15s %5s %9s %5s %6s\n", "Cipher", "Mode",
"Blocklen", "Time", "MiB/s");
}
for (struct test_fixture *fixture = fixtures; fixture;
fixture = fixture->next) {
if (!fixture->valid) {
if (result_file)
fprintf(result_file, "%s", fixture->raw_line);
continue;
}
for (int do_encrypt = 0; do_encrypt <= 1; ++do_encrypt) {
clock_gettime(CLOCK_REALTIME, &start);
uint8_t *data = do_encrypt ? fixture->pt : fixture->ct;
uint8_t *expected =
do_encrypt ? fixture->ct : fixture->pt;
uint8_t *result = safe_syscrypt(
fixture->cipher_name, crypt_impl, fixture->key,
fixture->key_len, fixture->iv, fixture->iv_len,
data, fixture->data_len, do_encrypt, repetitions);
clock_gettime(CLOCK_REALTIME, &stop);
timespecsub(&stop, &start, &tv);
double secs = (double)tv.tv_sec +
(double)tv.tv_nsec / 1000000000.0;
double mib_per_second =
((double)fixture->data_len * repetitions * 2) /
(1024.0 * 1024.0) / secs;
if (benchmark)
fprintf(
stderr, "%15s %5s %9ld %5.2f %6.1f\n",
fixture->cipher_name,
(do_encrypt ? "ENC" : "DEC"),
fixture->data_len, secs, mib_per_second);
++total;
if (result &&
memcmp(result, expected, fixture->data_len) == 0) {
if (!benchmark) {
fprintf(stderr, ".");
fflush(stderr);
}
++ok;
} else {
++failures;
if (!benchmark) {
fprintf(
stderr, "\nFAILED %s line %d\n",
do_encrypt ? "ENC" : "DEC",
fixture->lineno);
fprintf(stderr, "Expected: \n");
print_hex(
stderr, expected,
fixture->data_len);
fprintf(stderr, "\nActual: \n");
print_hex(
stderr, result, fixture->data_len);
fprintf(stderr, "\n");
}
}
if (result_file && do_encrypt)
print_test_fixture(
result_file, fixture, result);
if (result)
free(result);
}
}
if (result_file)
fclose(result_file);
while (fixtures) {
struct test_fixture *next = fixtures->next;
free_test_fixture(fixtures);
fixtures = next;
}
fprintf(stderr, "\n");
fprintf(stderr, "--------------------------------------------\n");
fprintf(
stderr, "Total: %d, OK: %d, Failures: %d\n", total, ok, failures);
fprintf(stderr, "--------------------------------------------\n");
if (!benchmark) {
if (failures > 0 || ok != total)
exit(1);
}
return 0;
}