#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/spinlock.h>
#include <sys/spinlock2.h>
#include <sys/csprng.h>
#define CHACHA_EMBED
#define CHACHA_NONCE0_CTR128
#define KEYSTREAM_ONLY
#include <crypto/chacha20/chacha.c>
#include <crypto/sha2/sha2.h>
#define MIN_POOL_SIZE (64 + SHA256_DIGEST_LENGTH)
#define MIN_RESEED_INTERVAL hz/10
#if 0
static void csprng_reseed_callout(void *arg);
#endif
static int csprng_reseed(struct csprng_state *state);
static struct timeval csprng_reseed_interval = { 0, 100000 };
static
int
csprng_pool_init(struct csprng_pool *pool, uint8_t *buf, size_t len)
{
pool->bytes = 0;
SHA256_Init(&pool->hash_ctx);
if (len > 0)
SHA256_Update(&pool->hash_ctx, buf, len);
return 0;
}
int
csprng_init(struct csprng_state *state)
{
int i, r;
bzero(state->key, sizeof(state->key));
bzero(&state->cipher_ctx, sizeof(state->cipher_ctx));
bzero(state->src_pool_idx, sizeof(state->src_pool_idx));
bzero(&state->last_reseed, sizeof(state->last_reseed));
state->reseed_cnt = 0;
state->failed_reseeds = 0;
state->callout_based_reseed = 0;
for (i = 0; i < 32; i++) {
r = csprng_pool_init(&state->pool[i], NULL, 0);
if (r != 0)
break;
}
return r;
}
#if 0
int
csprng_init_reseed(struct csprng_state *state)
{
state->callout_based_reseed = 1;
callout_init_mp(&state->reseed_callout);
callout_reset(&state->reseed_callout, MIN_RESEED_INTERVAL,
csprng_reseed_callout, state);
return 0;
}
#endif
int
csprng_get_random(struct csprng_state *state, uint8_t *out, int bytes,
int flags)
{
int cnt;
int total_bytes = 0;
again:
if (!state->callout_based_reseed &&
ratecheck(&state->last_reseed, &csprng_reseed_interval)) {
csprng_reseed(state);
}
if ((flags & CSPRNG_UNLIMITED) == 0 && state->reseed_cnt == 0) {
ssleep(state, &state->spin, 0, "csprngrsd", 0);
goto again;
}
while (bytes > 0) {
cnt = (bytes > (1 << 20)) ? (1 << 20) : bytes;
chacha_encrypt_bytes(&state->cipher_ctx, NULL, out, cnt);
chacha_encrypt_bytes(&state->cipher_ctx, NULL, state->key,
sizeof(state->key));
chacha_keysetup(&state->cipher_ctx, state->key,
8 * sizeof(state->key));
out += cnt;
bytes -= cnt;
total_bytes += cnt;
}
return total_bytes;
}
static
int
csprng_reseed(struct csprng_state *state)
{
int i;
struct csprng_pool *pool;
SHA256_CTX hash_ctx;
uint8_t digest[SHA256_DIGEST_LENGTH];
uint8_t counter[16];
if (state->pool[0].bytes < MIN_POOL_SIZE) {
++state->failed_reseeds;
return 1;
}
SHA256_Init(&hash_ctx);
SHA256_Update(&hash_ctx, state->key, sizeof(state->key));
state->reseed_cnt++;
for (i = 0; i < 32; i++) {
if ((state->reseed_cnt % (1 << i)) != 0)
break;
pool = &state->pool[i];
SHA256_Final(digest, &pool->hash_ctx);
csprng_pool_init(pool, digest, sizeof(digest));
SHA256_Update(&hash_ctx, digest, sizeof(digest));
}
SHA256_Final(state->key, &hash_ctx);
chacha_keysetup(&state->cipher_ctx, state->key, 8*sizeof(state->key));
bzero(counter, sizeof(counter));
chacha_ivsetup(&state->cipher_ctx, NULL, counter);
return 0;
}
#if 0
static
void
csprng_reseed_callout(void *arg)
{
struct csprng_state *state = (struct csprng_state *)arg;
int reseed_interval = MIN_RESEED_INTERVAL;
spin_lock(&state->spin);
csprng_reseed(arg);
spin_unlock(&state->spin);
wakeup(state);
callout_reset(&state->reseed_callout, reseed_interval,
csprng_reseed_callout, state);
}
#endif
int
csprng_add_entropy(struct csprng_state *state, int src_id,
const uint8_t *entropy, size_t bytes, int flags)
{
struct csprng_pool *pool;
int pool_id;
src_id &= 0xff;
pool_id = state->src_pool_idx[src_id]++ & 0x1f;
pool = &state->pool[pool_id];
SHA256_Update(&pool->hash_ctx, (const uint8_t *)&src_id,
sizeof(src_id));
SHA256_Update(&pool->hash_ctx, (const uint8_t *)&bytes,
sizeof(bytes));
SHA256_Update(&pool->hash_ctx, entropy, bytes);
pool->bytes += bytes;
return 0;
}