#include <sys/types.h>
#include <sys/conf.h>
#include <sys/sunddi.h>
#include <sys/disp.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/crypto/common.h>
#include <sys/crypto/api.h>
#include <sys/crypto/impl.h>
#include <sys/crypto/sched_impl.h>
#include <sys/crypto/ioctladmin.h>
#include <sys/random.h>
#include <sys/sha1.h>
#include <sys/time.h>
#include <sys/sysmacros.h>
#include <sys/cpuvar.h>
#include <sys/taskq.h>
#include <rng/fips_random.h>
#define RNDPOOLSIZE 1024
#define MINEXTRACTBYTES 20
#define MAXEXTRACTBYTES 1024
#define PRNG_MAXOBLOCKS 1310720
#define TIMEOUT_INTERVAL 5
typedef enum extract_type {
NONBLOCK_EXTRACT,
BLOCKING_EXTRACT,
ALWAYS_EXTRACT
} extract_type_t;
#define HASHSIZE 20
#define HASH_CTX SHA1_CTX
#define HashInit(ctx) SHA1Init((ctx))
#define HashUpdate(ctx, p, s) SHA1Update((ctx), (p), (s))
#define HashFinal(d, ctx) SHA1Final((d), (ctx))
#define HMAC_KEYSIZE 20
uint8_t rndpool[RNDPOOLSIZE];
static int findex, rindex;
static int rnbyte_cnt;
static kmutex_t rndpool_lock;
static kcondvar_t rndpool_read_cv;
static int num_waiters;
static struct pollhead rnd_pollhead;
static timeout_id_t kcf_rndtimeout_id;
static crypto_mech_type_t rngmech_type = CRYPTO_MECH_INVALID;
rnd_stats_t rnd_stats;
static boolean_t rng_prov_found = B_TRUE;
static boolean_t rng_ok_to_log = B_TRUE;
static boolean_t rngprov_task_idle = B_TRUE;
static void rndc_addbytes(uint8_t *, size_t);
static void rndc_getbytes(uint8_t *ptr, size_t len);
static void rnd_handler(void *);
static void rnd_alloc_magazines(void);
static void rnd_fips_discard_initial(void);
static void rnd_init2(void *);
static void rnd_schedule_timeout(void);
void
kcf_rnd_init()
{
hrtime_t ts;
time_t now;
mutex_init(&rndpool_lock, NULL, MUTEX_DEFAULT, NULL);
cv_init(&rndpool_read_cv, NULL, CV_DEFAULT, NULL);
ts = gethrtime();
rndc_addbytes((uint8_t *)&ts, sizeof (ts));
(void) drv_getparm(TIME, &now);
rndc_addbytes((uint8_t *)&now, sizeof (now));
rnbyte_cnt = 0;
findex = rindex = 0;
num_waiters = 0;
rnd_alloc_magazines();
(void) taskq_dispatch(system_taskq, rnd_init2, NULL, TQ_SLEEP);
}
static void
rnd_init2(void *unused)
{
_NOTE(ARGUNUSED(unused));
rngmech_type = crypto_mech2id(SUN_RANDOM);
(void) kcf_rngprov_check();
rnd_fips_discard_initial();
rnd_schedule_timeout();
}
boolean_t
kcf_rngprov_check(void)
{
int rv;
kcf_provider_desc_t *pd;
if ((pd = kcf_get_mech_provider(rngmech_type, NULL, NULL, &rv,
NULL, CRYPTO_FG_RANDOM, 0)) != NULL) {
KCF_PROV_REFRELE(pd);
rng_ok_to_log = B_TRUE;
rng_prov_found = B_TRUE;
return (B_TRUE);
} else {
rng_prov_found = B_FALSE;
return (B_FALSE);
}
}
static void
rngprov_seed(uint8_t *buf, int len, uint_t entropy_est, uint32_t flags)
{
kcf_provider_desc_t *pd = NULL;
if (kcf_get_sw_prov(rngmech_type, &pd, NULL, B_FALSE) ==
CRYPTO_SUCCESS) {
(void) KCF_PROV_SEED_RANDOM(pd, pd->pd_sid, buf, len,
entropy_est, flags, NULL);
KCF_PROV_REFRELE(pd);
}
}
static int
rngprov_getbytes(uint8_t *ptr, size_t need, boolean_t is_taskq_thr)
{
int rv;
int prov_cnt = 0;
int total_bytes = 0;
kcf_provider_desc_t *pd;
kcf_req_params_t params;
kcf_prov_tried_t *list = NULL;
while ((pd = kcf_get_mech_provider(rngmech_type, NULL, NULL, &rv,
list, CRYPTO_FG_RANDOM, 0)) != NULL) {
prov_cnt++;
KCF_WRAP_RANDOM_OPS_PARAMS(¶ms, KCF_OP_RANDOM_GENERATE,
pd->pd_sid, ptr, need, 0, 0);
rv = kcf_submit_request(pd, NULL, NULL, ¶ms, B_FALSE);
ASSERT(rv != CRYPTO_QUEUED);
if (rv == CRYPTO_SUCCESS) {
total_bytes += need;
if (is_taskq_thr)
rndc_addbytes(ptr, need);
else {
KCF_PROV_REFRELE(pd);
break;
}
}
if (is_taskq_thr || rv != CRYPTO_SUCCESS) {
if (kcf_insert_triedlist(&list, pd, KM_SLEEP) == NULL) {
KCF_PROV_REFRELE(pd);
break;
}
}
}
if (list != NULL)
kcf_free_triedlist(list);
if (prov_cnt == 0) {
rng_prov_found = B_FALSE;
return (-1);
} else {
rng_prov_found = B_TRUE;
rng_ok_to_log = B_TRUE;
}
return (total_bytes);
}
static void
notify_done(void *arg, int rv)
{
uchar_t *rndbuf = arg;
if (rv == CRYPTO_SUCCESS)
rndc_addbytes(rndbuf, MINEXTRACTBYTES);
bzero(rndbuf, MINEXTRACTBYTES);
kmem_free(rndbuf, MINEXTRACTBYTES);
}
static int
rngprov_getbytes_nblk(uint8_t *ptr, size_t len)
{
int rv, total_bytes;
size_t blen;
uchar_t *rndbuf;
kcf_provider_desc_t *pd;
kcf_req_params_t params;
crypto_call_req_t req;
kcf_prov_tried_t *list = NULL;
int prov_cnt = 0;
blen = 0;
total_bytes = 0;
req.cr_flag = CRYPTO_SKIP_REQID;
req.cr_callback_func = notify_done;
while ((pd = kcf_get_mech_provider(rngmech_type, NULL, NULL, &rv,
list, CRYPTO_FG_RANDOM, 0)) != NULL) {
prov_cnt ++;
switch (pd->pd_prov_type) {
case CRYPTO_HW_PROVIDER:
rndbuf = kmem_alloc(MINEXTRACTBYTES, KM_NOSLEEP);
if (rndbuf == NULL) {
KCF_PROV_REFRELE(pd);
if (list != NULL)
kcf_free_triedlist(list);
return (total_bytes);
}
req.cr_callback_arg = rndbuf;
KCF_WRAP_RANDOM_OPS_PARAMS(¶ms,
KCF_OP_RANDOM_GENERATE,
pd->pd_sid, rndbuf, MINEXTRACTBYTES, 0, 0);
break;
case CRYPTO_SW_PROVIDER:
KCF_WRAP_RANDOM_OPS_PARAMS(¶ms,
KCF_OP_RANDOM_GENERATE,
pd->pd_sid, ptr, len, 0, 0);
break;
}
rv = kcf_submit_request(pd, NULL, &req, ¶ms, B_FALSE);
if (rv == CRYPTO_SUCCESS) {
switch (pd->pd_prov_type) {
case CRYPTO_HW_PROVIDER:
blen = min(MINEXTRACTBYTES, len);
bcopy(rndbuf, ptr, blen);
if (len < MINEXTRACTBYTES)
rndc_addbytes(rndbuf + len,
MINEXTRACTBYTES - len);
ptr += blen;
len -= blen;
total_bytes += blen;
break;
case CRYPTO_SW_PROVIDER:
total_bytes += len;
len = 0;
break;
}
}
if (pd->pd_prov_type == CRYPTO_HW_PROVIDER &&
rv != CRYPTO_QUEUED) {
bzero(rndbuf, MINEXTRACTBYTES);
kmem_free(rndbuf, MINEXTRACTBYTES);
}
if (len == 0) {
KCF_PROV_REFRELE(pd);
break;
}
if (rv != CRYPTO_SUCCESS) {
if (kcf_insert_triedlist(&list, pd, KM_NOSLEEP) ==
NULL) {
KCF_PROV_REFRELE(pd);
break;
}
}
}
if (list != NULL) {
kcf_free_triedlist(list);
}
if (prov_cnt == 0) {
rng_prov_found = B_FALSE;
return (-1);
} else {
rng_prov_found = B_TRUE;
rng_ok_to_log = B_TRUE;
}
return (total_bytes);
}
static void
rngprov_task(void *arg)
{
int len = (int)(uintptr_t)arg;
uchar_t tbuf[MAXEXTRACTBYTES];
ASSERT(len <= MAXEXTRACTBYTES);
(void) rngprov_getbytes(tbuf, len, B_TRUE);
rngprov_task_idle = B_TRUE;
}
static int
rnd_get_bytes(uint8_t *ptr, size_t len, extract_type_t how)
{
size_t bytes;
int got;
ASSERT(mutex_owned(&rndpool_lock));
if (len <= rnbyte_cnt) {
rndc_getbytes(ptr, len);
mutex_exit(&rndpool_lock);
return (0);
}
mutex_exit(&rndpool_lock);
switch (how) {
case BLOCKING_EXTRACT:
if ((got = rngprov_getbytes(ptr, len, B_FALSE)) == -1)
break;
if (got == len)
return (0);
len -= got;
ptr += got;
break;
case NONBLOCK_EXTRACT:
case ALWAYS_EXTRACT:
if ((got = rngprov_getbytes_nblk(ptr, len)) == -1) {
if (how == NONBLOCK_EXTRACT) {
return (EAGAIN);
}
} else {
if (got == len)
return (0);
len -= got;
ptr += got;
}
if (how == NONBLOCK_EXTRACT && (rnbyte_cnt < len))
return (EAGAIN);
break;
}
mutex_enter(&rndpool_lock);
while (len > 0) {
if (how == BLOCKING_EXTRACT) {
while (rnbyte_cnt < MINEXTRACTBYTES) {
num_waiters++;
if (cv_wait_sig(&rndpool_read_cv,
&rndpool_lock) == 0) {
num_waiters--;
mutex_exit(&rndpool_lock);
return (EINTR);
}
num_waiters--;
}
}
bytes = min(len, rnbyte_cnt);
rndc_getbytes(ptr, bytes);
len -= bytes;
ptr += bytes;
if (len > 0 && how == ALWAYS_EXTRACT) {
while (len > 0) {
*ptr = rndpool[findex];
ptr++; len--;
rindex = findex = (findex + 1) &
(RNDPOOLSIZE - 1);
}
break;
}
}
mutex_exit(&rndpool_lock);
return (0);
}
int
kcf_rnd_get_bytes(uint8_t *ptr, size_t len, boolean_t noblock)
{
extract_type_t how;
int error;
how = noblock ? NONBLOCK_EXTRACT : BLOCKING_EXTRACT;
mutex_enter(&rndpool_lock);
if ((error = rnd_get_bytes(ptr, len, how)) != 0)
return (error);
BUMP_RND_STATS(rs_rndOut, len);
return (0);
}
#define RND_CPU_CACHE_SIZE 64
#define RND_CPU_PAD_SIZE RND_CPU_CACHE_SIZE*6
#define RND_CPU_PAD (RND_CPU_PAD_SIZE - \
sizeof (rndmag_t))
typedef struct rndmag_s
{
kmutex_t rm_lock;
uint8_t *rm_buffer;
uint8_t *rm_eptr;
uint8_t *rm_rptr;
uint32_t rm_oblocks;
uint32_t rm_ofuzz;
uint32_t rm_olimit;
rnd_stats_t rm_stats;
uint32_t rm_key[HASHSIZE/BYTES_IN_WORD];
uint32_t rm_seed[HASHSIZE/BYTES_IN_WORD];
uint32_t rm_previous[HASHSIZE/BYTES_IN_WORD];
} rndmag_t;
typedef struct rndmag_pad_s
{
rndmag_t rm_mag;
uint8_t rm_pad[RND_CPU_PAD];
} rndmag_pad_t;
static int
rnd_generate_pseudo_bytes(rndmag_pad_t *rmp, uint8_t *ptr, size_t len)
{
size_t bytes = len, size;
int nblock;
uint32_t oblocks;
uint32_t tempout[HASHSIZE/BYTES_IN_WORD];
uint32_t seed[HASHSIZE/BYTES_IN_WORD];
int i;
hrtime_t timestamp;
uint8_t *src, *dst;
ASSERT(mutex_owned(&rmp->rm_mag.rm_lock));
if (len == 0) {
mutex_exit(&rmp->rm_mag.rm_lock);
return (0);
}
nblock = howmany(len, HASHSIZE);
rmp->rm_mag.rm_oblocks += nblock;
oblocks = rmp->rm_mag.rm_oblocks;
do {
if (oblocks >= rmp->rm_mag.rm_olimit) {
if (rmp->rm_mag.rm_ofuzz) {
if ((rnbyte_cnt < MINEXTRACTBYTES) ||
(!mutex_tryenter(&rndpool_lock))) {
rmp->rm_mag.rm_olimit +=
rmp->rm_mag.rm_ofuzz;
rmp->rm_mag.rm_ofuzz >>= 1;
goto punt;
}
} else {
mutex_enter(&rndpool_lock);
}
(void) rnd_get_bytes((uint8_t *)rmp->rm_mag.rm_key,
HMAC_KEYSIZE, ALWAYS_EXTRACT);
rmp->rm_mag.rm_olimit = PRNG_MAXOBLOCKS/2;
rmp->rm_mag.rm_ofuzz = PRNG_MAXOBLOCKS/4;
oblocks = 0;
rmp->rm_mag.rm_oblocks = nblock;
}
punt:
timestamp = gethrtime();
src = (uint8_t *)×tamp;
dst = (uint8_t *)rmp->rm_mag.rm_seed;
for (i = 0; i < HASHSIZE; i++) {
dst[i] ^= src[i % sizeof (timestamp)];
}
bcopy(rmp->rm_mag.rm_seed, seed, HASHSIZE);
fips_random_inner(rmp->rm_mag.rm_key, tempout,
seed);
if (bytes >= HASHSIZE) {
size = HASHSIZE;
} else {
size = min(bytes, HASHSIZE);
}
for (i = 0; i < HASHSIZE/BYTES_IN_WORD; i++) {
if (tempout[i] != rmp->rm_mag.rm_previous[i])
break;
}
if (i == HASHSIZE/BYTES_IN_WORD) {
cmn_err(CE_WARN, "kcf_random: The value of 160-bit "
"block random bytes are same as the previous "
"one.\n");
mutex_exit(&rmp->rm_mag.rm_lock);
return (EIO);
}
bcopy(tempout, rmp->rm_mag.rm_previous,
HASHSIZE);
bcopy(tempout, ptr, size);
ptr += size;
bytes -= size;
oblocks++;
nblock--;
} while (bytes > 0);
bzero(seed, HASHSIZE);
bzero(tempout, HASHSIZE);
mutex_exit(&rmp->rm_mag.rm_lock);
return (0);
}
static rndmag_pad_t *rndmag;
static uint8_t *rndbuf;
static size_t rndmag_total;
static uint32_t random_max_ncpus = 0;
size_t rndmag_threshold = 2560;
size_t rndbuf_len = 5120;
size_t rndmag_size = 1280;
int
kcf_rnd_get_pseudo_bytes(uint8_t *ptr, size_t len)
{
rndmag_pad_t *rmp;
uint8_t *cptr, *eptr;
ASSERT(len > 0);
for (;;) {
rmp = &rndmag[CPU->cpu_seqid];
mutex_enter(&rmp->rm_mag.rm_lock);
if (len > rndmag_threshold) {
BUMP_CPU_RND_STATS(rmp, rs_urndOut, len);
return (rnd_generate_pseudo_bytes(rmp, ptr, len));
}
cptr = rmp->rm_mag.rm_rptr;
eptr = cptr + len;
if (eptr <= rmp->rm_mag.rm_eptr) {
rmp->rm_mag.rm_rptr = eptr;
bcopy(cptr, ptr, len);
BUMP_CPU_RND_STATS(rmp, rs_urndOut, len);
mutex_exit(&rmp->rm_mag.rm_lock);
return (0);
}
rmp->rm_mag.rm_rptr = rmp->rm_mag.rm_buffer;
(void) rnd_generate_pseudo_bytes(rmp, rmp->rm_mag.rm_buffer,
rndbuf_len);
}
}
static void
rnd_alloc_magazines()
{
rndmag_pad_t *rmp;
int i;
rndbuf_len = roundup(rndbuf_len, HASHSIZE);
if (rndmag_size < rndbuf_len)
rndmag_size = rndbuf_len;
rndmag_size = roundup(rndmag_size, RND_CPU_CACHE_SIZE);
random_max_ncpus = max_ncpus;
rndmag_total = rndmag_size * random_max_ncpus;
rndbuf = kmem_alloc(rndmag_total, KM_SLEEP);
rndmag = kmem_zalloc(sizeof (rndmag_pad_t) * random_max_ncpus,
KM_SLEEP);
for (i = 0; i < random_max_ncpus; i++) {
uint8_t *buf;
rmp = &rndmag[i];
mutex_init(&rmp->rm_mag.rm_lock, NULL, MUTEX_DRIVER, NULL);
buf = rndbuf + i * rndmag_size;
rmp->rm_mag.rm_buffer = buf;
rmp->rm_mag.rm_eptr = buf + rndbuf_len;
rmp->rm_mag.rm_rptr = buf + rndbuf_len;
rmp->rm_mag.rm_oblocks = 1;
}
}
static void
rnd_fips_discard_initial(void)
{
uint8_t discard_buf[HASHSIZE];
rndmag_pad_t *rmp;
int i;
for (i = 0; i < random_max_ncpus; i++) {
rmp = &rndmag[i];
mutex_enter(&rndpool_lock);
(void) rnd_get_bytes(discard_buf,
HMAC_KEYSIZE, ALWAYS_EXTRACT);
bcopy(discard_buf, rmp->rm_mag.rm_previous,
HMAC_KEYSIZE);
mutex_enter(&rndpool_lock);
(void) rnd_get_bytes((uint8_t *)rmp->rm_mag.rm_key,
HMAC_KEYSIZE, ALWAYS_EXTRACT);
mutex_enter(&rndpool_lock);
(void) rnd_get_bytes((uint8_t *)rmp->rm_mag.rm_seed,
HMAC_KEYSIZE, ALWAYS_EXTRACT);
}
}
static void
rnd_schedule_timeout(void)
{
clock_t ut;
ut = 500000 + (clock_t)((((uint32_t)rndpool[findex]) << 12) & 0xFF000);
kcf_rndtimeout_id = timeout(rnd_handler, NULL,
TIMEOUT_INTERVAL * drv_usectohz(ut));
}
void
kcf_rnd_chpoll(short events, int anyyet, short *reventsp,
struct pollhead **phpp)
{
*reventsp = events & POLLOUT;
if (events & (POLLIN | POLLRDNORM)) {
if (rnbyte_cnt >= MINEXTRACTBYTES)
*reventsp |= (events & (POLLIN | POLLRDNORM));
}
if ((*reventsp == 0 && !anyyet) || (events & POLLET))
*phpp = &rnd_pollhead;
}
static void
rnd_handler(void *arg)
{
int len = 0;
if (!rng_prov_found && rng_ok_to_log) {
cmn_err(CE_WARN, "No randomness provider enabled for "
"/dev/random. Use cryptoadm(8) to enable a provider.");
rng_ok_to_log = B_FALSE;
}
if (num_waiters > 0)
len = MAXEXTRACTBYTES;
else if (rnbyte_cnt < RNDPOOLSIZE)
len = MINEXTRACTBYTES;
if (len > 0 && rngprov_task_idle) {
rngprov_task_idle = B_FALSE;
if (taskq_dispatch(system_taskq, rngprov_task,
(void *)(uintptr_t)len, TQ_NOSLEEP | TQ_NOQUEUE) ==
TASKQID_INVALID) {
rngprov_task_idle = B_TRUE;
}
}
mutex_enter(&rndpool_lock);
if (rnbyte_cnt >= MINEXTRACTBYTES)
pollwakeup(&rnd_pollhead, POLLIN | POLLRDNORM);
if (num_waiters > 0)
cv_broadcast(&rndpool_read_cv);
mutex_exit(&rndpool_lock);
rnd_schedule_timeout();
}
static void
rndc_addbytes(uint8_t *ptr, size_t len)
{
ASSERT(ptr != NULL && len > 0);
ASSERT(rnbyte_cnt <= RNDPOOLSIZE);
mutex_enter(&rndpool_lock);
while ((len > 0) && (rnbyte_cnt < RNDPOOLSIZE)) {
rndpool[rindex] ^= *ptr;
ptr++; len--;
rindex = (rindex + 1) & (RNDPOOLSIZE - 1);
rnbyte_cnt++;
}
while (len > 0) {
rndpool[rindex] ^= *ptr;
ptr++; len--;
findex = rindex = (rindex + 1) & (RNDPOOLSIZE - 1);
}
mutex_exit(&rndpool_lock);
}
static void
rndc_getbytes(uint8_t *ptr, size_t len)
{
ASSERT(MUTEX_HELD(&rndpool_lock));
ASSERT(len <= rnbyte_cnt && rnbyte_cnt <= RNDPOOLSIZE);
BUMP_RND_STATS(rs_rndcOut, len);
while (len > 0) {
*ptr = rndpool[findex];
ptr++; len--;
findex = (findex + 1) & (RNDPOOLSIZE - 1);
rnbyte_cnt--;
}
}
int
random_add_pseudo_entropy(uint8_t *ptr, size_t len, uint_t entropy_est)
{
if (len < 1)
return (-1);
rngprov_seed(ptr, len, entropy_est, 0);
return (0);
}
int
random_add_entropy(uint8_t *ptr, size_t len, uint_t entropy_est)
{
if (len < 1)
return (-1);
rngprov_seed(ptr, len, entropy_est, CRYPTO_SEED_NOW);
return (0);
}
int
random_get_pseudo_bytes(uint8_t *ptr, size_t len)
{
ASSERT(!mutex_owned(&rndpool_lock));
if (len < 1)
return (0);
return (kcf_rnd_get_pseudo_bytes(ptr, len));
}
int
random_get_bytes(uint8_t *ptr, size_t len)
{
ASSERT(!mutex_owned(&rndpool_lock));
if (len < 1)
return (0);
return (kcf_rnd_get_bytes(ptr, len, B_TRUE));
}
int
random_get_blocking_bytes(uint8_t *ptr, size_t len)
{
ASSERT(!mutex_owned(&rndpool_lock));
if (len < 1)
return (0);
return (kcf_rnd_get_bytes(ptr, len, B_FALSE));
}