#include <sys/wait.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "common.h"
#include <libblake.h> /* for hexadecimal encoding */
#define ERROR(...) (fprintf(stderr, __VA_ARGS__), exit(1))
static char *
read_file(const char *path)
{
int fd;
ssize_t r;
size_t len = 0;
size_t size = 0;
char *buf = NULL;
fd = open(path, O_RDONLY);
if (fd < 0)
ERROR("Internal test error, while opening %s: %s\n", path, strerror(errno)); /* $covered$ */
for (;;) {
if (len == size) {
buf = realloc(buf, (size += 8192) + 1);
if (!buf)
ERROR("Internal test error, while reading %s: %s\n", path, strerror(ENOMEM)); /* $covered$ */
}
r = read(fd, &buf[len], size - len);
if (!r)
break;
if (r < 0)
ERROR("Internal test error, while reading %s: %s\n", path, strerror(errno)); /* $covered$ */
len += (size_t)r;
}
if (memchr(buf, 0, len))
ERROR("Internal test error: file %s contains NUL byte\n", path); /* $covered$ */
buf[len] = '\0';
close(fd);
return buf;
}
static int
check_kat_file(const char *path, const char *algname, void (*hash_function)(unsigned char **msg, size_t msglen, size_t *msgsize,
unsigned char **key, size_t keylen, size_t *keysize,
const unsigned char *hash, size_t hashlen,
unsigned char **out, size_t *outlen, size_t *outsize,
size_t testno, size_t test_lineno, const char *path))
{
char *data = read_file(path);
size_t lineno = 1;
size_t testno = 1;
size_t test_lineno = lineno;
char *in_line = NULL;
char *key_line = NULL;
char *hash_line = NULL;
char *line;
unsigned char *in_bin = NULL;
unsigned char *key_bin = NULL;
unsigned char *hash_bin = NULL;
size_t in_size = 0;
size_t key_size = 0;
size_t hash_size = 0;
size_t in_len = 0;
size_t key_len = 0;
size_t hash_len = 0;
int failed = 0;
int valid;
unsigned char *out = NULL;
size_t out_len;
size_t out_size = 0;
char *out_text = NULL;
size_t out_text_size = 0;
for (line = data; *line; lineno++) {
if (!strncmp(line, "in:", sizeof("in:") - 1)) {
if (in_line)
ERROR("Internal test error, at line %zu in file %s: test contains multiple 'in:'\n", lineno, path); /* $covered$ */
in_line = line;
} else if (!strncmp(line, "key:", sizeof("key:") - 1)) {
if (key_line)
ERROR("Internal test error, at line %zu in file %s: test contains multiple 'key:'\n", lineno, path); /* $covered$ */
key_line = line;
} else if (!strncmp(line, "hash:", sizeof("hash:") - 1)) {
if (hash_line)
ERROR("Internal test error, at line %zu in file %s: test contains multiple 'hash:'\n", lineno, path); /* $covered$ */
hash_line = line;
} else if (*line == '\n') {
if (!in_line && !key_line && !hash_line)
continue; /* $covered$ */
if (!in_line || !key_line || !hash_line)
ERROR("Internal test error, at line %zu in file %s: test is incomplete\n", lineno, path); /* $covered$ */
in_line += sizeof("in:") - 1;
key_line += sizeof("key:") - 1;
hash_line += sizeof("hash:") - 1;
while (isspace(*in_line))
in_line++;
while (isspace(*key_line))
key_line++;
while (isspace(*hash_line))
hash_line++;
in_len = strlen(in_line);
key_len = strlen(key_line);
hash_len = strlen(hash_line);
if (in_len % 2 || key_len % 2 || hash_len % 2)
ERROR("Internal test error: corrupted test at line %zu in file %s\n", test_lineno, path); /* $covered$ */
in_len /= 2;
key_len /= 2;
hash_len /= 2;
if (in_len > in_size) {
in_size = in_len;
in_bin = realloc(in_bin, in_size);
if (!in_bin)
ERROR("Internal test error: %s\n", strerror(ENOMEM)); /* $covered$ */
}
if (key_len > key_size) {
key_size = key_len;
key_bin = realloc(key_bin, key_size);
if (!key_bin)
ERROR("Internal test error: %s\n", strerror(ENOMEM)); /* $covered$ */
}
if (hash_len > hash_size) {
hash_size = hash_len;
hash_bin = realloc(hash_bin, hash_size);
if (!hash_bin)
ERROR("Internal test error: %s\n", strerror(ENOMEM)); /* $covered$ */
}
if (libblake_decode_hex(in_line, in_len * 2, in_bin, &valid) != in_len || !valid ||
libblake_decode_hex(key_line, key_len * 2, key_bin, &valid) != key_len || !valid ||
libblake_decode_hex(hash_line, hash_len * 2, hash_bin, &valid) != hash_len || !valid)
ERROR("Internal test error: corrupted test at line %zu in file %s\n", test_lineno, path); /* $covered$ */
hash_function(&in_bin, in_len, &in_size, &key_bin, key_len, &key_size, hash_bin,
hash_len, &out, &out_len, &out_size, testno, test_lineno, path);
if (out_len != hash_len || memcmp(out, hash_bin, hash_len)) {
/* $covered{$ */
if (out_text_size < out_len * 2 + 1) {
out_text_size = out_len * 2 + 1;
out_text = realloc(out_text, out_text_size);
if (!out_text)
ERROR("Internal test error: %s\n", strerror(ENOMEM));
}
libblake_encode_hex(out, out_len, out_text, 0);
fprintf(stderr,
"%s failed for test %zu at line %zu in file %s:\n"
"\tMessage: 0x%s (%zu %s)\n"
"\tKey: 0x%s (%zu %s)\n"
"\tResult: 0x%s (%zu %s)\n"
"\tExpected: 0x%s (%zu %s)\n"
"\n",
algname, testno, test_lineno, path,
in_line, in_len, in_len == 1 ? "byte" : "bytes",
key_line, key_len, key_len == 1 ? "byte" : "bytes",
out_text, out_len, out_len == 1 ? "byte" : "bytes",
hash_line, hash_len, hash_len == 1 ? "byte" : "bytes");
failed = 1;
/* $covered}$ */
}
in_line = NULL;
key_line = NULL;
hash_line = NULL;
testno += 1;
test_lineno = lineno + 1;
line++;
continue;
} else {
ERROR("Internal test error, at line %zu in file %s: unrecognised line\n", lineno, path); /* $covered$ */
}
line = strchr(line, '\n');
if (!line)
ERROR("Internal test error: file %s is not new-line terminated\n", path); /* $covered$ */
*line++ = '\0';
}
free(in_bin);
free(key_bin);
free(hash_bin);
free(out_text);
free(out);
free(data);
return failed;
}
static void
hashx(unsigned char **msg, size_t msglen, size_t *msgsize,
unsigned char **key, size_t keylen, size_t *keysize,
const unsigned char *hash, size_t hashlen,
unsigned char **out, size_t *outlen, size_t *outsize,
size_t testno, size_t test_lineno, const char *path,
const char *const argv[])
{
pid_t pid;
int input_pipe[2];
int output_pipe[2];
size_t off;
ssize_t r;
int status;
if (keylen) {
/* KEY OPTION IS NOT YET IMPLEMENTED */
*outlen = hashlen;
if (*outsize < hashlen) {
*outsize = hashlen;
*out = realloc(*out, *outsize);
if (!*out)
ERROR("Internal test error: %s\n", strerror(ENOMEM)); /* $covered$ */
}
memcpy(*out, hash, hashlen);
return;
}
if (pipe(input_pipe) || pipe(output_pipe))
ERROR("Internal test error: %s\n", strerror(errno)); /* $covered$ */
pid = fork();
if (pid < 0)
ERROR("Internal test error: %s\n", strerror(errno)); /* $covered$ */
if (!pid) {
/* $covered{$ */
close(input_pipe[1]);
close(output_pipe[0]);
if (input_pipe[0] != STDIN_FILENO) {
if (dup2(input_pipe[0], STDIN_FILENO) != STDIN_FILENO)
ERROR("Internal test error: %s\n", strerror(errno));
close(input_pipe[0]);
}
if (output_pipe[1] != STDOUT_FILENO) {
if (dup2(output_pipe[1], STDOUT_FILENO) != STDOUT_FILENO)
ERROR("Internal test error: %s\n", strerror(errno));
close(output_pipe[1]);
}
execv(argv[0], (void *const)argv);
ERROR("Internal test error: %s\n", strerror(errno));
/* $covered}$ */
} else {
close(input_pipe[0]);
close(output_pipe[1]);
for (off = 0; off < msglen; off += (size_t)r) {
r = write(input_pipe[1], &(*msg)[off], msglen - off);
if (r < 0)
ERROR("Internal test error: %s\n", strerror(errno)); /* $covered$ */
}
if (close(input_pipe[1]))
ERROR("Internal test error: %s\n", strerror(errno)); /* $covered$ */
for (*outlen = 0;;) {
if (*outlen == *outsize) {
*out = realloc(*out, *outsize += 512);
if (!*out)
ERROR("Internal test error: %s\n", strerror(ENOMEM)); /* $covered$ */
}
r = read(output_pipe[0], &(*out)[*outlen], *outsize - *outlen);
if (r <= 0) {
if (!r)
break;
ERROR("Internal test error: %s\n", strerror(errno)); /* $covered$ */
}
*outlen += (size_t)r;
}
close(output_pipe[0]);
if (waitpid(pid, &status, 0) != pid)
ERROR("Internal test error: %s\n", strerror(errno)); /* $covered$ */
if (status)
exit(1); /* $covered$ */
}
}
static void
hash_blake2s(unsigned char **msg, size_t msglen, size_t *msgsize,
unsigned char **key, size_t keylen, size_t *keysize,
const unsigned char *hash, size_t hashlen,
unsigned char **out, size_t *outlen, size_t *outsize,
size_t testno, size_t test_lineno, const char *path)
{
char out_bits_str[3 * sizeof(size_t) + 1];
sprintf(out_bits_str, "%zu", hashlen * 8);
hashx(msg, msglen, msgsize,
key, keylen, keysize,
hash, hashlen,
out, outlen, outsize,
testno, test_lineno, path,
(const char *const[]){"./b2sum", "-Bs", "-l", out_bits_str, NULL});
}
static void
hash_blake2b(unsigned char **msg, size_t msglen, size_t *msgsize,
unsigned char **key, size_t keylen, size_t *keysize,
const unsigned char *hash, size_t hashlen,
unsigned char **out, size_t *outlen, size_t *outsize,
size_t testno, size_t test_lineno, const char *path)
{
char out_bits_str[3 * sizeof(size_t) + 1];
sprintf(out_bits_str, "%zu", hashlen * 8);
hashx(msg, msglen, msgsize,
key, keylen, keysize,
hash, hashlen,
out, outlen, outsize,
testno, test_lineno, path,
(const char *const[]){"./b2sum", "-B", "-l", out_bits_str, NULL});
}
static void
hash_blake2xs(unsigned char **msg, size_t msglen, size_t *msgsize,
unsigned char **key, size_t keylen, size_t *keysize,
const unsigned char *hash, size_t hashlen,
unsigned char **out, size_t *outlen, size_t *outsize,
size_t testno, size_t test_lineno, const char *path)
{
char out_bits_str[3 * sizeof(size_t) + 1];
sprintf(out_bits_str, "%zu", hashlen * 8);
hashx(msg, msglen, msgsize,
key, keylen, keysize,
hash, hashlen,
out, outlen, outsize,
testno, test_lineno, path,
(const char *const[]){"./b2sum", "-Bs", "-X", out_bits_str, NULL});
}
static void
hash_blake2xb(unsigned char **msg, size_t msglen, size_t *msgsize,
unsigned char **key, size_t keylen, size_t *keysize,
const unsigned char *hash, size_t hashlen,
unsigned char **out, size_t *outlen, size_t *outsize,
size_t testno, size_t test_lineno, const char *path)
{
char out_bits_str[3 * sizeof(size_t) + 1];
sprintf(out_bits_str, "%zu", hashlen * 8);
hashx(msg, msglen, msgsize,
key, keylen, keysize,
hash, hashlen,
out, outlen, outsize,
testno, test_lineno, path,
(const char *const[]){"./b2sum", "-B", "-X", out_bits_str, NULL});
}
int
main(void)
{
int failed = 0;
/* TODO test bsum */
failed |= check_kat_file("kat/blake2s", "BLAKE2s", &hash_blake2s);
failed |= check_kat_file("kat/blake2b", "BLAKE2b", &hash_blake2b);
failed |= check_kat_file("kat/blake2xs", "BLAKE2Xs", &hash_blake2xs);
failed |= check_kat_file("kat/blake2xb", "BLAKE2Xb", &hash_blake2xb);
/* TODO test b2sum -cLUxzSPK, implicit -L, restrictions on -l/-X, and file operand */
return failed;
}