corecrypto/cckprng/crypto_test/crypto_test_kprng.c

675 lines
17 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Copyright (c) (2018-2020) Apple Inc. All rights reserved.
*
* corecrypto is licensed under Apple Inc.s Internal Use License Agreement (which
* is contained in the License.txt file distributed with corecrypto) and only to
* people who accept that license. IMPORTANT: Any license rights granted to you by
* Apple Inc. (if any) are limited to internal use within your organization only on
* devices and computers you own or control, for the sole purpose of verifying the
* security characteristics and correct functioning of the Apple Software. You may
* not, directly or indirectly, redistribute the Apple Software or any portions thereof.
*/
#include <setjmp.h>
#include <unistd.h>
#include "testmore.h"
#include <corecrypto/cc.h>
#include <corecrypto/cckprng.h>
#include "cckprng_internal.h"
#include <corecrypto/ccaes.h>
#include "testbyteBuffer.h"
#if CC_LINUX && !CC_ANDROID
#define CC_PRNG_SEEDTESTS 0
#else
#define CC_PRNG_SEEDTESTS 1
#endif
#include "ccshadow.h"
#if CC_PRNG_SEEDTESTS
struct file {
const char *path;
int fd;
uint8_t buf[256];
size_t len;
size_t pos;
};
static const int seedfd = 3;
static const int randomfd = 4;
static struct file filetab[] = {
{ .path = CCKPRNG_SEEDFILE, .fd = seedfd },
{ .path = CCKPRNG_RANDOMDEV, .fd = randomfd },
};
static struct file *seedfile = &filetab[0];
static struct file *randomdev = &filetab[1];
static struct file *file_lookup_path(const char *path)
{
int i;
for (i = 0; i < 2; i += 1) {
if (strcmp(path, filetab[i].path) == 0) {
return &filetab[i];
}
}
return NULL;
}
static struct file *file_lookup_fd(int fd)
{
int i;
for (i = 0; i < 2; i += 1) {
if (fd == filetab[i].fd) {
return &filetab[i];
}
}
return NULL;
}
static void file_erase(struct file *f)
{
cc_clear(sizeof(f->buf), f->buf);
f->pos = 0;
f->len = 0;
}
static void file_setup(struct file *f, const void *buf, size_t buflen)
{
memcpy(f->buf, buf, buflen);
f->pos = 0;
f->len = buflen;
}
static int open_lookup(const char *path, int oflag, mode_t mode)
{
(void)oflag;
(void)mode;
return file_lookup_path(path)->fd;
}
static int open_seedfile_fail(const char *path, int oflag, mode_t mode)
{
if (strcmp(path, CCKPRNG_SEEDFILE) == 0) {
return -1;
}
return open_lookup(path, oflag, mode);
}
static int open_randomdev_fail(const char *path, int oflag, mode_t mode)
{
if (strcmp(path, CCKPRNG_RANDOMDEV) == 0) {
return -1;
}
return open_lookup(path, oflag, mode);
}
static int close_nop(int fd)
{
(void)fd;
return 0;
}
static ssize_t read_fail(int fd, void *buf, size_t nbytes)
{
(void)fd;
(void)buf;
(void)nbytes;
return -1;
}
static ssize_t write_fail(int fd, const void *buf, size_t nbytes)
{
(void)fd;
(void)buf;
(void)nbytes;
return -1;
}
// The size of this buffer is arbitrary, but it is meant to be larger
// than and indivisible by CCKPRNG_SEEDSIZE
static const uint8_t seed[173] = {
0x14, 0xfb, 0xb6, 0xc1, 0xd3, 0x6d, 0x96, 0x93, 0x99, 0x04, 0x01, 0x41, 0xc9, 0xbe, 0x18, 0xe1, 0x65, 0x9f, 0x4c, 0xc5,
0x93, 0x00, 0x83, 0xb6, 0x7e, 0x02, 0xf0, 0x50, 0xcb, 0xf1, 0xdc, 0x76, 0xce, 0xa8, 0x44, 0x1c, 0x36, 0x1f, 0xa6, 0x8a,
0x17, 0x13, 0x3d, 0x5a, 0x94, 0x7d, 0xd1, 0x04, 0x8e, 0xd9, 0xef, 0xc3, 0x95, 0xa0, 0x40, 0x8a, 0xe2, 0xb2, 0xe5, 0x7c,
0x17, 0x20, 0xa8, 0xbf, 0xd0, 0xc9, 0x8d, 0xb1, 0x18, 0x10, 0x5f, 0x04, 0xac, 0xda, 0xb9, 0x85, 0xc7, 0x9a, 0xfc, 0x40,
0x9b, 0xf3, 0x64, 0x33, 0xa0, 0x13, 0x3c, 0x1c, 0x29, 0x6e, 0x87, 0xb9, 0x4c, 0xa1, 0xd7, 0xb5, 0x77, 0x83, 0xcb, 0x54,
0x39, 0x0e, 0x5f, 0x91, 0x5e, 0x1b, 0xd3, 0x74, 0xc7, 0x5a, 0x92, 0x73, 0x84, 0x7f, 0xe2, 0x2c, 0xf3, 0xfe, 0x26, 0xf5,
0x8d, 0xac, 0x89, 0xff, 0x01, 0x0f, 0x8e, 0x48, 0x20, 0xef, 0x05, 0xe4, 0x45, 0x27, 0x60, 0x77, 0xa7, 0x19, 0xcc, 0x0f,
0xc1, 0x35, 0xe9, 0xbd, 0x9f, 0x75, 0xb1, 0x2f, 0x4e, 0xba, 0xc2, 0x24, 0xc8, 0x7b, 0x09, 0x3f, 0xd3, 0x3b, 0x2c, 0x67,
0x52, 0x80, 0xc8, 0x07, 0x62, 0x01, 0x75, 0xdd, 0x70, 0x61, 0xd6, 0xae, 0x1a,
};
static ssize_t read_manybytes(int fd, void *buf, size_t nbytes)
{
struct file *f = file_lookup_fd(fd);
nbytes = CC_MIN(f->len - f->pos, nbytes);
memcpy(buf, &f->buf[f->pos], nbytes);
f->pos += nbytes;
return (ssize_t)nbytes;
}
static ssize_t read_onebyte(int fd, void *buf, size_t nbytes)
{
(void)nbytes;
return read_manybytes(fd, buf, 1);
}
static ssize_t write_manybytes(int fd, const void *buf, size_t nbytes)
{
struct file *f = file_lookup_fd(fd);
// This should never happen
if (nbytes > sizeof(f->buf) - f->pos) {
return -1;
}
memcpy(&f->buf[f->pos], buf, nbytes);
f->pos += nbytes;
f->len += nbytes;
return (ssize_t)nbytes;
}
static ssize_t write_onebyte(int fd, const void *buf, size_t nbytes)
{
(void)nbytes;
return write_manybytes(fd, buf, 1);
}
static int cckprng_test_loadseed(void)
{
file_setup(seedfile, seed, sizeof(seed));
file_erase(randomdev);
close_mock = close_nop;
open_mock = open_seedfile_fail;
is(cckprng_loadseed(), CCKPRNG_SEEDFILE_OPEN, "cckprng_loadseed open seedfile");
seedfile->pos = 0;
open_mock = open_randomdev_fail;
is(cckprng_loadseed(), CCKPRNG_RANDOMDEV_OPEN, "cckprng_loadseed open random device");
seedfile->pos = 0;
open_mock = open_lookup;
read_mock = read_fail;
is(cckprng_loadseed(), CCKPRNG_SEEDFILE_READ, "cckprng_loadseed read seedfile");
seedfile->pos = 0;
read_mock = read_manybytes;
write_mock = write_fail;
is(cckprng_loadseed(), CCKPRNG_RANDOMDEV_WRITE, "cckprng_loadseed write random device");
seedfile->pos = 0;
open_mock = open_lookup;
read_mock = read_manybytes;
write_mock = write_manybytes;
file_erase(randomdev);
seedfile->pos = 0;
is(cckprng_loadseed(), CCERR_OK, "cckprng_loadseed ok (many-byte read, many-byte write)");
is(randomdev->len, seedfile->len, "cckprng_loadseed size (many-byte read, many-byte write)");
ok_memcmp(seedfile->buf, randomdev->buf, seedfile->len, "cckprng_loadseed match (many-byte read, many-byte write)");
read_mock = read_onebyte;
write_mock = write_manybytes;
file_erase(randomdev);
seedfile->pos = 0;
is(cckprng_loadseed(), CCERR_OK, "cckprng_loadseed ok (one-byte read, many-byte write)");
is(randomdev->len, seedfile->len, "cckprng_loadseed size (one-byte read, many-byte write)");
ok_memcmp(seedfile->buf, randomdev->buf, seedfile->len, "cckprng_loadseed match (one-byte read, many-byte write)");
read_mock = read_manybytes;
write_mock = write_onebyte;
file_erase(randomdev);
seedfile->pos = 0;
is(cckprng_loadseed(), CCERR_OK, "cckprng_loadseed ok (many-byte read, one-byte write)");
is(randomdev->len, seedfile->len, "cckprng_loadseed size (many-byte read, one-byte write)");
ok_memcmp(seedfile->buf, randomdev->buf, seedfile->len, "cckprng_loadseed match (many-byte read, one-byte write)");
read_mock = read_onebyte;
write_mock = write_onebyte;
file_erase(randomdev);
seedfile->pos = 0;
is(cckprng_loadseed(), CCERR_OK, "cckprng_loadseed ok (one-byte read, one-byte write)");
is(randomdev->len, seedfile->len, "cckprng_loadseed size (one-byte read, one-byte write)");
ok_memcmp(seedfile->buf, randomdev->buf, seedfile->len, "cckprng_loadseed match (one-byte read, one-byte write)");
open_mock = NULL;
read_mock = NULL;
write_mock = NULL;
close_mock = NULL;
return 1;
}
static int getentropy_fail(void *buf, size_t buflen)
{
(void)buf;
(void)buflen;
return -1;
}
static int getentropy_seed(void *buf, size_t buflen)
{
memcpy(buf, seed, buflen);
return 0;
}
static int fchmod_fail(int fd, mode_t mode)
{
(void)fd;
(void)mode;
return -1;
}
static int fchmod_nop(int fd, mode_t mode)
{
(void)fd;
(void)mode;
return 0;
}
static int fchown_fail(int fd, uid_t owner, gid_t group)
{
(void)fd;
(void)owner;
(void)group;
return -1;
}
static int fchown_nop(int fd, uid_t owner, gid_t group)
{
(void)fd;
(void)owner;
(void)group;
return 0;
}
static int cckprng_test_storeseed(void)
{
close_mock = close_nop;
getentropy_mock = getentropy_fail;
is(cckprng_storeseed(), CCKPRNG_GETENTROPY, "cckprng_storeseed get entropy");
getentropy_mock = getentropy_seed;
open_mock = open_seedfile_fail;
is(cckprng_storeseed(), CCKPRNG_SEEDFILE_OPEN, "cckprng_storeseed open seedfile");
open_mock = open_lookup;
fchmod_mock = fchmod_fail;
is(cckprng_storeseed(), CCKPRNG_SEEDFILE_CHMOD, "cckprng_storeseed chmod seedfile");
fchmod_mock = fchmod_nop;
fchown_mock = fchown_fail;
is(cckprng_storeseed(), CCKPRNG_SEEDFILE_CHOWN, "cckprng_storeseed chown seedfile");
fchown_mock = fchown_nop;
write_mock = write_manybytes;
file_erase(seedfile);
is(cckprng_storeseed(), CCERR_OK, "cckprng_storeseed ok (many-byte write)");
is(seedfile->len, CCKPRNG_SEEDSIZE, "cckprng_storeseed size (many-byte write)");
ok_memcmp(seedfile->buf, seed, seedfile->len, "cckprng_storeseed match (many-byte write)");
write_mock = write_onebyte;
file_erase(seedfile);
is(cckprng_storeseed(), CCERR_OK, "cckprng_storeseed ok (one-byte write)");
is(seedfile->len, CCKPRNG_SEEDSIZE, "cckprng_storeseed size (one-byte write)");
ok_memcmp(seedfile->buf, seed, seedfile->len, "cckprng_storeseed match (one-byte write)");
getentropy_mock = NULL;
open_mock = NULL;
fchmod_mock = NULL;
fchown_mock = NULL;
write_mock = NULL;
close_mock = NULL;
return 1;
}
#endif /* CC_PRNG_SEEDTESTS */
/*
Test Structures
*/
enum {
OP_INIT,
OP_RESEED,
OP_REFRESH,
OP_GENERATE
};
struct cckprng_op {
unsigned id;
unsigned kind;
bool abort;
};
struct cckprng_vector {
unsigned id;
const char *note;
unsigned nops;
const struct cckprng_op **ops;
};
struct cckprng_op_init {
struct cckprng_op hd;
uint8_t seed[32];
uint8_t nonce[8];
ccrng_fortuna_getentropy getentropy;
};
struct cckprng_op_reseed {
struct cckprng_op hd;
uint64_t nonce;
size_t seed_nbytes;
uint8_t seed[512];
};
struct cckprng_op_refresh {
struct cckprng_op hd;
uint64_t rand;
int32_t nsamples;
bool needreseed;
};
struct cckprng_op_generate {
struct cckprng_op hd;
size_t rand_nbytes;
struct {
const uint8_t rand[512];
const uint8_t key[32];
const uint8_t ctr[16];
} out;
};
struct kat_ctx {
struct cckprng_ctx ctx;
bool reseed;
uint8_t rand[512];
int gen_err;
};
typedef void (*process_fn_t)(struct kat_ctx *, const struct cckprng_op *);
typedef void (*verify_fn_t)(struct kat_ctx *, const struct cckprng_op *);
static uint64_t cckprng_reseed_nonce_static;
static uint64_t cckprng_reseed_nonce_func()
{
return cckprng_reseed_nonce_static;
}
#if CC_USE_L4
#define cckprng_reseed_nonce_mock_func ert_time_now_mock
#elif CC_KERNEL
#define cckprng_reseed_nonce_mock_func mach_absolute_time_mock
#elif CC_DARWIN
#define cckprng_reseed_nonce_mock_func mach_absolute_time_mock
#elif defined(_WIN32)
#define cckprng_reseed_nonce_mock_func cc_absolute_time_mock
#elif CC_LINUX
#if CORECRYPTO_SIMULATE_POSIX_ENVIRONMENT
#define cckprng_reseed_nonce_mock_func mach_absolute_time_mock
#else
#define cckprng_reseed_nonce_mock_func cc_absolute_time_mock
#endif
#endif
static uint64_t rand_static;
static bool cc_rdrand_static(uint64_t *rand)
{
*rand = rand_static;
return true;
}
static int32_t fortuna_get_entropy_nsamples;
static int32_t fortuna_get_entropy_ones(size_t *entropy_nbytes, void *entropy, void *arg)
{
(void) arg;
uint8_t *out = (uint8_t *) entropy;
for (size_t i = 0; i < *entropy_nbytes; i++) {
out[i] = 0x01;
}
return fortuna_get_entropy_nsamples;
}
/*
Process & Verify Functions
*/
static void process_init(struct kat_ctx *kat_ctx, const struct cckprng_op *op)
{
const struct cckprng_op_init *o = (const struct cckprng_op_init *)op;
struct cckprng_ctx *ctx = &kat_ctx->ctx;
cckprng_init(ctx, sizeof(o->seed), o->seed, sizeof(o->nonce), o->nonce, o->getentropy, NULL);
}
static void verify_init(struct kat_ctx *kat_ctx, const struct cckprng_op *op)
{
(void) kat_ctx;
(void) op;
}
static void process_reseed(struct kat_ctx *kat_ctx, const struct cckprng_op *op)
{
const struct cckprng_op_reseed *o = (const struct cckprng_op_reseed *)op;
struct cckprng_ctx *ctx = &kat_ctx->ctx;
cckprng_reseed_nonce_static = o->nonce;
cckprng_reseed(ctx, o->seed_nbytes, o->seed);
}
static void verify_reseed(struct kat_ctx *kat_ctx, const struct cckprng_op *op)
{
(void) kat_ctx;
(void) op;
}
static void process_refresh(struct kat_ctx *kat_ctx, const struct cckprng_op *op)
{
const struct cckprng_op_refresh *o = (const struct cckprng_op_refresh *)op;
struct cckprng_ctx *ctx = &kat_ctx->ctx;
fortuna_get_entropy_nsamples = o->nsamples;
rand_static = o->rand;
cckprng_refresh(ctx);
}
static void verify_refresh(struct kat_ctx *kat_ctx, const struct cckprng_op *op)
{
const struct cckprng_op_refresh *o = (const struct cckprng_op_refresh *)op;
struct cckprng_ctx *ctx = &kat_ctx->ctx;
is(ctx->needreseed, o->needreseed, "refresh needreseed");
}
static void process_generate(struct kat_ctx *kat_ctx, const struct cckprng_op *op)
{
const struct cckprng_op_generate *o = (const struct cckprng_op_generate *)op;
struct cckprng_ctx *ctx = &kat_ctx->ctx;
cckprng_generate(ctx, 0, o->rand_nbytes, kat_ctx->rand);
}
static void verify_generate(struct kat_ctx *kat_ctx, const struct cckprng_op *op)
{
const struct cckprng_op_generate *o = (const struct cckprng_op_generate *)op;
ok_memcmp(kat_ctx->rand, o->out.rand, o->rand_nbytes, "generate rand");
}
/*
KAT Functions
*/
static void cckprng_test_kat(const struct cckprng_vector *vec)
{
struct kat_ctx kat_ctx;
process_fn_t process_fns[] = {
process_init,
process_reseed,
process_refresh,
process_generate
};
verify_fn_t verify_fns[] = {
verify_init,
verify_reseed,
verify_refresh,
verify_generate
};
cc_clear(sizeof(kat_ctx), &kat_ctx);
//cc_abort_mock = cc_abort_longjmp; TKTK
cckprng_reseed_nonce_mock_func = cckprng_reseed_nonce_func;
cc_rdrand_mock = cc_rdrand_static;
for (unsigned i = 0; i < vec->nops; i += 1) {
const struct cckprng_op *op = vec->ops[i];
/* TKTK
if (setjmp(env)) {
ok(op->abort, "erroneous abort");
goto cleanup;
}
*/
// Reset ephemeral bits of state
//kat_ctx.reseed = false;
cc_clear(sizeof(kat_ctx.rand), kat_ctx.rand);
// Process vector
process_fns[op->kind](&kat_ctx, op);
// Verify results
/* TKTK
ok(!op->abort, "no abort");
if (op->abort) {
goto cleanup;
}
*/
verify_fns[op->kind](&kat_ctx, op);
}
//ok(diag_eq(&kat_ctx.ctx, vec), "incorrect diagnostics");
//cleanup:
//cc_abort_mock = NULL;
cckprng_reseed_nonce_mock_func = NULL;
cc_rdrand_mock = NULL;
}
#include "cckprng_kat.inc"
static int cckprng_test_runner() {
diag("Start Fortuna KAT Tests");
for (unsigned i = 0; i < sizeof(test_vectors) / sizeof(test_vectors[0]); i += 1) {
cckprng_test_kat(test_vectors[i]);
}
diag("End Fortuna KAT Tests");
return 0;
}
#if CC_TSAN
#include <pthread.h>
static struct cckprng_ctx tsan_kprng;
static int32_t cckprng_tsan_get_entropy(size_t *nbytes, void *entropy, void *arg) {
(void) arg;
uint8_t *eb = (uint8_t *) entropy;
for (size_t i = 0; i < *nbytes; i++) {
eb[i] = i & 0xff;
}
return 2048;
}
static void *cckprng_tsan_thread_generate(void *arg) {
(void) arg;
uint8_t generate[32] = {0};
for (int i = 0; i < 10000; i++) {
cckprng_generate(&tsan_kprng, 0, sizeof(generate), generate);
}
return NULL;
}
static void *cckprng_tsan_thread_reseed(void *arg) {
(void) arg;
uint8_t reseed[32] = {1,2,3,4,5};
for (int i = 0; i < 10000; i++) {
cckprng_reseed(&tsan_kprng, sizeof(reseed), reseed);
}
return NULL;
}
static void cckprng_tsan_test() {
uint8_t seed[64] = {0};
uint8_t nonce[32] = {0};
cckprng_init(&tsan_kprng, sizeof(seed), seed, sizeof(nonce), nonce, cckprng_tsan_get_entropy, NULL);
pthread_t t_generate, t_reseed;
pthread_create(&t_generate, NULL, cckprng_tsan_thread_generate, NULL);
pthread_create(&t_reseed, NULL, cckprng_tsan_thread_reseed, NULL);
pthread_join(t_generate, NULL);
pthread_join(t_reseed, NULL);
}
#endif
int cckprng_tests(TM_UNUSED int argc, TM_UNUSED char *const *argv)
{
plan_tests(4030);
#if CC_PRNG_SEEDTESTS
ok(cckprng_test_loadseed(), "cckprng_test_loadseed");
ok(cckprng_test_storeseed(), "cckprng_test_storeseed");
#endif /* CC_PRNG_SEEDTESTS */
cckprng_test_runner();
#if CC_TSAN
diag("Start KPRNG TSAN Tests");
cckprng_tsan_test();
diag("End KPRNG TSAN Tests");
#endif
return 0;
}